LCOV - code coverage report
Current view: top level - src/backend/rewrite - rewriteGraphTable.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19beta1 Lines: 97.7 % 397 388
Test Date: 2026-06-24 14:16:41 Functions: 100.0 % 15 15
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * rewriteGraphTable.c
       4              :  *      Support for rewriting GRAPH_TABLE clauses.
       5              :  *
       6              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       7              :  * Portions Copyright (c) 1994, Regents of the University of California
       8              :  *
       9              :  * IDENTIFICATION
      10              :  *    src/backend/rewrite/rewriteGraphTable.c
      11              :  *
      12              :  *-------------------------------------------------------------------------
      13              :  */
      14              : #include "postgres.h"
      15              : 
      16              : #include "access/genam.h"
      17              : #include "access/sysattr.h"
      18              : #include "access/table.h"
      19              : #include "access/htup_details.h"
      20              : #include "catalog/pg_operator.h"
      21              : #include "catalog/pg_propgraph_element.h"
      22              : #include "catalog/pg_propgraph_element_label.h"
      23              : #include "catalog/pg_propgraph_label.h"
      24              : #include "catalog/pg_propgraph_label_property.h"
      25              : #include "catalog/pg_propgraph_property.h"
      26              : #include "miscadmin.h"
      27              : #include "nodes/makefuncs.h"
      28              : #include "nodes/nodeFuncs.h"
      29              : #include "optimizer/optimizer.h"
      30              : #include "parser/analyze.h"
      31              : #include "parser/parse_collate.h"
      32              : #include "parser/parse_func.h"
      33              : #include "parser/parse_node.h"
      34              : #include "parser/parse_oper.h"
      35              : #include "parser/parse_relation.h"
      36              : #include "parser/parsetree.h"
      37              : #include "parser/parse_graphtable.h"
      38              : #include "rewrite/rewriteGraphTable.h"
      39              : #include "rewrite/rewriteHandler.h"
      40              : #include "rewrite/rewriteManip.h"
      41              : #include "utils/array.h"
      42              : #include "utils/builtins.h"
      43              : #include "utils/fmgroids.h"
      44              : #include "utils/lsyscache.h"
      45              : #include "utils/ruleutils.h"
      46              : #include "utils/syscache.h"
      47              : 
      48              : 
      49              : /*
      50              :  * Represents one path factor in a path.
      51              :  *
      52              :  * In a non-cyclic path, one path factor corresponds to one element pattern.
      53              :  *
      54              :  * In a cyclic path, one path factor corresponds to all the element patterns with
      55              :  * the same variable name.
      56              :  */
      57              : struct path_factor
      58              : {
      59              :     GraphElementPatternKind kind;
      60              :     const char *variable;
      61              :     Node       *labelexpr;
      62              :     Node       *whereClause;
      63              :     int         factorpos;      /* Position of this path factor in the list of
      64              :                                  * path factors representing a given path
      65              :                                  * pattern. */
      66              :     List       *labeloids;      /* OIDs of all the labels referenced in
      67              :                                  * labelexpr. */
      68              :     /* Links to adjacent vertex path factors if this is an edge path factor. */
      69              :     struct path_factor *src_pf;
      70              :     struct path_factor *dest_pf;
      71              : };
      72              : 
      73              : /*
      74              :  * Represents one property graph element (vertex or edge) in the path.
      75              :  *
      76              :  * Label expression in an element pattern resolves into a set of elements. We
      77              :  * create one path_element object for each of those elements.
      78              :  */
      79              : struct path_element
      80              : {
      81              :     /* Path factor from which this element is derived. */
      82              :     struct path_factor *path_factor;
      83              :     Oid         elemoid;
      84              :     Oid         reloid;
      85              :     /* Source and destination vertex elements for an edge element. */
      86              :     Oid         srcvertexid;
      87              :     Oid         destvertexid;
      88              :     /* Source and destination conditions for an edge element. */
      89              :     List       *src_quals;
      90              :     List       *dest_quals;
      91              : };
      92              : 
      93              : static Node *replace_property_refs(Oid propgraphid, Node *node, const List *mappings);
      94              : static List *build_edge_vertex_link_quals(HeapTuple edgetup, int edgerti, int refrti, Oid refid, AttrNumber catalog_key_attnum, AttrNumber catalog_ref_attnum, AttrNumber catalog_eqop_attnum);
      95              : static List *generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern);
      96              : static Query *generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path);
      97              : static Node *generate_setop_from_pathqueries(List *pathqueries, List **rtable, List **targetlist);
      98              : static List *generate_queries_for_path_pattern_recurse(RangeTblEntry *rte, List *pathqueries, List *cur_path, List *path_elem_lists, int elempos);
      99              : static Query *generate_query_for_empty_path_pattern(RangeTblEntry *rte);
     100              : static Query *generate_union_from_pathqueries(List **pathqueries);
     101              : static List *get_path_elements_for_path_factor(Oid propgraphid, struct path_factor *pf);
     102              : static bool is_property_associated_with_label(Oid labeloid, Oid propoid);
     103              : static Node *get_element_property_expr(Oid elemoid, Oid propoid, int rtindex);
     104              : 
     105              : /*
     106              :  * Convert GRAPH_TABLE clause into a subquery using relational
     107              :  * operators.
     108              :  */
     109              : Query *
     110          494 : rewriteGraphTable(Query *parsetree, int rt_index)
     111              : {
     112              :     RangeTblEntry *rte;
     113              :     Query      *graph_table_query;
     114              :     List       *path_pattern;
     115          494 :     List       *pathqueries = NIL;
     116              : 
     117          494 :     rte = rt_fetch(rt_index, parsetree->rtable);
     118              : 
     119              :     Assert(list_length(rte->graph_pattern->path_pattern_list) == 1);
     120              : 
     121          494 :     path_pattern = linitial(rte->graph_pattern->path_pattern_list);
     122          494 :     pathqueries = generate_queries_for_path_pattern(rte, path_pattern);
     123          454 :     graph_table_query = generate_union_from_pathqueries(&pathqueries);
     124              : 
     125          454 :     AcquireRewriteLocks(graph_table_query, true, false);
     126              : 
     127          454 :     rte->rtekind = RTE_SUBQUERY;
     128          454 :     rte->subquery = graph_table_query;
     129          454 :     rte->lateral = true;
     130              : 
     131              :     /*
     132              :      * Reset no longer applicable fields, to appease
     133              :      * WRITE_READ_PARSE_PLAN_TREES.
     134              :      */
     135          454 :     rte->graph_pattern = NULL;
     136          454 :     rte->graph_table_columns = NIL;
     137              : 
     138          454 :     return parsetree;
     139              : }
     140              : 
     141              : /*
     142              :  * Generate queries representing the given path pattern applied to the given
     143              :  * property graph.
     144              :  *
     145              :  * A path pattern consists of one or more element patterns. Each of the element
     146              :  * patterns may be satisfied by multiple elements. A path satisfying the given
     147              :  * path pattern consists of one element from each element pattern.  There can be
     148              :  * as many paths as the number of combinations of the elements.  A path pattern
     149              :  * in itself is a K-partite graph where K = number of element patterns in the
     150              :  * path pattern. The possible paths are computed by performing a DFS in this
     151              :  * graph. The DFS is implemented as recursion. Each of these paths is converted
     152              :  * into a query connecting all the elements in that path. Set of these queries is
     153              :  * returned.
     154              :  *
     155              :  * Between every two vertex elements in the path there is an edge element that
     156              :  * connects them.  An edge connects two vertices identified by the source and
     157              :  * destination keys respectively. The connection between an edge and its
     158              :  * adjacent vertex is naturally computed as an equi-join between edge and vertex
     159              :  * table on their respective keys. Hence the query representing one path
     160              :  * consists of JOINs between edge and vertex tables.
     161              :  *
     162              :  * generate_queries_for_path_pattern() starts the recursion but actual work is
     163              :  * done by generate_queries_for_path_pattern_recurse().
     164              :  * generate_query_for_graph_path() constructs a query for a given path.
     165              :  *
     166              :  * A path pattern may end up producing no path if any of the element patterns
     167              :  * yields no elements or the edge patterns yield no edges connecting adjacent
     168              :  * vertex patterns.  In such a case a dummy query which returns no result is
     169              :  * returned (generate_query_for_empty_path_pattern()).
     170              :  *
     171              :  * 'path_pattern' is given path pattern to be applied on the property graph in
     172              :  * the GRAPH_TABLE clause represented by given 'rte'.
     173              :  */
     174              : static List *
     175          494 : generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
     176              : {
     177          494 :     List       *pathqueries = NIL;
     178          494 :     List       *path_elem_lists = NIL;
     179          494 :     int         factorpos = 0;
     180          494 :     List       *path_factors = NIL;
     181          494 :     struct path_factor *prev_pf = NULL;
     182              : 
     183              :     Assert(list_length(path_pattern) > 0);
     184              : 
     185              :     /*
     186              :      * Create a list of path factors representing the given path pattern
     187              :      * linking edge path factors to their adjacent vertex path factors.
     188              :      *
     189              :      * While doing that merge element patterns with the same variable name
     190              :      * into a single path_factor.
     191              :      */
     192         2492 :     foreach_node(GraphElementPattern, gep, path_pattern)
     193              :     {
     194         1528 :         struct path_factor *pf = NULL;
     195              : 
     196              :         /*
     197              :          * Unsupported conditions should have been caught by the parser
     198              :          * itself. We have corresponding Asserts here to document the
     199              :          * assumptions in this code.
     200              :          */
     201              :         Assert(gep->kind == VERTEX_PATTERN || IS_EDGE_PATTERN(gep->kind));
     202              :         Assert(!gep->quantifier);
     203              : 
     204         4591 :         foreach_ptr(struct path_factor, other, path_factors)
     205              :         {
     206         1695 :             if (gep->variable && other->variable &&
     207         1109 :                 strcmp(gep->variable, other->variable) == 0)
     208              :             {
     209          152 :                 if (other->kind != gep->kind)
     210            4 :                     ereport(ERROR,
     211              :                             (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     212              :                              errmsg("element patterns with same variable name \"%s\" but different element pattern types",
     213              :                                     gep->variable)));
     214              : 
     215              :                 /*
     216              :                  * If both the element patterns have label expressions, they
     217              :                  * need to be conjuncted, which is not supported right now.
     218              :                  *
     219              :                  * However, an empty label expression means all labels.
     220              :                  * Conjunction of any label expression with all labels is the
     221              :                  * expression itself. Hence if only one of the two element
     222              :                  * patterns has a label expression use that expression.
     223              :                  */
     224          148 :                 if (!other->labelexpr)
     225          136 :                     other->labelexpr = gep->labelexpr;
     226           12 :                 else if (gep->labelexpr && !equal(other->labelexpr, gep->labelexpr))
     227            4 :                     ereport(ERROR,
     228              :                             (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     229              :                              errmsg("element patterns with same variable name \"%s\" but different label expressions are not supported",
     230              :                                     gep->variable)));
     231              : 
     232              :                 /*
     233              :                  * If two element patterns have the same variable name, they
     234              :                  * represent the same set of graph elements and hence are
     235              :                  * constrained by conditions from both the element patterns.
     236              :                  */
     237          144 :                 if (!other->whereClause)
     238          128 :                     other->whereClause = gep->whereClause;
     239           16 :                 else if (gep->whereClause)
     240            8 :                     other->whereClause = (Node *) makeBoolExpr(AND_EXPR,
     241            8 :                                                                list_make2(other->whereClause, gep->whereClause),
     242              :                                                                -1);
     243          144 :                 pf = other;
     244          144 :                 break;
     245              :             }
     246              :         }
     247              : 
     248         1520 :         if (!pf)
     249              :         {
     250         1376 :             pf = palloc0_object(struct path_factor);
     251         1376 :             pf->factorpos = factorpos++;
     252         1376 :             pf->kind = gep->kind;
     253         1376 :             pf->labelexpr = gep->labelexpr;
     254         1376 :             pf->variable = gep->variable;
     255         1376 :             pf->whereClause = gep->whereClause;
     256              : 
     257         1376 :             path_factors = lappend(path_factors, pf);
     258              :         }
     259              : 
     260              :         /*
     261              :          * Setup links to the previous path factor in the path.
     262              :          *
     263              :          * If the previous path factor represents an edge, this path factor
     264              :          * represents an adjacent vertex; the source vertex for an edge
     265              :          * pointing left or the destination vertex for an edge pointing right.
     266              :          * If this path factor represents an edge, the previous path factor
     267              :          * represents an adjacent vertex; source vertex for an edge pointing
     268              :          * right or the destination vertex for an edge pointing left.
     269              :          *
     270              :          * Edge pointing in any direction is treated similar to that pointing
     271              :          * in right direction here.  When constructing a query in
     272              :          * generate_query_for_graph_path(), we will try links in both the
     273              :          * directions.
     274              :          *
     275              :          * If multiple edge patterns share the same variable name, they
     276              :          * constrain the adjacent vertex patterns since an edge can connect
     277              :          * only one pair of vertices. These adjacent vertex patterns need to
     278              :          * be merged even though they have different variables. Such element
     279              :          * patterns form a walk of graph where vertex and edges are repeated.
     280              :          * For example, in (a)-[b]->(c)<-[b]-(d), (a) and (d) represent the
     281              :          * same vertex element. This is slightly harder to implement and
     282              :          * probably less useful. Hence not supported for now.
     283              :          */
     284         1520 :         if (prev_pf)
     285              :         {
     286         1026 :             if (prev_pf->kind == EDGE_PATTERN_RIGHT || prev_pf->kind == EDGE_PATTERN_ANY)
     287              :             {
     288              :                 Assert(!IS_EDGE_PATTERN(pf->kind));
     289          501 :                 if (prev_pf->dest_pf && prev_pf->dest_pf != pf)
     290            0 :                     ereport(ERROR,
     291              :                             errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     292              :                             errmsg("an edge cannot connect more than two vertices even in a cyclic pattern"));
     293          501 :                 prev_pf->dest_pf = pf;
     294              :             }
     295          525 :             else if (prev_pf->kind == EDGE_PATTERN_LEFT)
     296              :             {
     297              :                 Assert(!IS_EDGE_PATTERN(pf->kind));
     298            8 :                 if (prev_pf->src_pf && prev_pf->src_pf != pf)
     299            0 :                     ereport(ERROR,
     300              :                             errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     301              :                             errmsg("an edge cannot connect more than two vertices even in a cyclic pattern"));
     302            8 :                 prev_pf->src_pf = pf;
     303              :             }
     304              :             else
     305              :             {
     306              :                 Assert(prev_pf->kind == VERTEX_PATTERN);
     307              :                 Assert(IS_EDGE_PATTERN(pf->kind));
     308              :             }
     309              : 
     310         1026 :             if (pf->kind == EDGE_PATTERN_RIGHT || pf->kind == EDGE_PATTERN_ANY)
     311              :             {
     312              :                 Assert(!IS_EDGE_PATTERN(prev_pf->kind));
     313          509 :                 if (pf->src_pf && pf->src_pf != prev_pf)
     314            4 :                     ereport(ERROR,
     315              :                             errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     316              :                             errmsg("an edge cannot connect more than two vertices even in a cyclic pattern"));
     317          505 :                 pf->src_pf = prev_pf;
     318              :             }
     319          517 :             else if (pf->kind == EDGE_PATTERN_LEFT)
     320              :             {
     321              :                 Assert(!IS_EDGE_PATTERN(prev_pf->kind));
     322            8 :                 if (pf->dest_pf && pf->dest_pf != prev_pf)
     323            0 :                     ereport(ERROR,
     324              :                             errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     325              :                             errmsg("an edge cannot connect more than two vertices even in a cyclic pattern"));
     326            8 :                 pf->dest_pf = prev_pf;
     327              :             }
     328              :             else
     329              :             {
     330              :                 Assert(pf->kind == VERTEX_PATTERN);
     331              :                 Assert(IS_EDGE_PATTERN(prev_pf->kind));
     332              :             }
     333              :         }
     334              : 
     335         1516 :         prev_pf = pf;
     336              :     }
     337              : 
     338              :     /*
     339              :      * Collect list of elements for each path factor. Do this after all the
     340              :      * edge links are setup correctly.
     341              :      */
     342         2276 :     foreach_ptr(struct path_factor, pf, path_factors)
     343         1320 :         path_elem_lists = lappend(path_elem_lists,
     344         1328 :                                   get_path_elements_for_path_factor(rte->relid, pf));
     345              : 
     346          474 :     pathqueries = generate_queries_for_path_pattern_recurse(rte, pathqueries,
     347              :                                                             NIL, path_elem_lists, 0);
     348          454 :     if (!pathqueries)
     349            4 :         pathqueries = list_make1(generate_query_for_empty_path_pattern(rte));
     350              : 
     351          454 :     return pathqueries;
     352              : }
     353              : 
     354              : /*
     355              :  * Recursive workhorse function of generate_queries_for_path_pattern().
     356              :  *
     357              :  * `elempos` is the position of the next element being added in the path being
     358              :  * built.
     359              :  */
     360              : static List *
     361        10278 : generate_queries_for_path_pattern_recurse(RangeTblEntry *rte, List *pathqueries, List *cur_path, List *path_elem_lists, int elempos)
     362              : {
     363        10278 :     List       *path_elems = list_nth_node(List, path_elem_lists, elempos);
     364              : 
     365              :     /* Guard against stack overflow due to complex path patterns. */
     366        10278 :     check_stack_depth();
     367              : 
     368        53533 :     foreach_ptr(struct path_element, pe, path_elems)
     369              :     {
     370              :         /* Update current path being built with current element. */
     371        33105 :         cur_path = lappend(cur_path, pe);
     372              : 
     373              :         /*
     374              :          * If this is the last element in the path, generate query for the
     375              :          * completed path. Else recurse processing the next element.
     376              :          */
     377        33105 :         if (list_length(path_elem_lists) == list_length(cur_path))
     378              :         {
     379        23301 :             Query      *pathquery = generate_query_for_graph_path(rte, cur_path);
     380              : 
     381              :             Assert(elempos == list_length(path_elem_lists) - 1);
     382        23281 :             if (pathquery)
     383          837 :                 pathqueries = lappend(pathqueries, pathquery);
     384              :         }
     385              :         else
     386         9804 :             pathqueries = generate_queries_for_path_pattern_recurse(rte, pathqueries,
     387              :                                                                     cur_path,
     388              :                                                                     path_elem_lists,
     389              :                                                                     elempos + 1);
     390              :         /* Make way for the next element at the same position. */
     391        33041 :         cur_path = list_delete_last(cur_path);
     392              :     }
     393              : 
     394        10214 :     return pathqueries;
     395              : }
     396              : 
     397              : /*
     398              :  * Construct a query representing given graph path.
     399              :  *
     400              :  * The query contains:
     401              :  *
     402              :  * 1. targetlist corresponding to the COLUMNS clause of GRAPH_TABLE clause
     403              :  *
     404              :  * 2. quals corresponding to the WHERE clause of individual elements, WHERE
     405              :  * clause in GRAPH_TABLE clause and quals representing edge-vertex links.
     406              :  *
     407              :  * 3. fromlist containing all elements in the path
     408              :  *
     409              :  * The collations of property expressions are obtained from the catalog. The
     410              :  * collations of expressions in COLUMNS and WHERE clauses are assigned before
     411              :  * rewriting the graph table.  The collations of the edge-vertex link quals are
     412              :  * assigned when crafting those quals. Thus everything in the query that requires
     413              :  * collation assignment has been taken care of already. No separate collation
     414              :  * assignment is required in this function.
     415              :  *
     416              :  * More details in the prologue of generate_queries_for_path_pattern().
     417              :  */
     418              : static Query *
     419        23301 : generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path)
     420              : {
     421        23301 :     Query      *path_query = makeNode(Query);
     422        23301 :     List       *fromlist = NIL;
     423        23301 :     List       *qual_exprs = NIL;
     424              :     List       *vars;
     425              : 
     426        23301 :     path_query->commandType = CMD_SELECT;
     427              : 
     428        53537 :     foreach_ptr(struct path_element, pe, graph_path)
     429              :     {
     430        51823 :         struct path_factor *pf = pe->path_factor;
     431              :         RangeTblRef *rtr;
     432              :         Relation    rel;
     433              :         ParseNamespaceItem *pni;
     434              : 
     435              :         Assert(pf->kind == VERTEX_PATTERN || IS_EDGE_PATTERN(pf->kind));
     436              : 
     437              :         /* Add conditions representing edge connections. */
     438        51823 :         if (IS_EDGE_PATTERN(pf->kind))
     439              :         {
     440              :             struct path_element *src_pe;
     441              :             struct path_element *dest_pe;
     442        25545 :             Expr       *edge_qual = NULL;
     443              : 
     444              :             Assert(pf->src_pf && pf->dest_pf);
     445        25545 :             src_pe = list_nth(graph_path, pf->src_pf->factorpos);
     446        25545 :             dest_pe = list_nth(graph_path, pf->dest_pf->factorpos);
     447              : 
     448              :             /* Make sure that the links of adjacent vertices are correct. */
     449              :             Assert(pf->src_pf == src_pe->path_factor &&
     450              :                    pf->dest_pf == dest_pe->path_factor);
     451              : 
     452        25545 :             if (src_pe->elemoid == pe->srcvertexid &&
     453         8817 :                 dest_pe->elemoid == pe->destvertexid)
     454         3093 :                 edge_qual = makeBoolExpr(AND_EXPR,
     455         3093 :                                          list_concat(copyObject(pe->src_quals),
     456         3093 :                                                      copyObject(pe->dest_quals)),
     457              :                                          -1);
     458              : 
     459              :             /*
     460              :              * An edge pattern in any direction matches edges in both
     461              :              * directions, try swapping source and destination. When the
     462              :              * source and destination is the same vertex table, quals
     463              :              * corresponding to either direction may get satisfied. Hence OR
     464              :              * the quals corresponding to both the directions.
     465              :              */
     466        25545 :             if (pf->kind == EDGE_PATTERN_ANY &&
     467          224 :                 dest_pe->elemoid == pe->srcvertexid &&
     468           80 :                 src_pe->elemoid == pe->destvertexid)
     469              :             {
     470           24 :                 List       *src_quals = copyObject(pe->dest_quals);
     471           24 :                 List       *dest_quals = copyObject(pe->src_quals);
     472              :                 Expr       *rev_edge_qual;
     473              : 
     474              :                 /* Swap the source and destination varnos in the quals. */
     475           24 :                 ChangeVarNodes((Node *) dest_quals, pe->path_factor->src_pf->factorpos + 1,
     476           24 :                                pe->path_factor->dest_pf->factorpos + 1, 0);
     477           24 :                 ChangeVarNodes((Node *) src_quals, pe->path_factor->dest_pf->factorpos + 1,
     478           24 :                                pe->path_factor->src_pf->factorpos + 1, 0);
     479              : 
     480           24 :                 rev_edge_qual = makeBoolExpr(AND_EXPR, list_concat(src_quals, dest_quals), -1);
     481           24 :                 if (edge_qual)
     482           16 :                     edge_qual = makeBoolExpr(OR_EXPR, list_make2(edge_qual, rev_edge_qual), -1);
     483              :                 else
     484            8 :                     edge_qual = rev_edge_qual;
     485              :             }
     486              : 
     487              :             /*
     488              :              * If the given edge element does not connect the adjacent vertex
     489              :              * elements in this path, the path is broken. Abandon this path as
     490              :              * it won't return any rows.
     491              :              */
     492        25545 :             if (edge_qual == NULL)
     493        22444 :                 return NULL;
     494              : 
     495         3101 :             qual_exprs = lappend(qual_exprs, edge_qual);
     496              :         }
     497              :         else
     498              :             Assert(!pe->src_quals && !pe->dest_quals);
     499              : 
     500              :         /*
     501              :          * Create RangeTblEntry for this element table.
     502              :          *
     503              :          * SQL/PGQ standard (Ref. Section 11.19, Access rule 2 and General
     504              :          * rule 4) does not specify whose access privileges to use when
     505              :          * accessing the element tables: property graph owner's or current
     506              :          * user's. It is safer to use current user's privileges to avoid
     507              :          * unprivileged data access through a property graph. This is inline
     508              :          * with the views being security_invoker by default.
     509              :          */
     510        29379 :         rel = table_open(pe->reloid, AccessShareLock);
     511        29379 :         pni = addRangeTableEntryForRelation(make_parsestate(NULL), rel, AccessShareLock,
     512              :                                             NULL, true, false);
     513        29379 :         table_close(rel, NoLock);
     514        29379 :         path_query->rtable = lappend(path_query->rtable, pni->p_rte);
     515        29379 :         path_query->rteperminfos = lappend(path_query->rteperminfos, pni->p_perminfo);
     516        29379 :         pni->p_rte->perminfoindex = list_length(path_query->rteperminfos);
     517        29379 :         rtr = makeNode(RangeTblRef);
     518        29379 :         rtr->rtindex = list_length(path_query->rtable);
     519        29379 :         fromlist = lappend(fromlist, rtr);
     520              : 
     521              :         /*
     522              :          * Make sure that the assumption mentioned in create_pe_for_element()
     523              :          * holds true; that the elements' RangeTblEntrys are added in the
     524              :          * order in which their respective path factors appear in the list of
     525              :          * path factors representing the path pattern.
     526              :          */
     527              :         Assert(pf->factorpos + 1 == rtr->rtindex);
     528              : 
     529        29379 :         if (pf->whereClause)
     530              :         {
     531              :             Node       *tr;
     532              : 
     533         3744 :             tr = replace_property_refs(rte->relid, pf->whereClause, list_make1(pe));
     534              : 
     535         3744 :             qual_exprs = lappend(qual_exprs, tr);
     536              :         }
     537              :     }
     538              : 
     539          857 :     if (rte->graph_pattern->whereClause)
     540              :     {
     541          343 :         Node       *path_quals = replace_property_refs(rte->relid,
     542          343 :                                                        (Node *) rte->graph_pattern->whereClause,
     543              :                                                        graph_path);
     544              : 
     545          343 :         qual_exprs = lappend(qual_exprs, path_quals);
     546              :     }
     547              : 
     548         1610 :     path_query->jointree = makeFromExpr(fromlist,
     549          753 :                                         qual_exprs ? (Node *) makeBoolExpr(AND_EXPR, qual_exprs, -1) : NULL);
     550              : 
     551              :     /* Construct query targetlist from COLUMNS specification of GRAPH_TABLE. */
     552          857 :     path_query->targetList = castNode(List,
     553              :                                       replace_property_refs(rte->relid,
     554              :                                                             (Node *) rte->graph_table_columns,
     555              :                                                             graph_path));
     556              : 
     557              :     /*
     558              :      * Mark the columns being accessed in the path query as requiring SELECT
     559              :      * privilege. Any lateral columns should have been handled when the
     560              :      * corresponding ColumnRefs were transformed. Ignore those here.
     561              :      */
     562          837 :     vars = pull_vars_of_level((Node *) list_make2(qual_exprs, path_query->targetList), 0);
     563         9849 :     foreach_node(Var, var, vars)
     564              :     {
     565         8175 :         RTEPermissionInfo *perminfo = getRTEPermissionInfo(path_query->rteperminfos,
     566         8175 :                                                            rt_fetch(var->varno, path_query->rtable));
     567              : 
     568              :         /* Must offset the attnum to fit in a bitmapset */
     569         8175 :         perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
     570         8175 :                                                 var->varattno - FirstLowInvalidHeapAttributeNumber);
     571              :     }
     572              : 
     573          837 :     return path_query;
     574              : }
     575              : 
     576              : /*
     577              :  * Construct a query which would not return any rows.
     578              :  *
     579              :  * More details in the prologue of generate_queries_for_path_pattern().
     580              :  */
     581              : static Query *
     582            4 : generate_query_for_empty_path_pattern(RangeTblEntry *rte)
     583              : {
     584            4 :     Query      *query = makeNode(Query);
     585              : 
     586            4 :     query->commandType = CMD_SELECT;
     587            4 :     query->rtable = NIL;
     588            4 :     query->rteperminfos = NIL;
     589            4 :     query->jointree = makeFromExpr(NIL, (Node *) makeBoolConst(false, false));
     590              : 
     591              :     /*
     592              :      * Even though no rows are returned, the result still projects the same
     593              :      * columns as projected by GRAPH_TABLE clause. Do this by constructing a
     594              :      * target list full of NULL values.
     595              :      */
     596           16 :     foreach_node(TargetEntry, te, rte->graph_table_columns)
     597              :     {
     598            8 :         Node       *nte = (Node *) te->expr;
     599              : 
     600            8 :         te->expr = (Expr *) makeNullConst(exprType(nte), exprTypmod(nte), exprCollation(nte));
     601            8 :         query->targetList = lappend(query->targetList, te);
     602              :     }
     603              : 
     604            4 :     return query;
     605              : }
     606              : 
     607              : /*
     608              :  * Construct a query which is UNION of given path queries.
     609              :  *
     610              :  * The UNION query derives collations of its targetlist entries from the
     611              :  * corresponding targetlist entries of the path queries. The targetlists of path
     612              :  * queries being UNION'ed already have collations assigned.  No separate
     613              :  * collation assignment required in this function.
     614              :  *
     615              :  * The function destroys given pathqueries list while constructing
     616              :  * SetOperationStmt recursively. Hence the function always returns with
     617              :  * `pathqueries` set to NIL.
     618              :  */
     619              : static Query *
     620          454 : generate_union_from_pathqueries(List **pathqueries)
     621              : {
     622          454 :     List       *rtable = NIL;
     623          454 :     Query      *sampleQuery = linitial_node(Query, *pathqueries);
     624              :     SetOperationStmt *sostmt;
     625              :     Query      *union_query;
     626              :     int         resno;
     627              :     ListCell   *lctl,
     628              :                *lct,
     629              :                *lcm,
     630              :                *lcc;
     631              : 
     632              :     Assert(list_length(*pathqueries) > 0);
     633              : 
     634              :     /* If there's only one pathquery, no need to construct a UNION query. */
     635          454 :     if (list_length(*pathqueries) == 1)
     636              :     {
     637          323 :         *pathqueries = NIL;
     638          323 :         return sampleQuery;
     639              :     }
     640              : 
     641          131 :     sostmt = castNode(SetOperationStmt,
     642              :                       generate_setop_from_pathqueries(*pathqueries, &rtable, NULL));
     643              : 
     644              :     /* Encapsulate the set operation statement into a Query. */
     645          131 :     union_query = makeNode(Query);
     646          131 :     union_query->commandType = CMD_SELECT;
     647          131 :     union_query->rtable = rtable;
     648          131 :     union_query->setOperations = (Node *) sostmt;
     649          131 :     union_query->rteperminfos = NIL;
     650          131 :     union_query->jointree = makeFromExpr(NIL, NULL);
     651              : 
     652              :     /*
     653              :      * Generate dummy targetlist for outer query using column names from one
     654              :      * of the queries and common datatypes/collations of topmost set
     655              :      * operation.  It shouldn't matter which query. Also it shouldn't matter
     656              :      * which RT index is used as varno in the target list entries, as long as
     657              :      * it corresponds to a real RT entry; else funny things may happen when
     658              :      * the tree is mashed by rule rewriting. So we use 1 since there's always
     659              :      * one RT entry at least.
     660              :      */
     661              :     Assert(rt_fetch(1, rtable));
     662          131 :     union_query->targetList = NULL;
     663          131 :     resno = 1;
     664          480 :     forfour(lct, sostmt->colTypes,
     665              :             lcm, sostmt->colTypmods,
     666              :             lcc, sostmt->colCollations,
     667              :             lctl, sampleQuery->targetList)
     668              :     {
     669          349 :         Oid         colType = lfirst_oid(lct);
     670          349 :         int32       colTypmod = lfirst_int(lcm);
     671          349 :         Oid         colCollation = lfirst_oid(lcc);
     672          349 :         TargetEntry *sample_tle = (TargetEntry *) lfirst(lctl);
     673              :         char       *colName;
     674              :         TargetEntry *tle;
     675              :         Var        *var;
     676              : 
     677              :         Assert(!sample_tle->resjunk);
     678          349 :         colName = pstrdup(sample_tle->resname);
     679          349 :         var = makeVar(1, sample_tle->resno, colType, colTypmod, colCollation, 0);
     680          349 :         var->location = exprLocation((Node *) sample_tle->expr);
     681          349 :         tle = makeTargetEntry((Expr *) var, (AttrNumber) resno++, colName, false);
     682          349 :         union_query->targetList = lappend(union_query->targetList, tle);
     683              :     }
     684              : 
     685          131 :     *pathqueries = NIL;
     686          131 :     return union_query;
     687              : }
     688              : 
     689              : /*
     690              :  * Construct a query which is UNION of all the given path queries.
     691              :  *
     692              :  * The function destroys given pathqueries list while constructing
     693              :  * SetOperationStmt recursively.
     694              :  */
     695              : static Node *
     696          649 : generate_setop_from_pathqueries(List *pathqueries, List **rtable, List **targetlist)
     697              : {
     698              :     SetOperationStmt *sostmt;
     699              :     Query      *lquery;
     700              :     Node       *rarg;
     701          649 :     RangeTblRef *lrtr = makeNode(RangeTblRef);
     702              :     List       *rtargetlist;
     703              :     ParseNamespaceItem *pni;
     704              : 
     705              :     /* Guard against stack overflow due to many path queries. */
     706          649 :     check_stack_depth();
     707              : 
     708              :     /* Recursion termination condition. */
     709          649 :     if (list_length(pathqueries) == 0)
     710              :     {
     711          131 :         *targetlist = NIL;
     712          131 :         return NULL;
     713              :     }
     714              : 
     715          518 :     lquery = linitial_node(Query, pathqueries);
     716              : 
     717              :     /*
     718              :      * Each path query will become a subquery of the UNION statement. So any
     719              :      * Vars that already refer outside the path query must be adjusted for
     720              :      * additional query level.
     721              :      */
     722          518 :     IncrementVarSublevelsUp((Node *) lquery, 1, 1);
     723              : 
     724          518 :     pni = addRangeTableEntryForSubquery(make_parsestate(NULL), lquery, NULL,
     725              :                                         false, false);
     726          518 :     *rtable = lappend(*rtable, pni->p_rte);
     727          518 :     lrtr->rtindex = list_length(*rtable);
     728          518 :     rarg = generate_setop_from_pathqueries(list_delete_first(pathqueries), rtable, &rtargetlist);
     729          518 :     if (rarg == NULL)
     730              :     {
     731              :         /*
     732              :          * No further path queries in the list. Convert the last query into a
     733              :          * RangeTblRef as expected by SetOperationStmt. Extract a list of the
     734              :          * non-junk TLEs for upper-level processing.
     735              :          */
     736          131 :         if (targetlist)
     737              :         {
     738          131 :             *targetlist = NIL;
     739          611 :             foreach_node(TargetEntry, tle, lquery->targetList)
     740              :             {
     741          349 :                 if (!tle->resjunk)
     742          349 :                     *targetlist = lappend(*targetlist, tle);
     743              :             }
     744              :         }
     745          131 :         return (Node *) lrtr;
     746              :     }
     747              : 
     748          387 :     sostmt = makeNode(SetOperationStmt);
     749          387 :     sostmt->op = SETOP_UNION;
     750          387 :     sostmt->all = true;
     751          387 :     sostmt->larg = (Node *) lrtr;
     752          387 :     sostmt->rarg = rarg;
     753          387 :     constructSetOpTargetlist(NULL, sostmt, lquery->targetList, rtargetlist, targetlist, "UNION", false);
     754              : 
     755          387 :     return (Node *) sostmt;
     756              : }
     757              : 
     758              : /*
     759              :  * Construct a path_element object for the graph element given by `elemoid`
     760              :  * satisfied by the path factor `pf`.
     761              :  *
     762              :  * If the type of graph element does not fit the element pattern kind, the
     763              :  * function returns NULL.
     764              :  */
     765              : static struct path_element *
     766         4617 : create_pe_for_element(struct path_factor *pf, Oid elemoid)
     767              : {
     768         4617 :     HeapTuple   eletup = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(elemoid));
     769              :     Form_pg_propgraph_element pgeform;
     770              :     struct path_element *pe;
     771              : 
     772         4617 :     if (!eletup)
     773            0 :         elog(ERROR, "cache lookup failed for property graph element %u", elemoid);
     774         4617 :     pgeform = ((Form_pg_propgraph_element) GETSTRUCT(eletup));
     775              : 
     776         4617 :     if ((pgeform->pgekind == PGEKIND_VERTEX && pf->kind != VERTEX_PATTERN) ||
     777         4001 :         (pgeform->pgekind == PGEKIND_EDGE && !IS_EDGE_PATTERN(pf->kind)))
     778              :     {
     779         1924 :         ReleaseSysCache(eletup);
     780         1924 :         return NULL;
     781              :     }
     782              : 
     783         2693 :     pe = palloc0_object(struct path_element);
     784         2693 :     pe->path_factor = pf;
     785         2693 :     pe->elemoid = elemoid;
     786         2693 :     pe->reloid = pgeform->pgerelid;
     787              : 
     788              :     /*
     789              :      * When a path is converted into a query
     790              :      * (generate_query_for_graph_path()), a RangeTblEntry will be created for
     791              :      * every element in the path.  Fixing rtindexes of RangeTblEntrys here
     792              :      * makes it possible to craft elements' qual expressions only once while
     793              :      * we have access to the catalog entry. Otherwise they need to be crafted
     794              :      * as many times as the number of paths a given element appears in,
     795              :      * fetching catalog entry again each time.  Hence we simply assume
     796              :      * RangeTblEntrys will be created in the same order in which the
     797              :      * corresponding path factors appear in the list of path factors
     798              :      * representing a path pattern. That way their rtindexes will be same as
     799              :      * path_factor::factorpos + 1.
     800              :      */
     801         2693 :     if (IS_EDGE_PATTERN(pf->kind))
     802              :     {
     803         1245 :         pe->srcvertexid = pgeform->pgesrcvertexid;
     804         1245 :         pe->destvertexid = pgeform->pgedestvertexid;
     805              :         Assert(pf->src_pf && pf->dest_pf);
     806              : 
     807         1245 :         pe->src_quals = build_edge_vertex_link_quals(eletup, pf->factorpos + 1, pf->src_pf->factorpos + 1,
     808              :                                                      pe->srcvertexid,
     809              :                                                      Anum_pg_propgraph_element_pgesrckey,
     810              :                                                      Anum_pg_propgraph_element_pgesrcref,
     811              :                                                      Anum_pg_propgraph_element_pgesrceqop);
     812         1245 :         pe->dest_quals = build_edge_vertex_link_quals(eletup, pf->factorpos + 1, pf->dest_pf->factorpos + 1,
     813              :                                                       pe->destvertexid,
     814              :                                                       Anum_pg_propgraph_element_pgedestkey,
     815              :                                                       Anum_pg_propgraph_element_pgedestref,
     816              :                                                       Anum_pg_propgraph_element_pgedesteqop);
     817              :     }
     818              : 
     819         2693 :     ReleaseSysCache(eletup);
     820              : 
     821         2693 :     return pe;
     822              : }
     823              : 
     824              : /*
     825              :  * Returns the list of OIDs of graph labels which the given label expression
     826              :  * resolves to in the given property graph.
     827              :  */
     828              : static List *
     829         1328 : get_labels_for_expr(Oid propgraphid, Node *labelexpr)
     830              : {
     831              :     List       *label_oids;
     832              : 
     833         1328 :     if (!labelexpr)
     834              :     {
     835              :         Relation    rel;
     836              :         SysScanDesc scan;
     837              :         ScanKeyData key[1];
     838              :         HeapTuple   tup;
     839              : 
     840              :         /*
     841              :          * According to section 9.2 "Contextual inference of a set of labels"
     842              :          * subclause 2.a.ii of SQL/PGQ standard, element pattern which does
     843              :          * not have a label expression is considered to have label expression
     844              :          * equivalent to '%|!%' which is set of all labels.
     845              :          */
     846          408 :         label_oids = NIL;
     847          408 :         rel = table_open(PropgraphLabelRelationId, AccessShareLock);
     848          408 :         ScanKeyInit(&key[0],
     849              :                     Anum_pg_propgraph_label_pglpgid,
     850              :                     BTEqualStrategyNumber,
     851              :                     F_OIDEQ, ObjectIdGetDatum(propgraphid));
     852          408 :         scan = systable_beginscan(rel, PropgraphLabelGraphNameIndexId,
     853              :                                   true, NULL, 1, key);
     854         2728 :         while (HeapTupleIsValid(tup = systable_getnext(scan)))
     855              :         {
     856         2320 :             Form_pg_propgraph_label label = (Form_pg_propgraph_label) GETSTRUCT(tup);
     857              : 
     858         2320 :             label_oids = lappend_oid(label_oids, label->oid);
     859              :         }
     860          408 :         systable_endscan(scan);
     861          408 :         table_close(rel, AccessShareLock);
     862              :     }
     863          920 :     else if (IsA(labelexpr, GraphLabelRef))
     864              :     {
     865          874 :         GraphLabelRef *glr = castNode(GraphLabelRef, labelexpr);
     866              : 
     867          874 :         label_oids = list_make1_oid(glr->labelid);
     868              :     }
     869           46 :     else if (IsA(labelexpr, BoolExpr))
     870              :     {
     871           46 :         BoolExpr   *be = castNode(BoolExpr, labelexpr);
     872           46 :         List       *label_exprs = be->args;
     873              : 
     874           46 :         label_oids = NIL;
     875          188 :         foreach_node(GraphLabelRef, glr, label_exprs)
     876           96 :             label_oids = lappend_oid(label_oids, glr->labelid);
     877              :     }
     878              :     else
     879              :     {
     880              :         /*
     881              :          * should not reach here since gram.y will not generate a label
     882              :          * expression with other node types.
     883              :          */
     884            0 :         elog(ERROR, "unsupported label expression node: %d", (int) nodeTag(labelexpr));
     885              :     }
     886              : 
     887         1328 :     return label_oids;
     888              : }
     889              : 
     890              : /*
     891              :  * Return a list of all the graph elements that satisfy the graph element pattern
     892              :  * represented by the given path_factor `pf`.
     893              :  *
     894              :  * First we find all the graph labels that satisfy the label expression in path
     895              :  * factor. Each label is associated with one or more graph elements.  A union of
     896              :  * all such elements satisfies the element pattern. We create one path_element
     897              :  * object representing every element whose graph element kind qualifies the
     898              :  * element pattern kind. A list of all such path_element objects is returned.
     899              :  *
     900              :  * Note that we need to report an error for an explicitly specified label which
     901              :  * is not associated with any graph element of the required kind. So we have to
     902              :  * treat each label separately. Without that requirement we could have collected
     903              :  * all the unique elements first and then created path_element objects for them
     904              :  * to simplify the code.
     905              :  */
     906              : static List *
     907         1328 : get_path_elements_for_path_factor(Oid propgraphid, struct path_factor *pf)
     908              : {
     909         1328 :     List       *label_oids = get_labels_for_expr(propgraphid, pf->labelexpr);
     910         1328 :     List       *elem_oids_seen = NIL;
     911         1328 :     List       *pf_elem_oids = NIL;
     912         1328 :     List       *path_elements = NIL;
     913         1328 :     List       *unresolved_labels = NIL;
     914              :     Relation    rel;
     915              :     SysScanDesc scan;
     916              :     ScanKeyData key[1];
     917              :     HeapTuple   tup;
     918              : 
     919              :     /*
     920              :      * A property graph element can be either a vertex or an edge. Other types
     921              :      * of path factors like nested path pattern need to be handled separately
     922              :      * when supported.
     923              :      */
     924              :     Assert(pf->kind == VERTEX_PATTERN || IS_EDGE_PATTERN(pf->kind));
     925              : 
     926         1328 :     rel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
     927         5926 :     foreach_oid(labeloid, label_oids)
     928              :     {
     929         3286 :         bool        found = false;
     930              : 
     931         3286 :         ScanKeyInit(&key[0],
     932              :                     Anum_pg_propgraph_element_label_pgellabelid,
     933              :                     BTEqualStrategyNumber,
     934              :                     F_OIDEQ, ObjectIdGetDatum(labeloid));
     935         3286 :         scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId, true,
     936              :                                   NULL, 1, key);
     937        10981 :         while (HeapTupleIsValid(tup = systable_getnext(scan)))
     938              :         {
     939         7695 :             Form_pg_propgraph_element_label label_elem = (Form_pg_propgraph_element_label) GETSTRUCT(tup);
     940         7695 :             Oid         elem_oid = label_elem->pgelelid;
     941              : 
     942         7695 :             if (!list_member_oid(elem_oids_seen, elem_oid))
     943              :             {
     944              :                 /*
     945              :                  * Create path_element object if the new element qualifies the
     946              :                  * element pattern kind.
     947              :                  */
     948         4617 :                 struct path_element *pe = create_pe_for_element(pf, elem_oid);
     949              : 
     950         4617 :                 if (pe)
     951              :                 {
     952         2693 :                     path_elements = lappend(path_elements, pe);
     953              : 
     954              :                     /* Remember qualified elements. */
     955         2693 :                     pf_elem_oids = lappend_oid(pf_elem_oids, elem_oid);
     956         2693 :                     found = true;
     957              :                 }
     958              : 
     959              :                 /*
     960              :                  * Remember qualified and unqualified elements processed so
     961              :                  * far to avoid processing already processed elements again.
     962              :                  */
     963         4617 :                 elem_oids_seen = lappend_oid(elem_oids_seen, label_elem->pgelelid);
     964              :             }
     965         3078 :             else if (list_member_oid(pf_elem_oids, elem_oid))
     966              :             {
     967              :                 /*
     968              :                  * The graph element is known to qualify the given element
     969              :                  * pattern. Flag that the current label has at least one
     970              :                  * qualified element associated with it.
     971              :                  */
     972         1582 :                 found = true;
     973              :             }
     974              :         }
     975              : 
     976         3286 :         if (!found)
     977              :         {
     978              :             /*
     979              :              * We did not find any qualified element associated with this
     980              :              * label. The label or its properties can not be associated with
     981              :              * the given element pattern. Throw an error if the label was
     982              :              * explicitly specified in the element pattern. Otherwise remember
     983              :              * it for later use.
     984              :              */
     985          984 :             if (!pf->labelexpr)
     986          976 :                 unresolved_labels = lappend_oid(unresolved_labels, labeloid);
     987              :             else
     988            8 :                 ereport(ERROR,
     989              :                         (errcode(ERRCODE_UNDEFINED_OBJECT),
     990              :                          errmsg("no property graph element of type \"%s\" has label \"%s\" associated with it in property graph \"%s\"",
     991              :                                 pf->kind == VERTEX_PATTERN ? "vertex" : "edge",
     992              :                                 get_propgraph_label_name(labeloid),
     993              :                                 get_rel_name(propgraphid))));
     994              :         }
     995              : 
     996         3278 :         systable_endscan(scan);
     997              :     }
     998         1320 :     table_close(rel, AccessShareLock);
     999              : 
    1000              :     /*
    1001              :      * Remove the labels which were not explicitly mentioned in the label
    1002              :      * expression but do not have any qualified elements associated with them.
    1003              :      * Properties associated with such labels may not be referenced. See
    1004              :      * replace_property_refs_mutator() for more details.
    1005              :      */
    1006         1320 :     pf->labeloids = list_difference_oid(label_oids, unresolved_labels);
    1007              : 
    1008         1320 :     return path_elements;
    1009              : }
    1010              : 
    1011              : /*
    1012              :  * Mutating property references into table variables
    1013              :  */
    1014              : 
    1015              : struct replace_property_refs_context
    1016              : {
    1017              :     Oid         propgraphid;
    1018              :     const List *mappings;
    1019              : };
    1020              : 
    1021              : static Node *
    1022        45384 : replace_property_refs_mutator(Node *node, struct replace_property_refs_context *context)
    1023              : {
    1024        45384 :     if (node == NULL)
    1025            0 :         return NULL;
    1026        45384 :     if (IsA(node, Var))
    1027              :     {
    1028           48 :         Var        *var = (Var *) node;
    1029           48 :         Var        *newvar = copyObject(var);
    1030              : 
    1031              :         /*
    1032              :          * If it's already a Var, then it was a lateral reference.  Since we
    1033              :          * are in a subquery after the rewrite, we have to increase the level
    1034              :          * by one.
    1035              :          */
    1036           48 :         newvar->varlevelsup++;
    1037              : 
    1038           48 :         return (Node *) newvar;
    1039              :     }
    1040        45336 :     else if (IsA(node, GraphPropertyRef))
    1041              :     {
    1042        10951 :         GraphPropertyRef *gpr = (GraphPropertyRef *) node;
    1043        10951 :         Node       *n = NULL;
    1044        10951 :         struct path_element *found_mapping = NULL;
    1045        10951 :         struct path_factor *mapping_factor = NULL;
    1046        10951 :         List       *unrelated_labels = NIL;
    1047              : 
    1048        25193 :         foreach_ptr(struct path_element, m, context->mappings)
    1049              :         {
    1050        14242 :             if (m->path_factor->variable && strcmp(gpr->elvarname, m->path_factor->variable) == 0)
    1051              :             {
    1052        10951 :                 found_mapping = m;
    1053        10951 :                 break;
    1054              :             }
    1055              :         }
    1056              : 
    1057              :         /*
    1058              :          * transformGraphTablePropertyRef() would not create a
    1059              :          * GraphPropertyRef for a variable which is not present in the graph
    1060              :          * path pattern.
    1061              :          */
    1062              :         Assert(found_mapping);
    1063              : 
    1064        10951 :         mapping_factor = found_mapping->path_factor;
    1065              : 
    1066              :         /*
    1067              :          * Find property definition for given element through any of the
    1068              :          * associated labels qualifying the given element pattern.
    1069              :          */
    1070        59893 :         foreach_oid(labeloid, mapping_factor->labeloids)
    1071              :         {
    1072        37991 :             Oid         elem_labelid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL,
    1073              :                                                        Anum_pg_propgraph_element_label_oid,
    1074              :                                                        ObjectIdGetDatum(found_mapping->elemoid),
    1075              :                                                        ObjectIdGetDatum(labeloid));
    1076              : 
    1077        37991 :             if (OidIsValid(elem_labelid))
    1078              :             {
    1079        23011 :                 HeapTuple   tup = SearchSysCache2(PROPGRAPHLABELPROP, ObjectIdGetDatum(elem_labelid),
    1080              :                                                   ObjectIdGetDatum(gpr->propid));
    1081              : 
    1082        23011 :                 if (!tup)
    1083              :                 {
    1084              :                     /*
    1085              :                      * The label is associated with the given element but it
    1086              :                      * is not associated with the required property. Check
    1087              :                      * next label.
    1088              :                      */
    1089        11720 :                     continue;
    1090              :                 }
    1091              : 
    1092        11291 :                 n = stringToNode(TextDatumGetCString(SysCacheGetAttrNotNull(PROPGRAPHLABELPROP,
    1093              :                                                                             tup, Anum_pg_propgraph_label_property_plpexpr)));
    1094        11291 :                 ChangeVarNodes(n, 1, mapping_factor->factorpos + 1, 0);
    1095              : 
    1096        11291 :                 ReleaseSysCache(tup);
    1097              :             }
    1098              :             else
    1099              :             {
    1100              :                 /*
    1101              :                  * Label is not associated with the element but it may be
    1102              :                  * associated with the property through some other element.
    1103              :                  * Save it for later use.
    1104              :                  */
    1105        14980 :                 unrelated_labels = lappend_oid(unrelated_labels, labeloid);
    1106              :             }
    1107              :         }
    1108              : 
    1109              :         /* See if we can resolve the property in some other way. */
    1110        10951 :         if (!n)
    1111              :         {
    1112           72 :             bool        prop_associated = false;
    1113              : 
    1114          168 :             foreach_oid(loid, unrelated_labels)
    1115              :             {
    1116           76 :                 if (is_property_associated_with_label(loid, gpr->propid))
    1117              :                 {
    1118           52 :                     prop_associated = true;
    1119           52 :                     break;
    1120              :                 }
    1121              :             }
    1122              : 
    1123           72 :             if (prop_associated)
    1124              :             {
    1125              :                 /*
    1126              :                  * The property is associated with at least one of the labels
    1127              :                  * that satisfy given element pattern. If it's associated with
    1128              :                  * the given element (through some other label), use
    1129              :                  * corresponding value expression. Otherwise NULL. Ref.
    1130              :                  * SQL/PGQ standard section 6.5 Property Reference, General
    1131              :                  * Rule 2.b.
    1132              :                  */
    1133           52 :                 n = get_element_property_expr(found_mapping->elemoid, gpr->propid,
    1134           52 :                                               mapping_factor->factorpos + 1);
    1135              : 
    1136           52 :                 if (!n)
    1137           48 :                     n = (Node *) makeNullConst(gpr->typeId, gpr->typmod, gpr->collation);
    1138              :             }
    1139              : 
    1140              :         }
    1141              : 
    1142        10951 :         if (!n)
    1143           20 :             ereport(ERROR,
    1144              :                     errcode(ERRCODE_UNDEFINED_OBJECT),
    1145              :                     errmsg("property \"%s\" for element variable \"%s\" not found",
    1146              :                            get_propgraph_property_name(gpr->propid), mapping_factor->variable));
    1147              : 
    1148        10931 :         return n;
    1149              :     }
    1150              : 
    1151        34385 :     return expression_tree_mutator(node, replace_property_refs_mutator, context);
    1152              : }
    1153              : 
    1154              : static Node *
    1155         4944 : replace_property_refs(Oid propgraphid, Node *node, const List *mappings)
    1156              : {
    1157              :     struct replace_property_refs_context context;
    1158              : 
    1159         4944 :     context.mappings = mappings;
    1160         4944 :     context.propgraphid = propgraphid;
    1161              : 
    1162         4944 :     return expression_tree_mutator(node, replace_property_refs_mutator, &context);
    1163              : }
    1164              : 
    1165              : /*
    1166              :  * Build join qualification expressions between edge and vertex tables.
    1167              :  */
    1168              : static List *
    1169         2490 : build_edge_vertex_link_quals(HeapTuple edgetup, int edgerti, int refrti, Oid refid, AttrNumber catalog_key_attnum, AttrNumber catalog_ref_attnum, AttrNumber catalog_eqop_attnum)
    1170              : {
    1171         2490 :     List       *quals = NIL;
    1172              :     Form_pg_propgraph_element pgeform;
    1173              :     Datum       datum;
    1174              :     Datum      *d1,
    1175              :                *d2,
    1176              :                *d3;
    1177              :     int         n1,
    1178              :                 n2,
    1179              :                 n3;
    1180         2490 :     ParseState *pstate = make_parsestate(NULL);
    1181         2490 :     Oid         refrelid = GetSysCacheOid1(PROPGRAPHELOID, Anum_pg_propgraph_element_pgerelid, ObjectIdGetDatum(refid));
    1182              : 
    1183         2490 :     pgeform = (Form_pg_propgraph_element) GETSTRUCT(edgetup);
    1184              : 
    1185         2490 :     datum = SysCacheGetAttrNotNull(PROPGRAPHELOID, edgetup, catalog_key_attnum);
    1186         2490 :     deconstruct_array_builtin(DatumGetArrayTypeP(datum), INT2OID, &d1, NULL, &n1);
    1187              : 
    1188         2490 :     datum = SysCacheGetAttrNotNull(PROPGRAPHELOID, edgetup, catalog_ref_attnum);
    1189         2490 :     deconstruct_array_builtin(DatumGetArrayTypeP(datum), INT2OID, &d2, NULL, &n2);
    1190              : 
    1191         2490 :     datum = SysCacheGetAttrNotNull(PROPGRAPHELOID, edgetup, catalog_eqop_attnum);
    1192         2490 :     deconstruct_array_builtin(DatumGetArrayTypeP(datum), OIDOID, &d3, NULL, &n3);
    1193              : 
    1194         2490 :     if (n1 != n2)
    1195            0 :         elog(ERROR, "array size key (%d) vs ref (%d) mismatch for element ID %u", catalog_key_attnum, catalog_ref_attnum, pgeform->oid);
    1196         2490 :     if (n1 != n3)
    1197            0 :         elog(ERROR, "array size key (%d) vs operator (%d) mismatch for element ID %u", catalog_key_attnum, catalog_eqop_attnum, pgeform->oid);
    1198              : 
    1199         5644 :     for (int i = 0; i < n1; i++)
    1200              :     {
    1201         3154 :         AttrNumber  keyattn = DatumGetInt16(d1[i]);
    1202         3154 :         AttrNumber  refattn = DatumGetInt16(d2[i]);
    1203         3154 :         Oid         eqop = DatumGetObjectId(d3[i]);
    1204              :         Var        *keyvar;
    1205              :         Var        *refvar;
    1206              :         Oid         atttypid;
    1207              :         int32       atttypmod;
    1208              :         Oid         attcoll;
    1209              :         HeapTuple   tup;
    1210              :         Form_pg_operator opform;
    1211              :         List       *args;
    1212              :         Oid         actual_arg_types[2];
    1213              :         Oid         declared_arg_types[2];
    1214              :         OpExpr     *linkqual;
    1215              : 
    1216         3154 :         get_atttypetypmodcoll(pgeform->pgerelid, keyattn, &atttypid, &atttypmod, &attcoll);
    1217         3154 :         keyvar = makeVar(edgerti, keyattn, atttypid, atttypmod, attcoll, 0);
    1218         3154 :         get_atttypetypmodcoll(refrelid, refattn, &atttypid, &atttypmod, &attcoll);
    1219         3154 :         refvar = makeVar(refrti, refattn, atttypid, atttypmod, attcoll, 0);
    1220              : 
    1221         3154 :         tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(eqop));
    1222         3154 :         if (!HeapTupleIsValid(tup))
    1223            0 :             elog(ERROR, "cache lookup failed for operator %u", eqop);
    1224         3154 :         opform = (Form_pg_operator) GETSTRUCT(tup);
    1225              :         /* An equality operator is a binary operator returning boolean result. */
    1226              :         Assert(opform->oprkind == 'b'
    1227              :                && RegProcedureIsValid(opform->oprcode)
    1228              :                && opform->oprresult == BOOLOID
    1229              :                && !get_func_retset(opform->oprcode));
    1230              : 
    1231              :         /*
    1232              :          * Prepare operands and cast them to the types required by the
    1233              :          * equality operator. Similar to PK/FK quals, referenced vertex key is
    1234              :          * used as left operand and referencing edge key is used as right
    1235              :          * operand.
    1236              :          */
    1237         3154 :         args = list_make2(refvar, keyvar);
    1238         3154 :         actual_arg_types[0] = exprType((Node *) refvar);
    1239         3154 :         actual_arg_types[1] = exprType((Node *) keyvar);
    1240         3154 :         declared_arg_types[0] = opform->oprleft;
    1241         3154 :         declared_arg_types[1] = opform->oprright;
    1242         3154 :         make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
    1243              : 
    1244         3154 :         linkqual = makeNode(OpExpr);
    1245         3154 :         linkqual->opno = opform->oid;
    1246         3154 :         linkqual->opfuncid = opform->oprcode;
    1247         3154 :         linkqual->opresulttype = opform->oprresult;
    1248         3154 :         linkqual->opretset = false;
    1249              :         /* opcollid and inputcollid will be set by parse_collate.c */
    1250         3154 :         linkqual->args = args;
    1251         3154 :         linkqual->location = -1;
    1252              : 
    1253         3154 :         ReleaseSysCache(tup);
    1254         3154 :         quals = lappend(quals, linkqual);
    1255              :     }
    1256              : 
    1257         2490 :     assign_expr_collations(pstate, (Node *) quals);
    1258              : 
    1259         2490 :     return quals;
    1260              : }
    1261              : 
    1262              : /*
    1263              :  * Check if the given property is associated with the given label.
    1264              :  *
    1265              :  * A label projects the same set of properties through every element it is
    1266              :  * associated with. Find any of the elements and return true if that element is
    1267              :  * associated with the given property. False otherwise.
    1268              :  */
    1269              : static bool
    1270           76 : is_property_associated_with_label(Oid labeloid, Oid propoid)
    1271              : {
    1272              :     Relation    rel;
    1273              :     SysScanDesc scan;
    1274              :     ScanKeyData key[1];
    1275              :     HeapTuple   tup;
    1276           76 :     bool        associated = false;
    1277              : 
    1278           76 :     rel = table_open(PropgraphElementLabelRelationId, RowShareLock);
    1279           76 :     ScanKeyInit(&key[0],
    1280              :                 Anum_pg_propgraph_element_label_pgellabelid,
    1281              :                 BTEqualStrategyNumber,
    1282              :                 F_OIDEQ, ObjectIdGetDatum(labeloid));
    1283           76 :     scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId,
    1284              :                               true, NULL, 1, key);
    1285              : 
    1286           76 :     if (HeapTupleIsValid(tup = systable_getnext(scan)))
    1287              :     {
    1288           76 :         Form_pg_propgraph_element_label ele_label = (Form_pg_propgraph_element_label) GETSTRUCT(tup);
    1289              : 
    1290           76 :         associated = SearchSysCacheExists2(PROPGRAPHLABELPROP,
    1291              :                                            ObjectIdGetDatum(ele_label->oid), ObjectIdGetDatum(propoid));
    1292              :     }
    1293           76 :     systable_endscan(scan);
    1294           76 :     table_close(rel, RowShareLock);
    1295              : 
    1296           76 :     return associated;
    1297              : }
    1298              : 
    1299              : /*
    1300              :  * If given element has the given property associated with it, through any of
    1301              :  * the associated labels, return value expression of the property. Otherwise
    1302              :  * NULL.
    1303              :  */
    1304              : static Node *
    1305           52 : get_element_property_expr(Oid elemoid, Oid propoid, int rtindex)
    1306              : {
    1307              :     Relation    rel;
    1308              :     SysScanDesc scan;
    1309              :     ScanKeyData key[1];
    1310              :     HeapTuple   labeltup;
    1311           52 :     Node       *n = NULL;
    1312              : 
    1313           52 :     rel = table_open(PropgraphElementLabelRelationId, RowShareLock);
    1314           52 :     ScanKeyInit(&key[0],
    1315              :                 Anum_pg_propgraph_element_label_pgelelid,
    1316              :                 BTEqualStrategyNumber,
    1317              :                 F_OIDEQ, ObjectIdGetDatum(elemoid));
    1318           52 :     scan = systable_beginscan(rel, PropgraphElementLabelElementLabelIndexId,
    1319              :                               true, NULL, 1, key);
    1320              : 
    1321          148 :     while (HeapTupleIsValid(labeltup = systable_getnext(scan)))
    1322              :     {
    1323          100 :         Form_pg_propgraph_element_label ele_label = (Form_pg_propgraph_element_label) GETSTRUCT(labeltup);
    1324              : 
    1325          100 :         HeapTuple   proptup = SearchSysCache2(PROPGRAPHLABELPROP,
    1326              :                                               ObjectIdGetDatum(ele_label->oid), ObjectIdGetDatum(propoid));
    1327              : 
    1328          100 :         if (!proptup)
    1329           96 :             continue;
    1330            4 :         n = stringToNode(TextDatumGetCString(SysCacheGetAttrNotNull(PROPGRAPHLABELPROP,
    1331              :                                                                     proptup, Anum_pg_propgraph_label_property_plpexpr)));
    1332            4 :         ChangeVarNodes(n, 1, rtindex, 0);
    1333              : 
    1334            4 :         ReleaseSysCache(proptup);
    1335            4 :         break;
    1336              :     }
    1337           52 :     systable_endscan(scan);
    1338           52 :     table_close(rel, RowShareLock);
    1339              : 
    1340           52 :     return n;
    1341              : }
        

Generated by: LCOV version 2.0-1