Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * parse_graphtable.c
4 : * parsing of GRAPH_TABLE
5 : *
6 : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : * Portions Copyright (c) 1994, Regents of the University of California
8 : *
9 : *
10 : * IDENTIFICATION
11 : * src/backend/parser/parse_graphtable.c
12 : *
13 : *-------------------------------------------------------------------------
14 : */
15 :
16 : #include "postgres.h"
17 :
18 : #include "access/genam.h"
19 : #include "access/htup_details.h"
20 : #include "access/table.h"
21 : #include "catalog/pg_propgraph_label.h"
22 : #include "catalog/pg_propgraph_property.h"
23 : #include "miscadmin.h"
24 : #include "nodes/makefuncs.h"
25 : #include "parser/parse_collate.h"
26 : #include "parser/parse_expr.h"
27 : #include "parser/parse_graphtable.h"
28 : #include "parser/parse_node.h"
29 : #include "utils/fmgroids.h"
30 : #include "utils/lsyscache.h"
31 : #include "utils/relcache.h"
32 : #include "utils/syscache.h"
33 :
34 :
35 : /*
36 : * Return human-readable name of the type of graph element pattern in
37 : * GRAPH_TABLE clause, usually for error message purpose.
38 : */
39 : static const char *
40 4 : get_gep_kind_name(GraphElementPatternKind gepkind)
41 : {
42 4 : switch (gepkind)
43 : {
44 0 : case VERTEX_PATTERN:
45 0 : return "vertex";
46 0 : case EDGE_PATTERN_LEFT:
47 0 : return "edge pointing left";
48 0 : case EDGE_PATTERN_RIGHT:
49 0 : return "edge pointing right";
50 0 : case EDGE_PATTERN_ANY:
51 0 : return "edge pointing any direction";
52 4 : case PAREN_EXPR:
53 4 : return "nested path pattern";
54 : }
55 :
56 : /*
57 : * When a GraphElementPattern is constructed by the parser, it will set a
58 : * value from the GraphElementPatternKind enum. But we may get here if the
59 : * GraphElementPatternKind value stored in a catalog is corrupted.
60 : */
61 0 : return "unknown";
62 : }
63 :
64 : /*
65 : * Transform a property reference.
66 : *
67 : * A property reference is parsed as a ColumnRef of the form:
68 : * <variable>.<property>. If <variable> is one of the variables bound to an
69 : * element pattern in the graph pattern and <property> can be resolved as a
70 : * property of the property graph, then we return a GraphPropertyRef node
71 : * representing the property reference. If the <variable> exists in the graph
72 : * pattern but <property> does not exist in the property graph, we raise an
73 : * error. However, if <variable> does not exist in the graph pattern, we return
74 : * NULL to let the caller handle it as some other kind of ColumnRef. The
75 : * variables bound to the element patterns in the graph pattern are expected to
76 : * be collected in the GraphTableParseState.
77 : */
78 : Node *
79 1181530 : transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref)
80 : {
81 1181530 : GraphTableParseState *gpstate = pstate->p_graph_table_pstate;
82 :
83 1181530 : if (!gpstate)
84 1179842 : return NULL;
85 :
86 1688 : if (list_length(cref->fields) == 2)
87 : {
88 1675 : Node *field1 = linitial(cref->fields);
89 1675 : Node *field2 = lsecond(cref->fields);
90 : char *elvarname;
91 : char *propname;
92 :
93 1675 : if (IsA(field1, A_Star) || IsA(field2, A_Star))
94 : {
95 8 : if (pstate->p_expr_kind == EXPR_KIND_SELECT_TARGET)
96 4 : ereport(ERROR,
97 : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
98 : errmsg("\"*\" is not supported here"),
99 : parser_errposition(pstate, cref->location));
100 : else
101 4 : ereport(ERROR,
102 : errcode(ERRCODE_SYNTAX_ERROR),
103 : errmsg("\"*\" not allowed here"),
104 : parser_errposition(pstate, cref->location));
105 : }
106 :
107 1667 : elvarname = strVal(field1);
108 1667 : propname = strVal(field2);
109 :
110 1667 : if (list_member(gpstate->variables, field1))
111 : {
112 : GraphPropertyRef *gpr;
113 : HeapTuple pgptup;
114 : Form_pg_propgraph_property pgpform;
115 :
116 : /*
117 : * If we are transforming expression in an element pattern,
118 : * property references containing only that variable are allowed.
119 : */
120 1655 : if (gpstate->cur_gep)
121 : {
122 159 : if (!gpstate->cur_gep->variable ||
123 151 : strcmp(elvarname, gpstate->cur_gep->variable) != 0)
124 16 : ereport(ERROR,
125 : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
126 : errmsg("non-local element variable reference is not supported"),
127 : parser_errposition(pstate, cref->location));
128 : }
129 :
130 1639 : gpr = makeNode(GraphPropertyRef);
131 1639 : pgptup = SearchSysCache2(PROPGRAPHPROPNAME, ObjectIdGetDatum(gpstate->graphid), CStringGetDatum(propname));
132 1639 : if (!HeapTupleIsValid(pgptup))
133 4 : ereport(ERROR,
134 : errcode(ERRCODE_SYNTAX_ERROR),
135 : errmsg("property \"%s\" does not exist", propname));
136 1635 : pgpform = (Form_pg_propgraph_property) GETSTRUCT(pgptup);
137 :
138 1635 : gpr->location = cref->location;
139 1635 : gpr->elvarname = elvarname;
140 1635 : gpr->propid = pgpform->oid;
141 1635 : gpr->typeId = pgpform->pgptypid;
142 1635 : gpr->typmod = pgpform->pgptypmod;
143 1635 : gpr->collation = pgpform->pgpcollation;
144 :
145 1635 : ReleaseSysCache(pgptup);
146 :
147 1635 : return (Node *) gpr;
148 : }
149 : }
150 :
151 25 : return NULL;
152 : }
153 :
154 : /*
155 : * Transform a label expression.
156 : *
157 : * A label expression is parsed as either a ColumnRef with a single field or a
158 : * label expression like label disjunction. The single field in the ColumnRef is
159 : * treated as a label name and transformed to a GraphLabelRef node. The label
160 : * expression is recursively transformed into an expression tree containg
161 : * GraphLabelRef nodes corresponding to the names of the labels appearing in the
162 : * expression. If any label name cannot be resolved to a label in the property
163 : * graph, an error is raised.
164 : */
165 : static Node *
166 1773 : transformLabelExpr(GraphTableParseState *gpstate, Node *labelexpr)
167 : {
168 : Node *result;
169 :
170 1773 : if (labelexpr == NULL)
171 665 : return NULL;
172 :
173 1108 : check_stack_depth();
174 :
175 1108 : switch (nodeTag(labelexpr))
176 : {
177 1056 : case T_ColumnRef:
178 : {
179 1056 : ColumnRef *cref = (ColumnRef *) labelexpr;
180 : const char *labelname;
181 : Oid labelid;
182 : GraphLabelRef *lref;
183 :
184 : Assert(list_length(cref->fields) == 1);
185 1056 : labelname = strVal(linitial(cref->fields));
186 :
187 1056 : labelid = GetSysCacheOid2(PROPGRAPHLABELNAME, Anum_pg_propgraph_label_oid, ObjectIdGetDatum(gpstate->graphid), CStringGetDatum(labelname));
188 1056 : if (!labelid)
189 4 : ereport(ERROR,
190 : errcode(ERRCODE_UNDEFINED_OBJECT),
191 : errmsg("label \"%s\" does not exist in property graph \"%s\"", labelname, get_rel_name(gpstate->graphid)));
192 :
193 1052 : lref = makeNode(GraphLabelRef);
194 1052 : lref->labelid = labelid;
195 1052 : lref->location = cref->location;
196 :
197 1052 : result = (Node *) lref;
198 1052 : break;
199 : }
200 :
201 52 : case T_BoolExpr:
202 : {
203 52 : BoolExpr *be = (BoolExpr *) labelexpr;
204 : ListCell *lc;
205 52 : List *args = NIL;
206 :
207 156 : foreach(lc, be->args)
208 : {
209 108 : Node *arg = (Node *) lfirst(lc);
210 :
211 108 : arg = transformLabelExpr(gpstate, arg);
212 104 : args = lappend(args, arg);
213 : }
214 :
215 48 : result = (Node *) makeBoolExpr(be->boolop, args, be->location);
216 48 : break;
217 : }
218 :
219 0 : default:
220 : /* should not reach here */
221 0 : elog(ERROR, "unsupported label expression node: %d", (int) nodeTag(labelexpr));
222 : result = NULL; /* keep compiler quiet */
223 : break;
224 : }
225 :
226 1100 : return result;
227 : }
228 :
229 : /*
230 : * Transform a GraphElementPattern.
231 : *
232 : * Transform the label expression and the where clause in the element pattern
233 : * given by GraphElementPattern. The variable name in the GraphElementPattern is
234 : * added to the list of variables in the GraphTableParseState which is used to
235 : * resolve property references in this element pattern or elsewhere in the
236 : * GRAPH_TABLE.
237 : */
238 : static Node *
239 1669 : transformGraphElementPattern(ParseState *pstate, GraphElementPattern *gep)
240 : {
241 1669 : GraphTableParseState *gpstate = pstate->p_graph_table_pstate;
242 :
243 1669 : if (gep->quantifier)
244 4 : ereport(ERROR,
245 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
246 : errmsg("element pattern quantifier is not supported")));
247 :
248 : Assert(!gpstate->cur_gep);
249 :
250 1665 : gpstate->cur_gep = gep;
251 :
252 1665 : gep->labelexpr = transformLabelExpr(gpstate, gep->labelexpr);
253 :
254 1661 : gep->whereClause = transformExpr(pstate, gep->whereClause, EXPR_KIND_WHERE);
255 1641 : assign_expr_collations(pstate, gep->whereClause);
256 :
257 1641 : gpstate->cur_gep = NULL;
258 :
259 1641 : return (Node *) gep;
260 : }
261 :
262 : /*
263 : * Transform a path term (list of GraphElementPattern's).
264 : */
265 : static Node *
266 561 : transformPathTerm(ParseState *pstate, List *path_term)
267 : {
268 561 : List *result = NIL;
269 561 : GraphElementPattern *prev_gep = NULL;
270 :
271 2719 : foreach_node(GraphElementPattern, gep, path_term)
272 : {
273 1685 : if (gep->kind != VERTEX_PATTERN && !IS_EDGE_PATTERN(gep->kind))
274 4 : ereport(ERROR,
275 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
276 : errmsg("unsupported element pattern kind: \"%s\"", get_gep_kind_name(gep->kind)),
277 : parser_errposition(pstate, gep->location)));
278 :
279 1681 : if (IS_EDGE_PATTERN(gep->kind))
280 : {
281 574 : if (!prev_gep)
282 4 : ereport(ERROR,
283 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
284 : errmsg("path pattern cannot start with an edge pattern"),
285 : parser_errposition(pstate, gep->location)));
286 570 : else if (prev_gep->kind != VERTEX_PATTERN)
287 4 : ereport(ERROR,
288 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
289 : errmsg("edge pattern must be preceded by a vertex pattern"),
290 : parser_errposition(pstate, gep->location)));
291 : }
292 : else
293 : {
294 1107 : if (prev_gep && !IS_EDGE_PATTERN(prev_gep->kind))
295 4 : ereport(ERROR,
296 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
297 : errmsg("adjacent vertex patterns are not supported"),
298 : parser_errposition(pstate, gep->location)));
299 : }
300 :
301 1641 : result = lappend(result,
302 1669 : transformGraphElementPattern(pstate, gep));
303 1641 : prev_gep = gep;
304 : }
305 :
306 : /* Path pattern should have at least one element pattern. */
307 : Assert(prev_gep);
308 :
309 517 : if (IS_EDGE_PATTERN(prev_gep->kind))
310 : {
311 4 : ereport(ERROR,
312 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
313 : errmsg("path pattern cannot end with an edge pattern"),
314 : parser_errposition(pstate, prev_gep->location)));
315 : }
316 :
317 513 : return (Node *) result;
318 : }
319 :
320 : /*
321 : * Transform a path pattern list (list of path terms).
322 : */
323 : static Node *
324 565 : transformPathPatternList(ParseState *pstate, List *path_pattern)
325 : {
326 565 : List *result = NIL;
327 565 : GraphTableParseState *gpstate = pstate->p_graph_table_pstate;
328 :
329 : Assert(gpstate);
330 :
331 : /* Grammar doesn't allow empty path pattern list */
332 : Assert(list_length(path_pattern) > 0);
333 :
334 : /*
335 : * We do not support multiple path patterns in one GRAPH_TABLE clause
336 : * right now. But we may do so in future.
337 : */
338 565 : if (list_length(path_pattern) != 1)
339 4 : ereport(ERROR,
340 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
341 : errmsg("multiple path patterns in one GRAPH_TABLE clause not supported")));
342 :
343 : /*
344 : * Collect all the variables in the path pattern into the
345 : * GraphTableParseState so that we can detect any non-local element
346 : * variable references. We need to do this before transforming the path
347 : * pattern so as to detect forward references to element variables in the
348 : * WHERE clause of an element pattern.
349 : */
350 1683 : foreach_node(List, path_term, path_pattern)
351 : {
352 2851 : foreach_node(GraphElementPattern, gep, path_term)
353 : {
354 1729 : if (gep->variable)
355 1370 : gpstate->variables = list_append_unique(gpstate->variables, makeString(pstrdup(gep->variable)));
356 : }
357 : }
358 :
359 1587 : foreach_node(List, path_term, path_pattern)
360 561 : result = lappend(result, transformPathTerm(pstate, path_term));
361 :
362 513 : return (Node *) result;
363 : }
364 :
365 : /*
366 : * Transform a GraphPattern.
367 : *
368 : * A GraphPattern consists of a list of one or more path patterns and an
369 : * optional where clause. Transform them. We use the previously constructure
370 : * list of variables in the GraphTableParseState to resolve property references
371 : * in the WHERE clause.
372 : */
373 : Node *
374 565 : transformGraphPattern(ParseState *pstate, GraphPattern *graph_pattern)
375 : {
376 565 : List *path_pattern_list = castNode(List,
377 : transformPathPatternList(pstate, graph_pattern->path_pattern_list));
378 :
379 513 : graph_pattern->path_pattern_list = path_pattern_list;
380 513 : graph_pattern->whereClause = transformExpr(pstate, graph_pattern->whereClause, EXPR_KIND_WHERE);
381 513 : assign_expr_collations(pstate, graph_pattern->whereClause);
382 :
383 513 : return (Node *) graph_pattern;
384 : }
|