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 : }
|