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

Generated by: LCOV version 2.0-1