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