Branch data 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 : 513 : rewriteGraphTable(Query *parsetree, int rt_index)
111 : : {
112 : : RangeTblEntry *rte;
113 : : Query *graph_table_query;
114 : : List *path_pattern;
115 : 513 : List *pathqueries = NIL;
116 : :
117 : 513 : rte = rt_fetch(rt_index, parsetree->rtable);
118 : :
119 : : Assert(list_length(rte->graph_pattern->path_pattern_list) == 1);
120 : :
121 : 513 : path_pattern = linitial(rte->graph_pattern->path_pattern_list);
122 : 513 : pathqueries = generate_queries_for_path_pattern(rte, path_pattern);
123 : 473 : graph_table_query = generate_union_from_pathqueries(&pathqueries);
124 : :
125 : 473 : AcquireRewriteLocks(graph_table_query, true, false);
126 : :
127 : 473 : rte->rtekind = RTE_SUBQUERY;
128 : 473 : rte->subquery = graph_table_query;
129 : 473 : rte->lateral = true;
130 : :
131 : : /*
132 : : * Reset no longer applicable fields, to appease
133 : : * WRITE_READ_PARSE_PLAN_TREES.
134 : : */
135 : 473 : rte->graph_pattern = NULL;
136 : 473 : rte->graph_table_columns = NIL;
137 : :
138 : 473 : 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 : 513 : generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
176 : : {
177 : 513 : List *pathqueries = NIL;
178 : 513 : List *path_elem_lists = NIL;
179 : 513 : int factorpos = 0;
180 : 513 : List *path_factors = NIL;
181 : 513 : 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 [ + - + + : 2563 : foreach_node(GraphElementPattern, gep, path_pattern)
+ + ]
193 : : {
194 : 1561 : 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 [ + + + + : 4678 : foreach_ptr(struct path_factor, other, path_factors)
+ + ]
205 : : {
206 [ + + + + ]: 1716 : if (gep->variable && other->variable &&
207 [ + + ]: 1130 : 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 [ + + ]: 1553 : if (!pf)
249 : : {
250 : 1409 : pf = palloc0_object(struct path_factor);
251 : 1409 : pf->factorpos = factorpos++;
252 : 1409 : pf->kind = gep->kind;
253 : 1409 : pf->labelexpr = gep->labelexpr;
254 : 1409 : pf->variable = gep->variable;
255 : 1409 : pf->whereClause = gep->whereClause;
256 : :
257 : 1409 : 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 [ + + ]: 1553 : if (prev_pf)
285 : : {
286 [ + + + + ]: 1040 : if (prev_pf->kind == EDGE_PATTERN_RIGHT || prev_pf->kind == EDGE_PATTERN_ANY)
287 : : {
288 : : Assert(!IS_EDGE_PATTERN(pf->kind));
289 [ + + - + ]: 508 : 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 : 508 : prev_pf->dest_pf = pf;
294 : : }
295 [ + + ]: 532 : 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 [ + + + + ]: 1040 : if (pf->kind == EDGE_PATTERN_RIGHT || pf->kind == EDGE_PATTERN_ANY)
311 : : {
312 : : Assert(!IS_EDGE_PATTERN(prev_pf->kind));
313 [ + + + + ]: 516 : 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 : 512 : pf->src_pf = prev_pf;
318 : : }
319 [ + + ]: 524 : 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 : 1549 : 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 [ + - + + : 2347 : foreach_ptr(struct path_factor, pf, path_factors)
+ + ]
343 : 1353 : path_elem_lists = lappend(path_elem_lists,
344 : 1361 : get_path_elements_for_path_factor(rte->relid, pf));
345 : :
346 : 493 : pathqueries = generate_queries_for_path_pattern_recurse(rte, pathqueries,
347 : : NIL, path_elem_lists, 0);
348 [ + + ]: 473 : if (!pathqueries)
349 : 4 : pathqueries = list_make1(generate_query_for_empty_path_pattern(rte));
350 : :
351 : 473 : 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 : 10311 : generate_queries_for_path_pattern_recurse(RangeTblEntry *rte, List *pathqueries, List *cur_path, List *path_elem_lists, int elempos)
362 : : {
363 : 10311 : List *path_elems = list_nth_node(List, path_elem_lists, elempos);
364 : :
365 : : /* Guard against stack overflow due to complex path patterns. */
366 : 10311 : check_stack_depth();
367 : :
368 [ + - + + : 53632 : foreach_ptr(struct path_element, pe, path_elems)
+ + ]
369 : : {
370 [ - + ]: 33138 : CHECK_FOR_INTERRUPTS();
371 : :
372 : : /* Update current path being built with current element. */
373 : 33138 : cur_path = lappend(cur_path, pe);
374 : :
375 : : /*
376 : : * If this is the last element in the path, generate query for the
377 : : * completed path. Else recurse processing the next element.
378 : : */
379 [ + + ]: 33138 : if (list_length(path_elem_lists) == list_length(cur_path))
380 : : {
381 : 23320 : Query *pathquery = generate_query_for_graph_path(rte, cur_path);
382 : :
383 : : Assert(elempos == list_length(path_elem_lists) - 1);
384 [ + + ]: 23300 : if (pathquery)
385 : 856 : pathqueries = lappend(pathqueries, pathquery);
386 : : }
387 : : else
388 : 9818 : pathqueries = generate_queries_for_path_pattern_recurse(rte, pathqueries,
389 : : cur_path,
390 : : path_elem_lists,
391 : : elempos + 1);
392 : : /* Make way for the next element at the same position. */
393 : 33074 : cur_path = list_delete_last(cur_path);
394 : : }
395 : :
396 : 10247 : return pathqueries;
397 : : }
398 : :
399 : : /*
400 : : * Construct a query representing given graph path.
401 : : *
402 : : * The query contains:
403 : : *
404 : : * 1. targetlist corresponding to the COLUMNS clause of GRAPH_TABLE clause
405 : : *
406 : : * 2. quals corresponding to the WHERE clause of individual elements, WHERE
407 : : * clause in GRAPH_TABLE clause and quals representing edge-vertex links.
408 : : *
409 : : * 3. fromlist containing all elements in the path
410 : : *
411 : : * The collations of property expressions are obtained from the catalog. The
412 : : * collations of expressions in COLUMNS and WHERE clauses are assigned before
413 : : * rewriting the graph table. The collations of the edge-vertex link quals are
414 : : * assigned when crafting those quals. Thus everything in the query that requires
415 : : * collation assignment has been taken care of already. No separate collation
416 : : * assignment is required in this function.
417 : : *
418 : : * More details in the prologue of generate_queries_for_path_pattern().
419 : : */
420 : : static Query *
421 : 23320 : generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path)
422 : : {
423 : 23320 : Query *path_query = makeNode(Query);
424 : 23320 : List *fromlist = NIL;
425 : 23320 : List *qual_exprs = NIL;
426 : : List *vars;
427 : :
428 : 23320 : path_query->commandType = CMD_SELECT;
429 : :
430 [ + - + + : 53608 : foreach_ptr(struct path_element, pe, graph_path)
+ + ]
431 : : {
432 : 51856 : struct path_factor *pf = pe->path_factor;
433 : : RangeTblRef *rtr;
434 : : Relation rel;
435 : : ParseNamespaceItem *pni;
436 : :
437 : : Assert(pf->kind == VERTEX_PATTERN || IS_EDGE_PATTERN(pf->kind));
438 : :
439 : : /* Add conditions representing edge connections. */
440 [ + + + + : 51856 : if (IS_EDGE_PATTERN(pf->kind))
+ + ]
441 : : {
442 : : struct path_element *src_pe;
443 : : struct path_element *dest_pe;
444 : 25552 : Expr *edge_qual = NULL;
445 : :
446 : : Assert(pf->src_pf && pf->dest_pf);
447 : 25552 : src_pe = list_nth(graph_path, pf->src_pf->factorpos);
448 : 25552 : dest_pe = list_nth(graph_path, pf->dest_pf->factorpos);
449 : :
450 : : /* Make sure that the links of adjacent vertices are correct. */
451 : : Assert(pf->src_pf == src_pe->path_factor &&
452 : : pf->dest_pf == dest_pe->path_factor);
453 : :
454 [ + + ]: 25552 : if (src_pe->elemoid == pe->srcvertexid &&
455 [ + + ]: 8824 : dest_pe->elemoid == pe->destvertexid)
456 : 3100 : edge_qual = makeBoolExpr(AND_EXPR,
457 : 3100 : list_concat(copyObject(pe->src_quals),
458 : 3100 : copyObject(pe->dest_quals)),
459 : : -1);
460 : :
461 : : /*
462 : : * An edge pattern in any direction matches edges in both
463 : : * directions, try swapping source and destination. When the
464 : : * source and destination is the same vertex table, quals
465 : : * corresponding to either direction may get satisfied. Hence OR
466 : : * the quals corresponding to both the directions.
467 : : */
468 [ + + ]: 25552 : if (pf->kind == EDGE_PATTERN_ANY &&
469 [ + + ]: 224 : dest_pe->elemoid == pe->srcvertexid &&
470 [ + + ]: 80 : src_pe->elemoid == pe->destvertexid)
471 : : {
472 : 24 : List *src_quals = copyObject(pe->dest_quals);
473 : 24 : List *dest_quals = copyObject(pe->src_quals);
474 : : Expr *rev_edge_qual;
475 : :
476 : : /* Swap the source and destination varnos in the quals. */
477 : 24 : ChangeVarNodes((Node *) dest_quals, pe->path_factor->src_pf->factorpos + 1,
478 : 24 : pe->path_factor->dest_pf->factorpos + 1, 0);
479 : 24 : ChangeVarNodes((Node *) src_quals, pe->path_factor->dest_pf->factorpos + 1,
480 : 24 : pe->path_factor->src_pf->factorpos + 1, 0);
481 : :
482 : 24 : rev_edge_qual = makeBoolExpr(AND_EXPR, list_concat(src_quals, dest_quals), -1);
483 [ + + ]: 24 : if (edge_qual)
484 : 16 : edge_qual = makeBoolExpr(OR_EXPR, list_make2(edge_qual, rev_edge_qual), -1);
485 : : else
486 : 8 : edge_qual = rev_edge_qual;
487 : : }
488 : :
489 : : /*
490 : : * If the given edge element does not connect the adjacent vertex
491 : : * elements in this path, the path is broken. Abandon this path as
492 : : * it won't return any rows.
493 : : */
494 [ + + ]: 25552 : if (edge_qual == NULL)
495 : 22444 : return NULL;
496 : :
497 : 3108 : qual_exprs = lappend(qual_exprs, edge_qual);
498 : : }
499 : : else
500 : : Assert(!pe->src_quals && !pe->dest_quals);
501 : :
502 : : /*
503 : : * Create RangeTblEntry for this element table.
504 : : *
505 : : * SQL/PGQ standard (Ref. Section 11.19, Access rule 2 and General
506 : : * rule 4) does not specify whose access privileges to use when
507 : : * accessing the element tables: property graph owner's or current
508 : : * user's. It is safer to use current user's privileges to avoid
509 : : * unprivileged data access through a property graph. This is inline
510 : : * with the views being security_invoker by default.
511 : : */
512 : 29412 : rel = table_open(pe->reloid, AccessShareLock);
513 : 29412 : pni = addRangeTableEntryForRelation(make_parsestate(NULL), rel, AccessShareLock,
514 : : NULL, true, false);
515 : 29412 : table_close(rel, NoLock);
516 : 29412 : path_query->rtable = lappend(path_query->rtable, pni->p_rte);
517 : 29412 : path_query->rteperminfos = lappend(path_query->rteperminfos, pni->p_perminfo);
518 : 29412 : pni->p_rte->perminfoindex = list_length(path_query->rteperminfos);
519 : 29412 : rtr = makeNode(RangeTblRef);
520 : 29412 : rtr->rtindex = list_length(path_query->rtable);
521 : 29412 : fromlist = lappend(fromlist, rtr);
522 : :
523 : : /*
524 : : * Make sure that the assumption mentioned in create_pe_for_element()
525 : : * holds true; that the elements' RangeTblEntrys are added in the
526 : : * order in which their respective path factors appear in the list of
527 : : * path factors representing the path pattern.
528 : : */
529 : : Assert(pf->factorpos + 1 == rtr->rtindex);
530 : :
531 [ + + ]: 29412 : if (pf->whereClause)
532 : : {
533 : : Node *tr;
534 : :
535 : 3752 : tr = replace_property_refs(rte->relid, pf->whereClause, list_make1(pe));
536 : :
537 : 3752 : qual_exprs = lappend(qual_exprs, tr);
538 : : }
539 : : }
540 : :
541 [ + + ]: 876 : if (rte->graph_pattern->whereClause)
542 : : {
543 : 350 : Node *path_quals = replace_property_refs(rte->relid,
544 : 350 : (Node *) rte->graph_pattern->whereClause,
545 : : graph_path);
546 : :
547 : 350 : qual_exprs = lappend(qual_exprs, path_quals);
548 : : }
549 : :
550 [ + + ]: 1640 : path_query->jointree = makeFromExpr(fromlist,
551 : 764 : qual_exprs ? (Node *) makeBoolExpr(AND_EXPR, qual_exprs, -1) : NULL);
552 : :
553 : : /* Construct query targetlist from COLUMNS specification of GRAPH_TABLE. */
554 : 876 : path_query->targetList = castNode(List,
555 : : replace_property_refs(rte->relid,
556 : : (Node *) rte->graph_table_columns,
557 : : graph_path));
558 : :
559 : : /*
560 : : * Mark the columns being accessed in the path query as requiring SELECT
561 : : * privilege. Any lateral columns should have been handled when the
562 : : * corresponding ColumnRefs were transformed. Ignore those here.
563 : : */
564 : 856 : vars = pull_vars_of_level((Node *) list_make2(qual_exprs, path_query->targetList), 0);
565 [ + + + + : 9958 : foreach_node(Var, var, vars)
+ + ]
566 : : {
567 : 8246 : RTEPermissionInfo *perminfo = getRTEPermissionInfo(path_query->rteperminfos,
568 : 8246 : rt_fetch(var->varno, path_query->rtable));
569 : :
570 : : /* Must offset the attnum to fit in a bitmapset */
571 : 8246 : perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
572 : 8246 : var->varattno - FirstLowInvalidHeapAttributeNumber);
573 : : }
574 : :
575 : 856 : return path_query;
576 : : }
577 : :
578 : : /*
579 : : * Construct a query which would not return any rows.
580 : : *
581 : : * More details in the prologue of generate_queries_for_path_pattern().
582 : : */
583 : : static Query *
584 : 4 : generate_query_for_empty_path_pattern(RangeTblEntry *rte)
585 : : {
586 : 4 : Query *query = makeNode(Query);
587 : :
588 : 4 : query->commandType = CMD_SELECT;
589 : 4 : query->rtable = NIL;
590 : 4 : query->rteperminfos = NIL;
591 : 4 : query->jointree = makeFromExpr(NIL, (Node *) makeBoolConst(false, false));
592 : :
593 : : /*
594 : : * Even though no rows are returned, the result still projects the same
595 : : * columns as projected by GRAPH_TABLE clause. Do this by constructing a
596 : : * target list full of NULL values.
597 : : */
598 [ + - + + : 16 : foreach_node(TargetEntry, te, rte->graph_table_columns)
+ + ]
599 : : {
600 : 8 : Node *nte = (Node *) te->expr;
601 : :
602 : 8 : te->expr = (Expr *) makeNullConst(exprType(nte), exprTypmod(nte), exprCollation(nte));
603 : 8 : query->targetList = lappend(query->targetList, te);
604 : : }
605 : :
606 : 4 : return query;
607 : : }
608 : :
609 : : /*
610 : : * Construct a query which is UNION of given path queries.
611 : : *
612 : : * The UNION query derives collations of its targetlist entries from the
613 : : * corresponding targetlist entries of the path queries. The targetlists of path
614 : : * queries being UNION'ed already have collations assigned. No separate
615 : : * collation assignment required in this function.
616 : : *
617 : : * The function destroys given pathqueries list while constructing
618 : : * SetOperationStmt recursively. Hence the function always returns with
619 : : * `pathqueries` set to NIL.
620 : : */
621 : : static Query *
622 : 473 : generate_union_from_pathqueries(List **pathqueries)
623 : : {
624 : 473 : List *rtable = NIL;
625 : 473 : Query *sampleQuery = linitial_node(Query, *pathqueries);
626 : : SetOperationStmt *sostmt;
627 : : Query *union_query;
628 : : int resno;
629 : : ListCell *lctl,
630 : : *lct,
631 : : *lcm,
632 : : *lcc;
633 : :
634 : : Assert(list_length(*pathqueries) > 0);
635 : :
636 : : /* If there's only one pathquery, no need to construct a UNION query. */
637 [ + + ]: 473 : if (list_length(*pathqueries) == 1)
638 : : {
639 : 342 : *pathqueries = NIL;
640 : 342 : return sampleQuery;
641 : : }
642 : :
643 : 131 : sostmt = castNode(SetOperationStmt,
644 : : generate_setop_from_pathqueries(*pathqueries, &rtable, NULL));
645 : :
646 : : /* Encapsulate the set operation statement into a Query. */
647 : 131 : union_query = makeNode(Query);
648 : 131 : union_query->commandType = CMD_SELECT;
649 : 131 : union_query->rtable = rtable;
650 : 131 : union_query->setOperations = (Node *) sostmt;
651 : 131 : union_query->rteperminfos = NIL;
652 : 131 : union_query->jointree = makeFromExpr(NIL, NULL);
653 : :
654 : : /*
655 : : * Generate dummy targetlist for outer query using column names from one
656 : : * of the queries and common datatypes/collations of topmost set
657 : : * operation. It shouldn't matter which query. Also it shouldn't matter
658 : : * which RT index is used as varno in the target list entries, as long as
659 : : * it corresponds to a real RT entry; else funny things may happen when
660 : : * the tree is mashed by rule rewriting. So we use 1 since there's always
661 : : * one RT entry at least.
662 : : */
663 : : Assert(rt_fetch(1, rtable));
664 : 131 : union_query->targetList = NULL;
665 : 131 : resno = 1;
666 [ + - + + : 480 : forfour(lct, sostmt->colTypes,
+ - + + +
- + + + -
+ + + + +
- + - + -
+ + ]
667 : : lcm, sostmt->colTypmods,
668 : : lcc, sostmt->colCollations,
669 : : lctl, sampleQuery->targetList)
670 : : {
671 : 349 : Oid colType = lfirst_oid(lct);
672 : 349 : int32 colTypmod = lfirst_int(lcm);
673 : 349 : Oid colCollation = lfirst_oid(lcc);
674 : 349 : TargetEntry *sample_tle = (TargetEntry *) lfirst(lctl);
675 : : char *colName;
676 : : TargetEntry *tle;
677 : : Var *var;
678 : :
679 : : Assert(!sample_tle->resjunk);
680 : 349 : colName = pstrdup(sample_tle->resname);
681 : 349 : var = makeVar(1, sample_tle->resno, colType, colTypmod, colCollation, 0);
682 : 349 : var->location = exprLocation((Node *) sample_tle->expr);
683 : 349 : tle = makeTargetEntry((Expr *) var, (AttrNumber) resno++, colName, false);
684 : 349 : union_query->targetList = lappend(union_query->targetList, tle);
685 : : }
686 : :
687 : 131 : *pathqueries = NIL;
688 : 131 : return union_query;
689 : : }
690 : :
691 : : /*
692 : : * Construct a query which is UNION of all the given path queries.
693 : : *
694 : : * The function destroys given pathqueries list while constructing
695 : : * SetOperationStmt recursively.
696 : : */
697 : : static Node *
698 : 649 : generate_setop_from_pathqueries(List *pathqueries, List **rtable, List **targetlist)
699 : : {
700 : : SetOperationStmt *sostmt;
701 : : Query *lquery;
702 : : Node *rarg;
703 : 649 : RangeTblRef *lrtr = makeNode(RangeTblRef);
704 : : List *rtargetlist;
705 : : ParseNamespaceItem *pni;
706 : :
707 : : /* Guard against stack overflow due to many path queries. */
708 : 649 : check_stack_depth();
709 : :
710 : : /* Recursion termination condition. */
711 [ + + ]: 649 : if (list_length(pathqueries) == 0)
712 : : {
713 : 131 : *targetlist = NIL;
714 : 131 : return NULL;
715 : : }
716 : :
717 : 518 : lquery = linitial_node(Query, pathqueries);
718 : :
719 : : /*
720 : : * Each path query will become a subquery of the UNION statement. So any
721 : : * Vars that already refer outside the path query must be adjusted for
722 : : * additional query level.
723 : : */
724 : 518 : IncrementVarSublevelsUp((Node *) lquery, 1, 1);
725 : :
726 : 518 : pni = addRangeTableEntryForSubquery(make_parsestate(NULL), lquery, NULL,
727 : : false, false);
728 : 518 : *rtable = lappend(*rtable, pni->p_rte);
729 : 518 : lrtr->rtindex = list_length(*rtable);
730 : 518 : rarg = generate_setop_from_pathqueries(list_delete_first(pathqueries), rtable, &rtargetlist);
731 [ + + ]: 518 : if (rarg == NULL)
732 : : {
733 : : /*
734 : : * No further path queries in the list. Convert the last query into a
735 : : * RangeTblRef as expected by SetOperationStmt. Extract a list of the
736 : : * non-junk TLEs for upper-level processing.
737 : : */
738 [ + - ]: 131 : if (targetlist)
739 : : {
740 : 131 : *targetlist = NIL;
741 [ + - + + : 611 : foreach_node(TargetEntry, tle, lquery->targetList)
+ + ]
742 : : {
743 [ + - ]: 349 : if (!tle->resjunk)
744 : 349 : *targetlist = lappend(*targetlist, tle);
745 : : }
746 : : }
747 : 131 : return (Node *) lrtr;
748 : : }
749 : :
750 : 387 : sostmt = makeNode(SetOperationStmt);
751 : 387 : sostmt->op = SETOP_UNION;
752 : 387 : sostmt->all = true;
753 : 387 : sostmt->larg = (Node *) lrtr;
754 : 387 : sostmt->rarg = rarg;
755 : 387 : constructSetOpTargetlist(NULL, sostmt, lquery->targetList, rtargetlist, targetlist, "UNION", false);
756 : :
757 : 387 : return (Node *) sostmt;
758 : : }
759 : :
760 : : /*
761 : : * Construct a path_element object for the graph element given by `elemoid`
762 : : * satisfied by the path factor `pf`.
763 : : *
764 : : * If the type of graph element does not fit the element pattern kind, the
765 : : * function returns NULL.
766 : : */
767 : : static struct path_element *
768 : 4650 : create_pe_for_element(struct path_factor *pf, Oid elemoid)
769 : : {
770 : 4650 : HeapTuple eletup = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(elemoid));
771 : : Form_pg_propgraph_element pgeform;
772 : : struct path_element *pe;
773 : :
774 [ - + ]: 4650 : if (!eletup)
775 [ # # ]: 0 : elog(ERROR, "cache lookup failed for property graph element %u", elemoid);
776 : 4650 : pgeform = ((Form_pg_propgraph_element) GETSTRUCT(eletup));
777 : :
778 [ + + + + ]: 4650 : if ((pgeform->pgekind == PGEKIND_VERTEX && pf->kind != VERTEX_PATTERN) ||
779 [ + + + + : 4034 : (pgeform->pgekind == PGEKIND_EDGE && !IS_EDGE_PATTERN(pf->kind)))
+ + + + ]
780 : : {
781 : 1924 : ReleaseSysCache(eletup);
782 : 1924 : return NULL;
783 : : }
784 : :
785 : 2726 : pe = palloc0_object(struct path_element);
786 : 2726 : pe->path_factor = pf;
787 : 2726 : pe->elemoid = elemoid;
788 : 2726 : pe->reloid = pgeform->pgerelid;
789 : :
790 : : /*
791 : : * When a path is converted into a query
792 : : * (generate_query_for_graph_path()), a RangeTblEntry will be created for
793 : : * every element in the path. Fixing rtindexes of RangeTblEntrys here
794 : : * makes it possible to craft elements' qual expressions only once while
795 : : * we have access to the catalog entry. Otherwise they need to be crafted
796 : : * as many times as the number of paths a given element appears in,
797 : : * fetching catalog entry again each time. Hence we simply assume
798 : : * RangeTblEntrys will be created in the same order in which the
799 : : * corresponding path factors appear in the list of path factors
800 : : * representing a path pattern. That way their rtindexes will be same as
801 : : * path_factor::factorpos + 1.
802 : : */
803 [ + + + + : 2726 : if (IS_EDGE_PATTERN(pf->kind))
+ + ]
804 : : {
805 : 1252 : pe->srcvertexid = pgeform->pgesrcvertexid;
806 : 1252 : pe->destvertexid = pgeform->pgedestvertexid;
807 : : Assert(pf->src_pf && pf->dest_pf);
808 : :
809 : 1252 : pe->src_quals = build_edge_vertex_link_quals(eletup, pf->factorpos + 1, pf->src_pf->factorpos + 1,
810 : : pe->srcvertexid,
811 : : Anum_pg_propgraph_element_pgesrckey,
812 : : Anum_pg_propgraph_element_pgesrcref,
813 : : Anum_pg_propgraph_element_pgesrceqop);
814 : 1252 : pe->dest_quals = build_edge_vertex_link_quals(eletup, pf->factorpos + 1, pf->dest_pf->factorpos + 1,
815 : : pe->destvertexid,
816 : : Anum_pg_propgraph_element_pgedestkey,
817 : : Anum_pg_propgraph_element_pgedestref,
818 : : Anum_pg_propgraph_element_pgedesteqop);
819 : : }
820 : :
821 : 2726 : ReleaseSysCache(eletup);
822 : :
823 : 2726 : return pe;
824 : : }
825 : :
826 : : /*
827 : : * Returns the list of OIDs of graph labels which the given label expression
828 : : * resolves to in the given property graph.
829 : : */
830 : : static List *
831 : 1361 : get_labels_for_expr(Oid propgraphid, Node *labelexpr)
832 : : {
833 : : List *label_oids;
834 : :
835 [ + + ]: 1361 : if (!labelexpr)
836 : : {
837 : : Relation rel;
838 : : SysScanDesc scan;
839 : : ScanKeyData key[1];
840 : : HeapTuple tup;
841 : :
842 : : /*
843 : : * According to section 9.2 "Contextual inference of a set of labels"
844 : : * subclause 2.a.ii of SQL/PGQ standard, element pattern which does
845 : : * not have a label expression is considered to have label expression
846 : : * equivalent to '%|!%' which is set of all labels.
847 : : */
848 : 408 : label_oids = NIL;
849 : 408 : rel = table_open(PropgraphLabelRelationId, AccessShareLock);
850 : 408 : ScanKeyInit(&key[0],
851 : : Anum_pg_propgraph_label_pglpgid,
852 : : BTEqualStrategyNumber,
853 : : F_OIDEQ, ObjectIdGetDatum(propgraphid));
854 : 408 : scan = systable_beginscan(rel, PropgraphLabelGraphNameIndexId,
855 : : true, NULL, 1, key);
856 [ + + ]: 2728 : while (HeapTupleIsValid(tup = systable_getnext(scan)))
857 : : {
858 : 2320 : Form_pg_propgraph_label label = (Form_pg_propgraph_label) GETSTRUCT(tup);
859 : :
860 : 2320 : label_oids = lappend_oid(label_oids, label->oid);
861 : : }
862 : 408 : systable_endscan(scan);
863 : 408 : table_close(rel, AccessShareLock);
864 : : }
865 [ + + ]: 953 : else if (IsA(labelexpr, GraphLabelRef))
866 : : {
867 : 907 : GraphLabelRef *glr = castNode(GraphLabelRef, labelexpr);
868 : :
869 : 907 : label_oids = list_make1_oid(glr->labelid);
870 : : }
871 [ + - ]: 46 : else if (IsA(labelexpr, BoolExpr))
872 : : {
873 : 46 : BoolExpr *be = castNode(BoolExpr, labelexpr);
874 : 46 : List *label_exprs = be->args;
875 : :
876 : 46 : label_oids = NIL;
877 [ + - + + : 188 : foreach_node(GraphLabelRef, glr, label_exprs)
+ + ]
878 : 96 : label_oids = lappend_oid(label_oids, glr->labelid);
879 : : }
880 : : else
881 : : {
882 : : /*
883 : : * should not reach here since gram.y will not generate a label
884 : : * expression with other node types.
885 : : */
886 [ # # ]: 0 : elog(ERROR, "unsupported label expression node: %d", (int) nodeTag(labelexpr));
887 : : }
888 : :
889 : 1361 : return label_oids;
890 : : }
891 : :
892 : : /*
893 : : * Return a list of all the graph elements that satisfy the graph element pattern
894 : : * represented by the given path_factor `pf`.
895 : : *
896 : : * First we find all the graph labels that satisfy the label expression in path
897 : : * factor. Each label is associated with one or more graph elements. A union of
898 : : * all such elements satisfies the element pattern. We create one path_element
899 : : * object representing every element whose graph element kind qualifies the
900 : : * element pattern kind. A list of all such path_element objects is returned.
901 : : *
902 : : * Note that we need to report an error for an explicitly specified label which
903 : : * is not associated with any graph element of the required kind. So we have to
904 : : * treat each label separately. Without that requirement we could have collected
905 : : * all the unique elements first and then created path_element objects for them
906 : : * to simplify the code.
907 : : */
908 : : static List *
909 : 1361 : get_path_elements_for_path_factor(Oid propgraphid, struct path_factor *pf)
910 : : {
911 : 1361 : List *label_oids = get_labels_for_expr(propgraphid, pf->labelexpr);
912 : 1361 : List *elem_oids_seen = NIL;
913 : 1361 : List *pf_elem_oids = NIL;
914 : 1361 : List *path_elements = NIL;
915 : 1361 : List *unresolved_labels = NIL;
916 : : Relation rel;
917 : : SysScanDesc scan;
918 : : ScanKeyData key[1];
919 : : HeapTuple tup;
920 : :
921 : : /*
922 : : * A property graph element can be either a vertex or an edge. Other types
923 : : * of path factors like nested path pattern need to be handled separately
924 : : * when supported.
925 : : */
926 : : Assert(pf->kind == VERTEX_PATTERN || IS_EDGE_PATTERN(pf->kind));
927 : :
928 : 1361 : rel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
929 [ + - + + : 6025 : foreach_oid(labeloid, label_oids)
+ + ]
930 : : {
931 : 3319 : bool found = false;
932 : :
933 : 3319 : ScanKeyInit(&key[0],
934 : : Anum_pg_propgraph_element_label_pgellabelid,
935 : : BTEqualStrategyNumber,
936 : : F_OIDEQ, ObjectIdGetDatum(labeloid));
937 : 3319 : scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId, true,
938 : : NULL, 1, key);
939 [ + + ]: 11047 : while (HeapTupleIsValid(tup = systable_getnext(scan)))
940 : : {
941 : 7728 : Form_pg_propgraph_element_label label_elem = (Form_pg_propgraph_element_label) GETSTRUCT(tup);
942 : 7728 : Oid elem_oid = label_elem->pgelelid;
943 : :
944 [ + + ]: 7728 : if (!list_member_oid(elem_oids_seen, elem_oid))
945 : : {
946 : : /*
947 : : * Create path_element object if the new element qualifies the
948 : : * element pattern kind.
949 : : */
950 : 4650 : struct path_element *pe = create_pe_for_element(pf, elem_oid);
951 : :
952 [ + + ]: 4650 : if (pe)
953 : : {
954 : 2726 : path_elements = lappend(path_elements, pe);
955 : :
956 : : /* Remember qualified elements. */
957 : 2726 : pf_elem_oids = lappend_oid(pf_elem_oids, elem_oid);
958 : 2726 : found = true;
959 : : }
960 : :
961 : : /*
962 : : * Remember qualified and unqualified elements processed so
963 : : * far to avoid processing already processed elements again.
964 : : */
965 : 4650 : elem_oids_seen = lappend_oid(elem_oids_seen, label_elem->pgelelid);
966 : : }
967 [ + + ]: 3078 : else if (list_member_oid(pf_elem_oids, elem_oid))
968 : : {
969 : : /*
970 : : * The graph element is known to qualify the given element
971 : : * pattern. Flag that the current label has at least one
972 : : * qualified element associated with it.
973 : : */
974 : 1582 : found = true;
975 : : }
976 : : }
977 : :
978 [ + + ]: 3319 : if (!found)
979 : : {
980 : : /*
981 : : * We did not find any qualified element associated with this
982 : : * label. The label or its properties can not be associated with
983 : : * the given element pattern. Throw an error if the label was
984 : : * explicitly specified in the element pattern. Otherwise remember
985 : : * it for later use.
986 : : */
987 [ + + ]: 984 : if (!pf->labelexpr)
988 : 976 : unresolved_labels = lappend_oid(unresolved_labels, labeloid);
989 : : else
990 [ + - + - ]: 8 : ereport(ERROR,
991 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
992 : : errmsg("no property graph element of type \"%s\" has label \"%s\" associated with it in property graph \"%s\"",
993 : : pf->kind == VERTEX_PATTERN ? "vertex" : "edge",
994 : : get_propgraph_label_name(labeloid),
995 : : get_rel_name(propgraphid))));
996 : : }
997 : :
998 : 3311 : systable_endscan(scan);
999 : : }
1000 : 1353 : table_close(rel, AccessShareLock);
1001 : :
1002 : : /*
1003 : : * Remove the labels which were not explicitly mentioned in the label
1004 : : * expression but do not have any qualified elements associated with them.
1005 : : * Properties associated with such labels may not be referenced. See
1006 : : * replace_property_refs_mutator() for more details.
1007 : : */
1008 : 1353 : pf->labeloids = list_difference_oid(label_oids, unresolved_labels);
1009 : :
1010 : 1353 : return path_elements;
1011 : : }
1012 : :
1013 : : /*
1014 : : * Mutating property references into table variables
1015 : : */
1016 : :
1017 : : struct replace_property_refs_context
1018 : : {
1019 : : Oid propgraphid;
1020 : : const List *mappings;
1021 : : };
1022 : :
1023 : : static Node *
1024 : 50452 : replace_property_refs_mutator(Node *node, struct replace_property_refs_context *context)
1025 : : {
1026 [ - + ]: 50452 : if (node == NULL)
1027 : 0 : return NULL;
1028 [ + + ]: 50452 : if (IsA(node, Var))
1029 : : {
1030 : 56 : Var *var = (Var *) node;
1031 : 56 : Var *newvar = copyObject(var);
1032 : :
1033 : : /*
1034 : : * If it's already a Var, then it was a lateral reference. Since we
1035 : : * are in a subquery after the rewrite, we have to increase the level
1036 : : * by one.
1037 : : */
1038 : 56 : newvar->varlevelsup++;
1039 : :
1040 : 56 : return (Node *) newvar;
1041 : : }
1042 [ + + ]: 50396 : else if (IsA(node, GraphPropertyRef))
1043 : : {
1044 : 10994 : GraphPropertyRef *gpr = (GraphPropertyRef *) node;
1045 : 10994 : Node *n = NULL;
1046 : 10994 : struct path_element *found_mapping = NULL;
1047 : 10994 : struct path_factor *mapping_factor = NULL;
1048 : 10994 : List *unrelated_labels = NIL;
1049 : :
1050 [ + - + - : 25300 : foreach_ptr(struct path_element, m, context->mappings)
+ + ]
1051 : : {
1052 [ + + + + ]: 14306 : if (m->path_factor->variable && strcmp(gpr->elvarname, m->path_factor->variable) == 0)
1053 : : {
1054 : 10994 : found_mapping = m;
1055 : 10994 : break;
1056 : : }
1057 : : }
1058 : :
1059 : : /*
1060 : : * transformGraphTablePropertyRef() would not create a
1061 : : * GraphPropertyRef for a variable which is not present in the graph
1062 : : * path pattern.
1063 : : */
1064 : : Assert(found_mapping);
1065 : :
1066 : 10994 : mapping_factor = found_mapping->path_factor;
1067 : :
1068 : : /*
1069 : : * Find property definition for given element through any of the
1070 : : * associated labels qualifying the given element pattern.
1071 : : */
1072 [ + - + + : 60022 : foreach_oid(labeloid, mapping_factor->labeloids)
+ + ]
1073 : : {
1074 : 38034 : Oid elem_labelid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL,
1075 : : Anum_pg_propgraph_element_label_oid,
1076 : : ObjectIdGetDatum(found_mapping->elemoid),
1077 : : ObjectIdGetDatum(labeloid));
1078 : :
1079 [ + + ]: 38034 : if (OidIsValid(elem_labelid))
1080 : : {
1081 : 23054 : HeapTuple tup = SearchSysCache2(PROPGRAPHLABELPROP, ObjectIdGetDatum(elem_labelid),
1082 : : ObjectIdGetDatum(gpr->propid));
1083 : :
1084 [ + + ]: 23054 : if (!tup)
1085 : : {
1086 : : /*
1087 : : * The label is associated with the given element but it
1088 : : * is not associated with the required property. Check
1089 : : * next label.
1090 : : */
1091 : 11720 : continue;
1092 : : }
1093 : :
1094 : 11334 : n = stringToNode(TextDatumGetCString(SysCacheGetAttrNotNull(PROPGRAPHLABELPROP,
1095 : : tup, Anum_pg_propgraph_label_property_plpexpr)));
1096 : 11334 : ChangeVarNodes(n, 1, mapping_factor->factorpos + 1, 0);
1097 : :
1098 : 11334 : ReleaseSysCache(tup);
1099 : : }
1100 : : else
1101 : : {
1102 : : /*
1103 : : * Label is not associated with the element but it may be
1104 : : * associated with the property through some other element.
1105 : : * Save it for later use.
1106 : : */
1107 : 14980 : unrelated_labels = lappend_oid(unrelated_labels, labeloid);
1108 : : }
1109 : : }
1110 : :
1111 : : /* See if we can resolve the property in some other way. */
1112 [ + + ]: 10994 : if (!n)
1113 : : {
1114 : 72 : bool prop_associated = false;
1115 : :
1116 [ + + + + : 168 : foreach_oid(loid, unrelated_labels)
+ + ]
1117 : : {
1118 [ + + ]: 76 : if (is_property_associated_with_label(loid, gpr->propid))
1119 : : {
1120 : 52 : prop_associated = true;
1121 : 52 : break;
1122 : : }
1123 : : }
1124 : :
1125 [ + + ]: 72 : if (prop_associated)
1126 : : {
1127 : : /*
1128 : : * The property is associated with at least one of the labels
1129 : : * that satisfy given element pattern. If it's associated with
1130 : : * the given element (through some other label), use
1131 : : * corresponding value expression. Otherwise NULL. Ref.
1132 : : * SQL/PGQ standard section 6.5 Property Reference, General
1133 : : * Rule 2.b.
1134 : : */
1135 : 52 : n = get_element_property_expr(found_mapping->elemoid, gpr->propid,
1136 : 52 : mapping_factor->factorpos + 1);
1137 : :
1138 [ + + ]: 52 : if (!n)
1139 : 48 : n = (Node *) makeNullConst(gpr->typeId, gpr->typmod, gpr->collation);
1140 : : }
1141 : :
1142 : : }
1143 : :
1144 [ + + ]: 10994 : if (!n)
1145 [ + - ]: 20 : ereport(ERROR,
1146 : : errcode(ERRCODE_UNDEFINED_OBJECT),
1147 : : errmsg("property \"%s\" for element variable \"%s\" not found",
1148 : : get_propgraph_property_name(gpr->propid), mapping_factor->variable));
1149 : :
1150 : 10974 : return n;
1151 : : }
1152 : :
1153 : 39402 : return expression_tree_mutator(node, replace_property_refs_mutator, context);
1154 : : }
1155 : :
1156 : : static Node *
1157 : 4978 : replace_property_refs(Oid propgraphid, Node *node, const List *mappings)
1158 : : {
1159 : : struct replace_property_refs_context context;
1160 : :
1161 : 4978 : context.mappings = mappings;
1162 : 4978 : context.propgraphid = propgraphid;
1163 : :
1164 : 4978 : return replace_property_refs_mutator(node, &context);
1165 : : }
1166 : :
1167 : : /*
1168 : : * Build join qualification expressions between edge and vertex tables.
1169 : : */
1170 : : static List *
1171 : 2504 : 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)
1172 : : {
1173 : 2504 : List *quals = NIL;
1174 : : Form_pg_propgraph_element pgeform;
1175 : : Datum datum;
1176 : : Datum *d1,
1177 : : *d2,
1178 : : *d3;
1179 : : int n1,
1180 : : n2,
1181 : : n3;
1182 : 2504 : ParseState *pstate = make_parsestate(NULL);
1183 : 2504 : Oid refrelid = GetSysCacheOid1(PROPGRAPHELOID, Anum_pg_propgraph_element_pgerelid, ObjectIdGetDatum(refid));
1184 : :
1185 : 2504 : pgeform = (Form_pg_propgraph_element) GETSTRUCT(edgetup);
1186 : :
1187 : 2504 : datum = SysCacheGetAttrNotNull(PROPGRAPHELOID, edgetup, catalog_key_attnum);
1188 : 2504 : deconstruct_array_builtin(DatumGetArrayTypeP(datum), INT2OID, &d1, NULL, &n1);
1189 : :
1190 : 2504 : datum = SysCacheGetAttrNotNull(PROPGRAPHELOID, edgetup, catalog_ref_attnum);
1191 : 2504 : deconstruct_array_builtin(DatumGetArrayTypeP(datum), INT2OID, &d2, NULL, &n2);
1192 : :
1193 : 2504 : datum = SysCacheGetAttrNotNull(PROPGRAPHELOID, edgetup, catalog_eqop_attnum);
1194 : 2504 : deconstruct_array_builtin(DatumGetArrayTypeP(datum), OIDOID, &d3, NULL, &n3);
1195 : :
1196 [ - + ]: 2504 : if (n1 != n2)
1197 [ # # ]: 0 : elog(ERROR, "array size key (%d) vs ref (%d) mismatch for element ID %u", catalog_key_attnum, catalog_ref_attnum, pgeform->oid);
1198 [ - + ]: 2504 : if (n1 != n3)
1199 [ # # ]: 0 : elog(ERROR, "array size key (%d) vs operator (%d) mismatch for element ID %u", catalog_key_attnum, catalog_eqop_attnum, pgeform->oid);
1200 : :
1201 [ + + ]: 5672 : for (int i = 0; i < n1; i++)
1202 : : {
1203 : 3168 : AttrNumber keyattn = DatumGetInt16(d1[i]);
1204 : 3168 : AttrNumber refattn = DatumGetInt16(d2[i]);
1205 : 3168 : Oid eqop = DatumGetObjectId(d3[i]);
1206 : : Var *keyvar;
1207 : : Var *refvar;
1208 : : Oid atttypid;
1209 : : int32 atttypmod;
1210 : : Oid attcoll;
1211 : : HeapTuple tup;
1212 : : Form_pg_operator opform;
1213 : : List *args;
1214 : : Oid actual_arg_types[2];
1215 : : Oid declared_arg_types[2];
1216 : : OpExpr *linkqual;
1217 : :
1218 : 3168 : get_atttypetypmodcoll(pgeform->pgerelid, keyattn, &atttypid, &atttypmod, &attcoll);
1219 : 3168 : keyvar = makeVar(edgerti, keyattn, atttypid, atttypmod, attcoll, 0);
1220 : 3168 : get_atttypetypmodcoll(refrelid, refattn, &atttypid, &atttypmod, &attcoll);
1221 : 3168 : refvar = makeVar(refrti, refattn, atttypid, atttypmod, attcoll, 0);
1222 : :
1223 : 3168 : tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(eqop));
1224 [ - + ]: 3168 : if (!HeapTupleIsValid(tup))
1225 [ # # ]: 0 : elog(ERROR, "cache lookup failed for operator %u", eqop);
1226 : 3168 : opform = (Form_pg_operator) GETSTRUCT(tup);
1227 : : /* An equality operator is a binary operator returning boolean result. */
1228 : : Assert(opform->oprkind == 'b'
1229 : : && RegProcedureIsValid(opform->oprcode)
1230 : : && opform->oprresult == BOOLOID
1231 : : && !get_func_retset(opform->oprcode));
1232 : :
1233 : : /*
1234 : : * Prepare operands and cast them to the types required by the
1235 : : * equality operator. Similar to PK/FK quals, referenced vertex key is
1236 : : * used as left operand and referencing edge key is used as right
1237 : : * operand.
1238 : : */
1239 : 3168 : args = list_make2(refvar, keyvar);
1240 : 3168 : actual_arg_types[0] = exprType((Node *) refvar);
1241 : 3168 : actual_arg_types[1] = exprType((Node *) keyvar);
1242 : 3168 : declared_arg_types[0] = opform->oprleft;
1243 : 3168 : declared_arg_types[1] = opform->oprright;
1244 : 3168 : make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
1245 : :
1246 : 3168 : linkqual = makeNode(OpExpr);
1247 : 3168 : linkqual->opno = opform->oid;
1248 : 3168 : linkqual->opfuncid = opform->oprcode;
1249 : 3168 : linkqual->opresulttype = opform->oprresult;
1250 : 3168 : linkqual->opretset = false;
1251 : : /* opcollid and inputcollid will be set by parse_collate.c */
1252 : 3168 : linkqual->args = args;
1253 : 3168 : linkqual->location = -1;
1254 : :
1255 : 3168 : ReleaseSysCache(tup);
1256 : 3168 : quals = lappend(quals, linkqual);
1257 : : }
1258 : :
1259 : 2504 : assign_expr_collations(pstate, (Node *) quals);
1260 : :
1261 : 2504 : return quals;
1262 : : }
1263 : :
1264 : : /*
1265 : : * Check if the given property is associated with the given label.
1266 : : *
1267 : : * A label projects the same set of properties through every element it is
1268 : : * associated with. Find any of the elements and return true if that element is
1269 : : * associated with the given property. False otherwise.
1270 : : */
1271 : : static bool
1272 : 76 : is_property_associated_with_label(Oid labeloid, Oid propoid)
1273 : : {
1274 : : Relation rel;
1275 : : SysScanDesc scan;
1276 : : ScanKeyData key[1];
1277 : : HeapTuple tup;
1278 : 76 : bool associated = false;
1279 : :
1280 : 76 : rel = table_open(PropgraphElementLabelRelationId, RowShareLock);
1281 : 76 : ScanKeyInit(&key[0],
1282 : : Anum_pg_propgraph_element_label_pgellabelid,
1283 : : BTEqualStrategyNumber,
1284 : : F_OIDEQ, ObjectIdGetDatum(labeloid));
1285 : 76 : scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId,
1286 : : true, NULL, 1, key);
1287 : :
1288 [ + - ]: 76 : if (HeapTupleIsValid(tup = systable_getnext(scan)))
1289 : : {
1290 : 76 : Form_pg_propgraph_element_label ele_label = (Form_pg_propgraph_element_label) GETSTRUCT(tup);
1291 : :
1292 : 76 : associated = SearchSysCacheExists2(PROPGRAPHLABELPROP,
1293 : : ObjectIdGetDatum(ele_label->oid), ObjectIdGetDatum(propoid));
1294 : : }
1295 : 76 : systable_endscan(scan);
1296 : 76 : table_close(rel, RowShareLock);
1297 : :
1298 : 76 : return associated;
1299 : : }
1300 : :
1301 : : /*
1302 : : * If given element has the given property associated with it, through any of
1303 : : * the associated labels, return value expression of the property. Otherwise
1304 : : * NULL.
1305 : : */
1306 : : static Node *
1307 : 52 : get_element_property_expr(Oid elemoid, Oid propoid, int rtindex)
1308 : : {
1309 : : Relation rel;
1310 : : SysScanDesc scan;
1311 : : ScanKeyData key[1];
1312 : : HeapTuple labeltup;
1313 : 52 : Node *n = NULL;
1314 : :
1315 : 52 : rel = table_open(PropgraphElementLabelRelationId, RowShareLock);
1316 : 52 : ScanKeyInit(&key[0],
1317 : : Anum_pg_propgraph_element_label_pgelelid,
1318 : : BTEqualStrategyNumber,
1319 : : F_OIDEQ, ObjectIdGetDatum(elemoid));
1320 : 52 : scan = systable_beginscan(rel, PropgraphElementLabelElementLabelIndexId,
1321 : : true, NULL, 1, key);
1322 : :
1323 [ + + ]: 148 : while (HeapTupleIsValid(labeltup = systable_getnext(scan)))
1324 : : {
1325 : 100 : Form_pg_propgraph_element_label ele_label = (Form_pg_propgraph_element_label) GETSTRUCT(labeltup);
1326 : :
1327 : 100 : HeapTuple proptup = SearchSysCache2(PROPGRAPHLABELPROP,
1328 : : ObjectIdGetDatum(ele_label->oid), ObjectIdGetDatum(propoid));
1329 : :
1330 [ + + ]: 100 : if (!proptup)
1331 : 96 : continue;
1332 : 4 : n = stringToNode(TextDatumGetCString(SysCacheGetAttrNotNull(PROPGRAPHLABELPROP,
1333 : : proptup, Anum_pg_propgraph_label_property_plpexpr)));
1334 : 4 : ChangeVarNodes(n, 1, rtindex, 0);
1335 : :
1336 : 4 : ReleaseSysCache(proptup);
1337 : 4 : break;
1338 : : }
1339 : 52 : systable_endscan(scan);
1340 : 52 : table_close(rel, RowShareLock);
1341 : :
1342 : 52 : return n;
1343 : : }
|