Hello Ashutosh,
Thanks for the review.
On 30/07/2026 09:20, Ashutosh Bapat wrote:
Hi Ayoub,
Thanks for your interest in SQL/PGQ.
On Wed, Jul 22, 2026 at 3:02 PM <[email protected]> wrote:
Hello everyone,
I'm proposing a patch that adds support for label conjunction in
SQL/PGQ, i'll quote from the commit message since its already a
semi-complete description:
The SQL/PGQ standard allows label expressions to use boolean
operators,
such as conjunction of labels: MATCH (a IS label1 & label2).
Previously,
only
disjunction (|) was supported in graph element patterns.
As your patch does, the label conjunction requires changes to the
grammar. We need to be careful there. But we can support conjunction
without requiring a change to the grammar - by allowing multiple
element patterns with non-empty label expression to share the same
variable name. I would start with that. You have mentioned that your
patch supports it. So, let's create two patches - one without grammar
and other with grammar. The first one by itself has higher chances of
getting committed. Oracle also does not seem to support explicit
label disjunction.
I would also question whether label conjunction is a feature required
by the field just so that we prioritize the features by field demand.
Supporting label conjunction, disjunction and in the future ; label
negation, requires an evaluation of a full label expression.
Previously
get_path_elements_for_path_factor() considered only flat BoolExpr
evaluation (as it only knew about disjunctions).
As required, this commit adds a recursive label expression evaluator,
get_path_elements_for_path_factor() now fetches all candidate table
elements for the path factor kind once and evaluates each table
element's label OIDs against the label expression tree.
I'm not sure if the current approach of getting all elements and then
iteratively pulling their corresponding labels from catalog ; which
might seem costly (if doing many `table_open`s every time is a lot
and
pg_propgraph_element is large enough), we can change it to keep
`pg_propgraph_element_label` open until all element OIDs are treated.
I think the way label are resolved into elements works only for
disjunction, conjunction and negation require some refactoring. I
would suggest taking patches from [1], rebasing all properties
supporting patch from [2] on top of those patches. Implement implicit
label conjunction on top of these patches. I am hoping that patches
from [1] would get committed to PG 19 - hence those are separate
patches. For PG 20, we may decide to distribute the code
slightly differently across the patches.
[1]
https://www.postgresql.org/message-id/CAExHW5uNRS9tcgnHCc03rh5oQhFPz-wrC1YF%2B_XUhPdwK2fbXw%40mail.gmail.com
[2]
https://www.postgresql.org/message-id/caexhw5tyce9qycvvrakueeskw5rtr+mrzsg3u64qsps-rpj...@mail.gmail.com
--
Best Wishes,
Ashutosh Bapat
I managed to rebase your previous patches that you referenced, then on
top of them i added the implicit label conjunction, the explicit case
can be left for later since its requires just grammar change (having
both at the same time is very good for expressiveness).
I noticed a small issue in your
v20260318-0001-Support-all-properties-reference-in-COLUMN.patch
- BoolExpr *be = castNode(BoolExpr, labelexpr);
+ BoolExpr *be = castNode(BoolExpr, pf->labelexpr);
this would break the recursion since its always getting the top level
expression i guess ?
Attached are all 3 rebased patches on top of each other as you suggested
+ the label conjunction patch on top of them.
Regards,
Ayoub Kazar
From 6ca0e68c3973aabd1b381fe6d5332ea818314b0a Mon Sep 17 00:00:00 2001
From: AyoubKAZ <[email protected]>
Date: Wed, 5 Aug 2026 16:04:04 +0200
Subject: [PATCH v1 4/4] Add support for label conjunction (&) in SQL/PGQ
The SQL/PGQ standard allows label expressions to use boolean operators,
such as conjunction of labels: MATCH (a IS label1 & label2). Previously, only
disjunction (|) was supported in graph element patterns.
This commit adds support for implicit label conjunction: a path factor appearing with different label expressions in multiple graph element patterns, which needs to be conjucted as if it was written with an explicit "&" which is not yet supported in grammar.
Author: Ayoub Kazar <[email protected]>
---
src/backend/rewrite/rewriteGraphTable.c | 96 ++++++++++++++++-------
src/test/regress/expected/graph_table.out | 16 +++-
src/test/regress/sql/graph_table.sql | 4 +-
3 files changed, 83 insertions(+), 33 deletions(-)
diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index 35404563bc3..a07ff860643 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -218,7 +218,7 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
/*
* If both the element patterns have label expressions, they
- * need to be conjuncted, which is not supported right now.
+ * need to be conjuncted.
*
* However, an empty label expression means all labels.
* Conjunction of any label expression with all labels is the
@@ -231,10 +231,10 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
other->has_empty_labelexpr = gep->has_empty_labelexpr;
}
else if (!gep->has_empty_labelexpr && !equal(other->labelexpr, gep->labelexpr))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("element patterns with same variable name \"%s\" but different label expressions are not supported",
- gep->variable)));
+ other->labelexpr = (Node *) makeBoolExpr(AND_EXPR,
+ list_make2(other->labelexpr,
+ gep->labelexpr),
+ -1);
/*
* If two element patterns have the same variable name, they
@@ -870,49 +870,85 @@ get_path_elements_from_labelexpr(struct path_factor *pf, Node *labelexpr)
}
else if (IsA(labelexpr, BoolExpr))
{
- BoolExpr *be = castNode(BoolExpr, pf->labelexpr);
+ BoolExpr *be = castNode(BoolExpr, labelexpr);
List *label_exprs = be->args;
- /*
- * We only support label disjunction. So we just collect the distinct
- * elements merging element label OIDs of the elements with same OID.
- */
- Assert(be->boolop == OR_EXPR);
+ Assert(be->boolop == OR_EXPR || be->boolop == AND_EXPR);
- path_elements = NIL;
- foreach_ptr(Node, label_expr, label_exprs)
+ if (be->boolop == OR_EXPR)
{
- List *node_path_elements = get_path_elements_from_labelexpr(pf, label_expr);
-
- if (path_elements == NIL)
- path_elements = node_path_elements;
- else
+ /*
+ * Label disjunction: collect all distinct elements across all
+ * sub-expressions, merging elem_label_oids for elements that
+ * appear in more than one sub-expression.
+ */
+ path_elements = NIL;
+ foreach_ptr(Node, label_expr, label_exprs)
{
- foreach_ptr(struct path_element, npe, node_path_elements)
- {
- struct path_element *found = NULL;
+ List *node_path_elements = get_path_elements_from_labelexpr(pf, label_expr);
- foreach_ptr(struct path_element, pe, path_elements)
+ if (path_elements == NIL)
+ path_elements = node_path_elements;
+ else
+ {
+ foreach_ptr(struct path_element, npe, node_path_elements)
{
- if (npe->elemoid == pe->elemoid)
+ struct path_element *found = NULL;
+
+ foreach_ptr(struct path_element, pe, path_elements)
{
- pe->elem_label_oids = list_concat(pe->elem_label_oids,
- npe->elem_label_oids);
- found = pe;
- break;
+ if (npe->elemoid == pe->elemoid)
+ {
+ pe->elem_label_oids = list_concat(pe->elem_label_oids,
+ npe->elem_label_oids);
+ found = pe;
+ break;
+ }
}
+
+ if (!found)
+ path_elements = lappend(path_elements, npe);
}
+ }
+ }
+ }
+ else
+ {
+ List *left_elems;
+ List *right_elems;
+ List *intersection = NIL;
- if (!found)
- path_elements = lappend(path_elements, npe);
+ /*
+ * Label conjunction (implicit, from same-variable element
+ * patterns with different label expressions): recurse into each
+ * child of the binary AND tree and keep only elements that appear
+ * in both sides. Merge elem_label_oids so that property
+ * resolution can use labels from both sides of the conjunction.
+ */
+ left_elems = get_path_elements_from_labelexpr(pf, linitial(label_exprs));
+ right_elems = get_path_elements_from_labelexpr(pf, lsecond(label_exprs));
+
+ foreach_ptr(struct path_element, pe, left_elems)
+ {
+ foreach_ptr(struct path_element, npe, right_elems)
+ {
+ if (pe->elemoid == npe->elemoid)
+ {
+ pe->elem_label_oids = list_concat(pe->elem_label_oids,
+ npe->elem_label_oids);
+ intersection = lappend(intersection, pe);
+ break;
+ }
}
}
+ path_elements = intersection;
}
+
}
else
{
path_elements = NIL; /* Keep compiler quiet */
- elog(ERROR, "unsupported label expression node: %d", (int) nodeTag(pf->labelexpr));
+ elog(ERROR, "unsupported label expression node: %d", (int) nodeTag(labelexpr));
}
return path_elements;
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
index 082b509f95d..a0194424150 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -646,8 +646,20 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (a WHERE a.vprop1 between 20 and 2000)->(b W
-- labels and elements kinds of element patterns with the same variable name
SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS l1)-[a IS l1]->(b IS l1) COLUMNS (a.elname AS aename, b.elname AS bename)) ORDER BY 1, 2; -- error
ERROR: element patterns with same variable name "a" but different element pattern types
-SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1)->(b)->(a IS vl2) WHERE a.vname <> b.vname COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through; -- error
-ERROR: element patterns with same variable name "a" but different label expressions are not supported
+-- Implicit conjunction
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1)->(b)->(a IS vl2) WHERE a.vname <> b.vname COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+ self | through | self_p1 | through_p1
+------+---------+---------+------------
+(0 rows)
+
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl2)->(b)->(a IS vl3) COLUMNS (a.vname AS self, b.vname AS through, a.vprop2 AS vl2_prop, a.vprop1 AS vl3_prop)) ORDER BY self, through;
+ self | through | vl2_prop | vl3_prop
+------+---------+----------+----------
+ v21 | v12 | 1100 | 1010
+ v22 | v32 | 1200 | 1020
+ v23 | v13 | 1300 | 1030
+(3 rows)
+
SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1)->(b)->(a) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
self | through | self_p1 | through_p1
------+---------+---------+------------
diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql
index e37ce9a4d3d..12eede01019 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -378,7 +378,9 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b WHERE b.vprop1 > 20)->(a WHERE a.vpr
SELECT * FROM GRAPH_TABLE (g1 MATCH (a WHERE a.vprop1 between 20 and 2000)->(b WHERE b.vprop1 > 20)->(a WHERE a.vprop1 between 20 and 2000) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
-- labels and elements kinds of element patterns with the same variable name
SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS l1)-[a IS l1]->(b IS l1) COLUMNS (a.elname AS aename, b.elname AS bename)) ORDER BY 1, 2; -- error
-SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1)->(b)->(a IS vl2) WHERE a.vname <> b.vname COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through; -- error
+-- Implicit conjunction
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1)->(b)->(a IS vl2) WHERE a.vname <> b.vname COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl2)->(b)->(a IS vl3) COLUMNS (a.vname AS self, b.vname AS through, a.vprop2 AS vl2_prop, a.vprop1 AS vl3_prop)) ORDER BY self, through;
SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1)->(b)->(a) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(a IS vl1) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
--
2.34.1
From 3dc21234aa3bca685fe3202f852d190aecadf9e5 Mon Sep 17 00:00:00 2001
From: AyoubKAZ <[email protected]>
Date: Fri, 31 Jul 2026 13:11:45 +0200
Subject: [PATCH v1 1/4] Empty label expression in view definition
A view definition depends on all the graph labels that are explicitly
mentioned in the graph patterns in it so as to avoid it being rendered
invalid when any of the labels is dropped. But when a view definition
contains an empty label expression, we do not create any dependency
between the view and the labels that the empty label expression resolves
to. Resolve an empty label expression during transformation phase so
that we can create dependency between those labels and the view.
This will further help to avoid invalidation of a view containing
all-properties references when we support it.
An empty label expression may resolve to an empty set of labels if there
are not labels associated with the elements matching the kind of element
pattern containing the label expression. We do not have a syntax level
support for a label expression containing no labels. Hence we can not
dump a view containing such an empty label expression. Throw an error
when a query contains such an empty label expression. There are possibly
no real usecases which use such queries.
Author: Ashutosh Bapat <[email protected]>
Discussion: https://www.postgresql.org/message-id/CAExHW5twGP5Zuk4Zch4kz8XDrSpckWQipMs=ysaj8gmqna2...@mail.gmail.com
---
src/backend/parser/parse_graphtable.c | 110 +++++++++++++++++-
src/backend/rewrite/rewriteGraphTable.c | 78 ++++---------
src/include/nodes/parsenodes.h | 8 ++
.../expected/create_property_graph.out | 1 +
src/test/regress/expected/graph_table.out | 28 ++++-
.../regress/sql/create_property_graph.sql | 1 +
src/test/regress/sql/graph_table.sql | 15 ++-
7 files changed, 178 insertions(+), 63 deletions(-)
diff --git a/src/backend/parser/parse_graphtable.c b/src/backend/parser/parse_graphtable.c
index 73fbfb541f7..5c9da75c8cf 100644
--- a/src/backend/parser/parse_graphtable.c
+++ b/src/backend/parser/parse_graphtable.c
@@ -18,6 +18,8 @@
#include "access/genam.h"
#include "access/htup_details.h"
#include "access/table.h"
+#include "catalog/pg_propgraph_element.h"
+#include "catalog/pg_propgraph_element_label.h"
#include "catalog/pg_propgraph_label.h"
#include "catalog/pg_propgraph_property.h"
#include "miscadmin.h"
@@ -151,6 +153,51 @@ transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref)
return NULL;
}
+/*
+ * Given the OID of a label and the kind of graph element pattern, return true if
+ * there exists at least one element matching the given kind associated with the
+ * label. Otherwise return false.
+ */
+static bool
+label_has_elements_of_kind(Oid labelid, GraphElementPatternKind gepkind)
+{
+ Relation rel;
+ SysScanDesc scan;
+ ScanKeyData key[1];
+ HeapTuple tup;
+ bool result = false;
+
+ rel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+ ScanKeyInit(&key[0],
+ Anum_pg_propgraph_element_label_pgellabelid,
+ BTEqualStrategyNumber,
+ F_OIDEQ, ObjectIdGetDatum(labelid));
+ scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId,
+ true, NULL, 1, key);
+ while (!result && HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_propgraph_element_label element_label = (Form_pg_propgraph_element_label) GETSTRUCT(tup);
+ Oid element_oid = element_label->pgelelid;
+ HeapTuple element_tup = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(element_oid));
+ Form_pg_propgraph_element element_form;
+
+ if (!HeapTupleIsValid(element_tup))
+ elog(ERROR, "cache lookup failed for property graph element %u", element_oid);
+
+ element_form = (Form_pg_propgraph_element) GETSTRUCT(element_tup);
+
+ if ((element_form->pgekind == PGEKIND_VERTEX && gepkind == VERTEX_PATTERN) ||
+ (element_form->pgekind == PGEKIND_EDGE && IS_EDGE_PATTERN(gepkind)))
+ result = true;
+
+ ReleaseSysCache(element_tup);
+ }
+
+ systable_endscan(scan);
+ table_close(rel, AccessShareLock);
+ return result;
+}
+
/*
* Transform a label expression.
*
@@ -161,14 +208,66 @@ transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref)
* GraphLabelRef nodes corresponding to the names of the labels appearing in the
* expression. If any label name cannot be resolved to a label in the property
* graph, an error is raised.
+ *
+ * An empty label expression is treated as a special case. According to section
+ * 9.2 "Contextual inference of a set of labels" subclause 2.a.ii of SQL/PGQ
+ * standard, element pattern which does not have a label expression is
+ * considered to have label expression equivalent to '%|!%' which is set of all
+ * labels which have at least one element of the given element kind associated with it.
*/
static Node *
-transformLabelExpr(GraphTableParseState *gpstate, Node *labelexpr)
+transformLabelExpr(GraphTableParseState *gpstate, Node *labelexpr, GraphElementPatternKind gepkind)
{
Node *result;
- if (labelexpr == NULL)
- return NULL;
+ if (!labelexpr)
+ {
+ Relation rel;
+ SysScanDesc scan;
+ ScanKeyData key[1];
+ HeapTuple tup;
+ List *args = NIL;
+
+ rel = table_open(PropgraphLabelRelationId, AccessShareLock);
+ ScanKeyInit(&key[0],
+ Anum_pg_propgraph_label_pglpgid,
+ BTEqualStrategyNumber,
+ F_OIDEQ, ObjectIdGetDatum(gpstate->graphid));
+ scan = systable_beginscan(rel, PropgraphLabelGraphNameIndexId,
+ true, NULL, 1, key);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_propgraph_label label = (Form_pg_propgraph_label) GETSTRUCT(tup);
+ GraphLabelRef *lref;
+
+ if (!label_has_elements_of_kind(label->oid, gepkind))
+ continue;
+
+ lref = makeNode(GraphLabelRef);
+ lref->labelid = label->oid;
+ lref->location = -1;
+ args = lappend(args, lref);
+ }
+ systable_endscan(scan);
+ table_close(rel, AccessShareLock);
+
+ /*
+ * If there are no labels with elements of the given kind, the set of
+ * labels that this label expression resolves to is empty. There is no
+ * way to represent an empty set of labels as a label expression since
+ * we do not support label conjunction as well as negation. So we can
+ * not dump a view containing such a label expression. Hence prohibit
+ * it for now.
+ */
+ if (!args)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("empty label expression does not resolve to any label"));
+
+ result = (Node *) makeBoolExpr(OR_EXPR, args, -1);
+ return result;
+
+ }
check_stack_depth();
@@ -208,7 +307,7 @@ transformLabelExpr(GraphTableParseState *gpstate, Node *labelexpr)
{
Node *arg = (Node *) lfirst(lc);
- arg = transformLabelExpr(gpstate, arg);
+ arg = transformLabelExpr(gpstate, arg, gepkind);
args = lappend(args, arg);
}
@@ -249,7 +348,8 @@ transformGraphElementPattern(ParseState *pstate, GraphElementPattern *gep)
gpstate->cur_gep = gep;
- gep->labelexpr = transformLabelExpr(gpstate, gep->labelexpr);
+ gep->has_empty_labelexpr = !gep->labelexpr;
+ gep->labelexpr = transformLabelExpr(gpstate, gep->labelexpr, gep->kind);
gep->whereClause = transformExpr(pstate, gep->whereClause, EXPR_KIND_WHERE);
diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index cdb1f4c0dca..a5b1be77a0a 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -58,6 +58,8 @@ struct path_factor
{
GraphElementPatternKind kind;
const char *variable;
+ bool has_empty_labelexpr; /* Copied from the corresponding
+ * GraphElementPattern */
Node *labelexpr;
Node *whereClause;
int factorpos; /* Position of this path factor in the list of
@@ -221,9 +223,12 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
* expression itself. Hence if only one of the two element
* patterns has a label expression use that expression.
*/
- if (!other->labelexpr)
+ if (other->has_empty_labelexpr)
+ {
other->labelexpr = gep->labelexpr;
- else if (gep->labelexpr && !equal(other->labelexpr, gep->labelexpr))
+ other->has_empty_labelexpr = gep->has_empty_labelexpr;
+ }
+ else if (!gep->has_empty_labelexpr && !equal(other->labelexpr, gep->labelexpr))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("element patterns with same variable name \"%s\" but different label expressions are not supported",
@@ -250,6 +255,7 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
pf = palloc0_object(struct path_factor);
pf->factorpos = factorpos++;
pf->kind = gep->kind;
+ pf->has_empty_labelexpr = gep->has_empty_labelexpr;
pf->labelexpr = gep->labelexpr;
pf->variable = gep->variable;
pf->whereClause = gep->whereClause;
@@ -830,37 +836,9 @@ get_labels_for_expr(Oid propgraphid, Node *labelexpr)
{
List *label_oids;
- if (!labelexpr)
- {
- Relation rel;
- SysScanDesc scan;
- ScanKeyData key[1];
- HeapTuple tup;
-
- /*
- * According to section 9.2 "Contextual inference of a set of labels"
- * subclause 2.a.ii of SQL/PGQ standard, element pattern which does
- * not have a label expression is considered to have label expression
- * equivalent to '%|!%' which is set of all labels.
- */
- label_oids = NIL;
- rel = table_open(PropgraphLabelRelationId, AccessShareLock);
- ScanKeyInit(&key[0],
- Anum_pg_propgraph_label_pglpgid,
- BTEqualStrategyNumber,
- F_OIDEQ, ObjectIdGetDatum(propgraphid));
- scan = systable_beginscan(rel, PropgraphLabelGraphNameIndexId,
- true, NULL, 1, key);
- while (HeapTupleIsValid(tup = systable_getnext(scan)))
- {
- Form_pg_propgraph_label label = (Form_pg_propgraph_label) GETSTRUCT(tup);
+ Assert(labelexpr);
- label_oids = lappend_oid(label_oids, label->oid);
- }
- systable_endscan(scan);
- table_close(rel, AccessShareLock);
- }
- else if (IsA(labelexpr, GraphLabelRef))
+ if (IsA(labelexpr, GraphLabelRef))
{
GraphLabelRef *glr = castNode(GraphLabelRef, labelexpr);
@@ -910,7 +888,6 @@ get_path_elements_for_path_factor(Oid propgraphid, struct path_factor *pf)
List *elem_oids_seen = NIL;
List *pf_elem_oids = NIL;
List *path_elements = NIL;
- List *unresolved_labels = NIL;
Relation rel;
SysScanDesc scan;
ScanKeyData key[1];
@@ -977,33 +954,26 @@ get_path_elements_for_path_factor(Oid propgraphid, struct path_factor *pf)
{
/*
* We did not find any qualified element associated with this
- * label. The label or its properties can not be associated with
- * the given element pattern. Throw an error if the label was
- * explicitly specified in the element pattern. Otherwise remember
- * it for later use.
+ * label. Throw an error.
+ *
+ * An empty label expression is replaced by all labels that are
+ * associated with at least one element of the required kind. We
+ * should not reach here in that case.
*/
- if (!pf->labelexpr)
- unresolved_labels = lappend_oid(unresolved_labels, labeloid);
- else
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("no property graph element of type \"%s\" has label \"%s\" associated with it in property graph \"%s\"",
- pf->kind == VERTEX_PATTERN ? "vertex" : "edge",
- get_propgraph_label_name(labeloid),
- get_rel_name(propgraphid))));
+ Assert(!pf->has_empty_labelexpr);
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("no property graph element of type \"%s\" has label \"%s\" associated with it in property graph \"%s\"",
+ pf->kind == VERTEX_PATTERN ? "vertex" : "edge",
+ get_propgraph_label_name(labeloid),
+ get_rel_name(propgraphid))));
}
systable_endscan(scan);
}
table_close(rel, AccessShareLock);
-
- /*
- * Remove the labels which were not explicitly mentioned in the label
- * expression but do not have any qualified elements associated with them.
- * Properties associated with such labels may not be referenced. See
- * replace_property_refs_mutator() for more details.
- */
- pf->labeloids = list_difference_oid(label_oids, unresolved_labels);
+ pf->labeloids = label_oids;
return path_elements;
}
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5c8f9a07b62..b98a0758a7b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1050,7 +1050,15 @@ typedef struct GraphElementPattern
NodeTag type;
GraphElementPatternKind kind;
const char *variable;
+
+ /*
+ * If no label expression is specified, we will replace it with a non-NULL
+ * expression in transformLabelExpr(). This flag indicates whether the
+ * label expression was originally empty.
+ */
+ bool has_empty_labelexpr;
Node *labelexpr;
+
List *subexpr;
Node *whereClause;
List *quantifier;
diff --git a/src/test/regress/expected/create_property_graph.out b/src/test/regress/expected/create_property_graph.out
index 7387751eac2..2da3fed981f 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -960,6 +960,7 @@ DETAIL: Table "v2tmp" is a temporary table.
DROP TABLE g2; -- error: wrong object type
ERROR: "g2" is not a table
HINT: Use DROP PROPERTY GRAPH to remove a property graph.
+ALTER PROPERTY GRAPH g1 ADD VERTEX TABLES (t1 KEY (a)); -- to make a valid graph query
CREATE VIEW vg1 AS SELECT * FROM GRAPH_TABLE(g1 MATCH () COLUMNS (1 AS one));
DROP PROPERTY GRAPH g1; -- error
ERROR: cannot drop property graph g1 because other objects depend on it
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
index 46566b2e32f..97ff6899ba4 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -865,6 +865,8 @@ EXECUTE loopstmt;
(2 rows)
-- inheritance and partitioning
+--
+-- Also test temporary property graphs, keywords NODE and RELATIONSHIP
CREATE TABLE pv (id int, val int);
CREATE TABLE cv1 () INHERITS (pv);
CREATE TABLE cv2 () INHERITS (pv);
@@ -934,8 +936,12 @@ CREATE TABLE ptne1 PARTITION OF ptne FOR VALUES IN (1, 2);
CREATE TABLE ptne2 PARTITION OF ptne FOR VALUES IN (3);
INSERT INTO ptne VALUES (1, 1, 2, 100), (2, 2, 3, 200), (3, 3, 1, 300);
CREATE PROPERTY GRAPH g4
- VERTEX TABLES (ptnv)
- EDGE TABLES (
+ VERTEX TABLES (ptnv);
+-- empty label expression which resolves to no labels
+SELECT * FROM GRAPH_TABLE (g4 MATCH (s is ptnv)-[e]-(d is ptnv) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3; -- error
+ERROR: empty label expression does not resolve to any label
+ALTER PROPERTY GRAPH g4
+ ADD EDGE TABLES (
ptne
SOURCE KEY (src) REFERENCES ptnv(id)
DESTINATION KEY (dest) REFERENCES ptnv(id)
@@ -991,6 +997,17 @@ ALTER PROPERTY GRAPH myshop ALTER VERTEX TABLE products
ERROR: cannot drop property price of property graph myshop because other objects depend on it
DETAIL: view customers_us depends on property price of property graph myshop
HINT: Use DROP ... CASCADE to drop the dependent objects too.
+-- Empty label expression creates a dependency between the view and the set of
+-- labels it resolves to
+CREATE VIEW v_empty_label AS SELECT * FROM GRAPH_TABLE (g1 MATCH (v WHERE v.vprop1 = 10) COLUMNS (v.elname));
+ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v1 DROP LABEL vl1; -- error
+ERROR: cannot drop label vl1 of property graph g1 because other objects depend on it
+DETAIL: view v_empty_label depends on label vl1 of property graph g1
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v2 DROP LABEL vl2; -- error
+ERROR: cannot drop label vl2 of property graph g1 because other objects depend on it
+DETAIL: view v_empty_label depends on label vl2 of property graph g1
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
-- ruleutils reverse parsing
SELECT pg_get_viewdef('customers_us'::regclass);
pg_get_viewdef
@@ -1004,6 +1021,13 @@ SELECT pg_get_viewdef('customers_us'::regclass);
ORDER BY g.customer_name, g.product_name;
(1 row)
+SELECT pg_get_viewdef('v_empty_label'::regclass);
+ pg_get_viewdef
+----------------------------------------------------------------------------------------------------------
+ SELECT elname +
+ FROM GRAPH_TABLE (g1 MATCH (v IS l1|vl1|vl2|vl3 WHERE (v.vprop1 = 10)) COLUMNS (v.elname AS elname));
+(1 row)
+
-- test view/graph nesting
CREATE VIEW customers_view AS SELECT customer_id, 'redacted' || customer_id AS name_redacted, address FROM customers;
SELECT * FROM customers;
diff --git a/src/test/regress/sql/create_property_graph.sql b/src/test/regress/sql/create_property_graph.sql
index 3494390b923..907b4ecb1cb 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -375,6 +375,7 @@ ALTER PROPERTY GRAPH g1
-- DROP, ALTER SET SCHEMA, ALTER PROPERTY GRAPH RENAME TO
DROP TABLE g2; -- error: wrong object type
+ALTER PROPERTY GRAPH g1 ADD VERTEX TABLES (t1 KEY (a)); -- to make a valid graph query
CREATE VIEW vg1 AS SELECT * FROM GRAPH_TABLE(g1 MATCH () COLUMNS (1 AS one));
DROP PROPERTY GRAPH g1; -- error
ALTER PROPERTY GRAPH g1 SET SCHEMA create_property_graph_tests_2;
diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql
index 3fb0e50ddb2..0afcb94eee8 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -479,6 +479,8 @@ ALTER PROPERTY GRAPH g1 ALTER EDGE TABLE e3_3 ALTER LABEL l2 ADD PROPERTIES ((en
EXECUTE loopstmt;
-- inheritance and partitioning
+--
+-- Also test temporary property graphs, keywords NODE and RELATIONSHIP
CREATE TABLE pv (id int, val int);
CREATE TABLE cv1 () INHERITS (pv);
CREATE TABLE cv2 () INHERITS (pv);
@@ -525,8 +527,11 @@ CREATE TABLE ptne1 PARTITION OF ptne FOR VALUES IN (1, 2);
CREATE TABLE ptne2 PARTITION OF ptne FOR VALUES IN (3);
INSERT INTO ptne VALUES (1, 1, 2, 100), (2, 2, 3, 200), (3, 3, 1, 300);
CREATE PROPERTY GRAPH g4
- VERTEX TABLES (ptnv)
- EDGE TABLES (
+ VERTEX TABLES (ptnv);
+-- empty label expression which resolves to no labels
+SELECT * FROM GRAPH_TABLE (g4 MATCH (s is ptnv)-[e]-(d is ptnv) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3; -- error
+ALTER PROPERTY GRAPH g4
+ ADD EDGE TABLES (
ptne
SOURCE KEY (src) REFERENCES ptnv(id)
DESTINATION KEY (dest) REFERENCES ptnv(id)
@@ -556,8 +561,14 @@ ALTER PROPERTY GRAPH myshop ALTER VERTEX TABLE customers
ALTER LABEL customers DROP PROPERTIES (address); -- error
ALTER PROPERTY GRAPH myshop ALTER VERTEX TABLE products
ALTER LABEL products DROP PROPERTIES (price); -- error
+-- Empty label expression creates a dependency between the view and the set of
+-- labels it resolves to
+CREATE VIEW v_empty_label AS SELECT * FROM GRAPH_TABLE (g1 MATCH (v WHERE v.vprop1 = 10) COLUMNS (v.elname));
+ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v1 DROP LABEL vl1; -- error
+ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v2 DROP LABEL vl2; -- error
-- ruleutils reverse parsing
SELECT pg_get_viewdef('customers_us'::regclass);
+SELECT pg_get_viewdef('v_empty_label'::regclass);
-- test view/graph nesting
--
2.34.1
From d20bad85e4165e341ab4288430ebd1b2aab79aec Mon Sep 17 00:00:00 2001
From: AyoubKAZ <[email protected]>
Date: Wed, 5 Aug 2026 14:15:25 +0200
Subject: [PATCH v1 3/4] Support all properties reference in COLUMNs list of a
GraphTableRef
In order to expand all properties reference, we need to find all the properties
associated with the set of labels which results from evaluating label
expression. The properties are not directly associated with a label, but through
an element. Hence we need to find at one element associated with each label in
the set. Further the set of labels that an empty label expression results into
is all the labels which have at least one element of given element kind in the
property graph. That's another reason why we want to find at least one element
associated with each of the labels. If we are looking up element label catalog,
why not to fetch all the elements during transformation itself rather than
waiting all the way to till the rewriting phase. So I changed the code to do
that. And I think the resultant code is much simpler, moves the error handling
to appropriate places and simplifies a lot of the rewriteGraphTable.c code. Flip
side is transform* functions are heavier, however in the end it's code
simplification. Since we are expanding the empty label expresison during
transformation phase itself, we replace empty label expression with a
disjunction. But we need to know whether the original label expression was empty
or not in the ruleutils and when consolidating path elements
(generate_queries_for_path_pattern()). The later usage will vanish once we
support label disjunction. So I introduced a flag to retain that status.
While at it also fix a test to use correct property name so that it
throws expected error. Before this change, the properties of a given
element were resolved after the element patterns with the same variable
name were squashed. With this change the order is reversed.
Author: Ashutosh Bapat <[email protected]>
Reported by: Henson Choi, Junwang Zhao
---
src/backend/parser/parse_clause.c | 62 ++-
src/backend/parser/parse_graphtable.c | 467 +++++++++++++++++-----
src/backend/rewrite/rewriteGraphTable.c | 297 ++++----------
src/include/nodes/parsenodes.h | 6 +-
src/include/nodes/primnodes.h | 3 +
src/include/parser/parse_graphtable.h | 2 +-
src/include/parser/parse_node.h | 6 +
src/test/regress/expected/graph_table.out | 62 ++-
src/test/regress/sql/graph_table.sql | 15 +-
src/tools/pgindent/typedefs.list | 1 +
10 files changed, 571 insertions(+), 350 deletions(-)
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 485e33b9e5a..d3ee787f15b 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -981,28 +981,60 @@ transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt)
TargetEntry *te;
char *colname;
- colexpr = transformExpr(pstate, rt->val, EXPR_KIND_SELECT_TARGET);
+ bool is_all_props_ref;
- if (rt->name)
- colname = rt->name;
+ /*
+ * Try resolving the expression as <variable>.* first. Those can only
+ * appear directly in COLUMNs list. It gets expanded to a list of
+ * GraphPropertyRef, which are added to the targetlist.
+ *
+ * If there are no properties associated with the variable, an empty
+ * list is returned. In such a case, we don't add anything to the
+ * targetlist.
+ */
+ colexpr = transformGraphTableAllPropRef(pstate, rt->val, &is_all_props_ref);
+ if (is_all_props_ref)
+ {
+ if (colexpr)
+ {
+ List *property_list = castNode(List, colexpr);
+
+ Assert(!rt->name);
+
+ /* Process each GraphPropertyRef in the list */
+ foreach_node(GraphPropertyRef, gpr, property_list)
+ {
+ char *prop_colname = get_propgraph_property_name(gpr->propid);
+
+ colnames = lappend(colnames, makeString(prop_colname));
+ te = makeTargetEntry((Expr *) gpr, ++resno, prop_colname, false);
+ columns = lappend(columns, te);
+ }
+ }
+ }
else
{
- if (IsA(colexpr, GraphPropertyRef))
- colname = get_propgraph_property_name(castNode(GraphPropertyRef, colexpr)->propid);
+ colexpr = transformExpr(pstate, rt->val, EXPR_KIND_SELECT_TARGET);
+ if (rt->name)
+ colname = rt->name;
else
{
- ereport(ERROR,
- errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("complex graph table column must specify an explicit column name"),
- parser_errposition(pstate, rt->location));
- colname = NULL;
+ if (IsA(colexpr, GraphPropertyRef))
+ colname = get_propgraph_property_name(castNode(GraphPropertyRef, colexpr)->propid);
+ else
+ {
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("complex graph table column must specify an explicit column name"),
+ parser_errposition(pstate, rt->location));
+ colname = NULL;
+ }
}
- }
- colnames = lappend(colnames, makeString(colname));
-
- te = makeTargetEntry((Expr *) colexpr, ++resno, colname, false);
- columns = lappend(columns, te);
+ colnames = lappend(colnames, makeString(colname));
+ te = makeTargetEntry((Expr *) colexpr, ++resno, colname, false);
+ columns = lappend(columns, te);
+ }
}
/* resolve any still-unresolved output columns as being type text */
diff --git a/src/backend/parser/parse_graphtable.c b/src/backend/parser/parse_graphtable.c
index 5c9da75c8cf..fa9c7a513b8 100644
--- a/src/backend/parser/parse_graphtable.c
+++ b/src/backend/parser/parse_graphtable.c
@@ -21,6 +21,7 @@
#include "catalog/pg_propgraph_element.h"
#include "catalog/pg_propgraph_element_label.h"
#include "catalog/pg_propgraph_label.h"
+#include "catalog/pg_propgraph_label_property.h"
#include "catalog/pg_propgraph_property.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -34,6 +35,169 @@
#include "utils/syscache.h"
+static List *get_labelexpr_properties(Node *labelexpr);
+
+/*
+ * Find all properties associated with a label.
+ *
+ * We do not store the direct relationship between labels and properties in the
+ * catalog, but instead we link them through element labels. A label is
+ * associated with an element through an element_label and the element_label is associated with
+ * properties. Therefore, to find properties of a label, we first find an element_label
+ * associated with the label and then find properties associated with that
+ * element_label. Since all elements with the same label have the same set of
+ * properties, it does not matter which element_label we choose as long as there
+ * is at least one.
+ *
+ * `elem_label_oid` is the OID of the element_label associated with the label
+ * whose properties we want to find.
+ *
+ * Returns List of property OIDs.
+ */
+static List *
+get_propgraph_label_properties(Oid elem_label_oid)
+{
+ List *propids = NIL;
+ Relation label_prop_rel;
+ SysScanDesc prop_scan;
+ ScanKeyData prop_key[1];
+ HeapTuple prop_tup;
+
+ /* Find properties for this element_label */
+ label_prop_rel = table_open(PropgraphLabelPropertyRelationId, AccessShareLock);
+ ScanKeyInit(&prop_key[0],
+ Anum_pg_propgraph_label_property_plpellabelid,
+ BTEqualStrategyNumber,
+ F_OIDEQ, ObjectIdGetDatum(elem_label_oid));
+ prop_scan = systable_beginscan(label_prop_rel, PropgraphLabelPropertyLabelPropIndexId,
+ true, NULL, 1, prop_key);
+
+ while (HeapTupleIsValid(prop_tup = systable_getnext(prop_scan)))
+ {
+ Form_pg_propgraph_label_property label_prop = (Form_pg_propgraph_label_property) GETSTRUCT(prop_tup);
+
+ propids = lappend_oid(propids, label_prop->plppropid);
+ }
+
+ systable_endscan(prop_scan);
+ table_close(label_prop_rel, AccessShareLock);
+
+ return propids;
+}
+
+/*
+ * Find the set of properties associated with the given label expression.
+ *
+ * Independent of the actual expression, the set of properties that can be
+ * projected by an element variable associated with the given expression is the
+ * union of the properties associated with each label that appears in the expression.
+ */
+List *
+get_labelexpr_properties(Node *labelexpr)
+{
+ List *propids = NIL;
+
+ Assert(labelexpr != NULL);
+
+ check_stack_depth();
+
+ switch (nodeTag(labelexpr))
+ {
+ case T_GraphLabelRef:
+ {
+ GraphLabelRef *lref = castNode(GraphLabelRef, labelexpr);
+
+ if (!lref->elem_labels)
+ {
+ /*
+ * No element is associated with this label, return empty
+ * property list.
+ */
+ propids = NIL;
+ }
+ else
+ propids = get_propgraph_label_properties(linitial_oid(lref->elem_labels));
+
+ break;
+ }
+
+ case T_BoolExpr:
+ {
+ BoolExpr *be = castNode(BoolExpr, labelexpr);
+
+ foreach_ptr(Node, arg, be->args)
+ {
+ List *arg_propids = get_labelexpr_properties(arg);
+
+ propids = list_concat_unique_oid(propids, arg_propids);
+ }
+ break;
+ }
+
+ default:
+ /* should not reach here for a transformed label expression */
+ elog(ERROR, "unsupported label expression node: %d", (int) nodeTag(labelexpr));
+ break;
+ }
+
+ return propids;
+}
+
+/*
+ * Find all elements that fit the given kind associated with the given label.
+ *
+ * Return the element OIDs through the output parameter `elements`. Often we
+ * will need the element_label OIDs corresponding to these elements as well, so
+ * we return them through the output parameter `elem_labels`.
+ */
+static void
+get_propgraph_label_elements(Oid labelid, GraphElementPatternKind gepkind,
+ List **elements, List **elem_labels)
+{
+ List *element_oids = NIL;
+ List *elem_labels_oids = NIL;
+ Relation rel;
+ SysScanDesc scan;
+ ScanKeyData key[1];
+ HeapTuple tup;
+
+ rel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+ ScanKeyInit(&key[0],
+ Anum_pg_propgraph_element_label_pgellabelid,
+ BTEqualStrategyNumber,
+ F_OIDEQ, ObjectIdGetDatum(labelid));
+ scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId,
+ true, NULL, 1, key);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_propgraph_element_label element_label = (Form_pg_propgraph_element_label) GETSTRUCT(tup);
+ Oid element_oid = element_label->pgelelid;
+ Oid elem_label_oid = element_label->oid;
+ HeapTuple element_tup = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(element_oid));
+ Form_pg_propgraph_element element_form;
+
+ if (!HeapTupleIsValid(element_tup))
+ elog(ERROR, "cache lookup failed for property graph element %u", element_oid);
+
+ element_form = (Form_pg_propgraph_element) GETSTRUCT(element_tup);
+
+ if ((element_form->pgekind == PGEKIND_VERTEX && gepkind == VERTEX_PATTERN) ||
+ (element_form->pgekind == PGEKIND_EDGE && IS_EDGE_PATTERN(gepkind)))
+ {
+ element_oids = lappend_oid(element_oids, element_oid);
+ elem_labels_oids = lappend_oid(elem_labels_oids, elem_label_oid);
+ }
+
+ ReleaseSysCache(element_tup);
+ }
+
+ systable_endscan(scan);
+ table_close(rel, AccessShareLock);
+
+ *elements = element_oids;
+ *elem_labels = elem_labels_oids;
+}
+
/*
* Return human-readable name of the type of graph element pattern in
* GRAPH_TABLE clause, usually for error message purpose.
@@ -64,89 +228,77 @@ get_gep_kind_name(GraphElementPatternKind gepkind)
}
/*
- * Transform a property reference.
+ * Transform a <variable>.* expression.
*
- * A property reference is parsed as a ColumnRef of the form:
- * <variable>.<property>. If <variable> is one of the variables bound to an
- * element pattern in the graph pattern and <property> can be resolved as a
- * property of the property graph, then we return a GraphPropertyRef node
- * representing the property reference. If the <variable> exists in the graph
- * pattern but <property> does not exist in the property graph, we raise an
- * error. However, if <variable> does not exist in the graph pattern, we return
- * NULL to let the caller handle it as some other kind of ColumnRef. The
- * variables bound to the element patterns in the graph pattern are expected to
- * be collected in the GraphTableParseState.
+ * If the column reference is of the form <variable>.*, return a list of
+ * GraphPropertyRef nodes representing the properties of the variable. When there
+ * are no properties associated with the variable, return an empty list. If the
+ * column reference is not of the form <variable>.*, return NULL.
+ *
+ * Since an empty list and NULL are both represented as a C NULL pointer, we use the
+ * output parameter `is_all_props_ref` to indicate whether the NULL pointer
+ * returned by this function means an empty list of properties or that the given
+ * column reference is not of the form <variable>.*.
*/
Node *
-transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref)
+transformGraphTableAllPropRef(ParseState *pstate, Node *node, bool *is_all_props_ref)
{
GraphTableParseState *gpstate = pstate->p_graph_table_pstate;
+ ColumnRef *cref;
+
+ *is_all_props_ref = false;
if (!gpstate)
return NULL;
+ if (!IsA(node, ColumnRef))
+ return NULL;
+
+ cref = castNode(ColumnRef, node);
if (list_length(cref->fields) == 2)
{
Node *field1 = linitial(cref->fields);
Node *field2 = lsecond(cref->fields);
- char *elvarname;
- char *propname;
+ char *elvarname = strVal(field1);
- if (IsA(field1, A_Star) || IsA(field2, A_Star))
+ /* Find the variable in the graph pattern variables */
+ foreach_ptr(GraphTableElementVariable, var, gpstate->variables)
{
- if (pstate->p_expr_kind == EXPR_KIND_SELECT_TARGET)
- ereport(ERROR,
- errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("\"*\" is not supported here"),
- parser_errposition(pstate, cref->location));
- else
- ereport(ERROR,
- errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("\"*\" not allowed here"),
- parser_errposition(pstate, cref->location));
- }
+ if (strcmp(var->name, elvarname) == 0)
+ {
+ GraphPropertyRef *gpr;
+ HeapTuple pgptup;
+ Form_pg_propgraph_property pgpform;
- elvarname = strVal(field1);
- propname = strVal(field2);
+ if (IsA(field2, A_Star))
+ {
+ List *propref_list = NIL;
- if (list_member(gpstate->variables, field1))
- {
- GraphPropertyRef *gpr;
- HeapTuple pgptup;
- Form_pg_propgraph_property pgpform;
+ *is_all_props_ref = true;
- /*
- * If we are transforming expression in an element pattern,
- * property references containing only that variable are allowed.
- */
- if (gpstate->cur_gep)
- {
- if (!gpstate->cur_gep->variable ||
- strcmp(elvarname, gpstate->cur_gep->variable) != 0)
- ereport(ERROR,
- errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("non-local element variable reference is not supported"),
- parser_errposition(pstate, cref->location));
- }
+ foreach_oid(propid, var->properties)
+ {
+ gpr = makeNode(GraphPropertyRef);
+ pgptup = SearchSysCache1(PROPGRAPHPROPOID, ObjectIdGetDatum(propid));
- gpr = makeNode(GraphPropertyRef);
- pgptup = SearchSysCache2(PROPGRAPHPROPNAME, ObjectIdGetDatum(gpstate->graphid), CStringGetDatum(propname));
- if (!HeapTupleIsValid(pgptup))
- ereport(ERROR,
- errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("property \"%s\" does not exist", propname));
- pgpform = (Form_pg_propgraph_property) GETSTRUCT(pgptup);
+ if (!HeapTupleIsValid(pgptup))
+ elog(ERROR, "cache lookup failed for property %u", propid);
+ pgpform = (Form_pg_propgraph_property) GETSTRUCT(pgptup);
- gpr->location = cref->location;
- gpr->elvarname = elvarname;
- gpr->propid = pgpform->oid;
- gpr->typeId = pgpform->pgptypid;
- gpr->typmod = pgpform->pgptypmod;
- gpr->collation = pgpform->pgpcollation;
+ gpr->location = cref->location;
+ gpr->elvarname = elvarname;
+ gpr->propid = propid;
+ gpr->typeId = pgpform->pgptypid;
+ gpr->typmod = pgpform->pgptypmod;
+ gpr->collation = pgpform->pgpcollation;
- ReleaseSysCache(pgptup);
+ ReleaseSysCache(pgptup);
+ propref_list = lappend(propref_list, gpr);
+ }
- return (Node *) gpr;
+ return (Node *) propref_list;
+ }
+ }
}
}
@@ -154,50 +306,106 @@ transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref)
}
/*
- * Given the OID of a label and the kind of graph element pattern, return true if
- * there exists at least one element matching the given kind associated with the
- * label. Otherwise return false.
+ * Transform a property reference.
+ *
+ * A property reference is parsed as a ColumnRef of the form:
+ * <variable>.<property>.
+ *
+ * If <variable> is one of the variables bound to an
+ * element pattern in the graph pattern and <property> can be resolved as a
+ * property of the property graph and is associated with the element variable,
+ * then we return a GraphPropertyRef node representing the property reference.
+ *
+ * If
+ * the <variable> exists in the graph pattern but <property> does not exist in
+ * the property graph, we raise an error.
+ *
+ * However, if <variable> does not exist
+ * in the graph pattern, we return NULL to let the caller handle it as some other
+ * kind of ColumnRef. The variables bound to the element patterns in the graph
+ * pattern are expected to be collected in the GraphTableParseState.
*/
-static bool
-label_has_elements_of_kind(Oid labelid, GraphElementPatternKind gepkind)
+Node *
+transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref)
{
- Relation rel;
- SysScanDesc scan;
- ScanKeyData key[1];
- HeapTuple tup;
- bool result = false;
+ GraphTableParseState *gpstate = pstate->p_graph_table_pstate;
- rel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
- ScanKeyInit(&key[0],
- Anum_pg_propgraph_element_label_pgellabelid,
- BTEqualStrategyNumber,
- F_OIDEQ, ObjectIdGetDatum(labelid));
- scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId,
- true, NULL, 1, key);
- while (!result && HeapTupleIsValid(tup = systable_getnext(scan)))
+ if (!gpstate)
+ return NULL;
+
+ if (list_length(cref->fields) == 2)
{
- Form_pg_propgraph_element_label element_label = (Form_pg_propgraph_element_label) GETSTRUCT(tup);
- Oid element_oid = element_label->pgelelid;
- HeapTuple element_tup = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(element_oid));
- Form_pg_propgraph_element element_form;
+ Node *field1 = linitial(cref->fields);
+ Node *field2 = lsecond(cref->fields);
+ char *elvarname;
+ char *propname;
- if (!HeapTupleIsValid(element_tup))
- elog(ERROR, "cache lookup failed for property graph element %u", element_oid);
+ if (IsA(field1, A_Star) || IsA(field2, A_Star))
+ {
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("\"*\" not allowed here"),
+ parser_errposition(pstate, cref->location));
+ }
- element_form = (Form_pg_propgraph_element) GETSTRUCT(element_tup);
+ elvarname = strVal(field1);
+ propname = strVal(field2);
- if ((element_form->pgekind == PGEKIND_VERTEX && gepkind == VERTEX_PATTERN) ||
- (element_form->pgekind == PGEKIND_EDGE && IS_EDGE_PATTERN(gepkind)))
- result = true;
+ /* Find the variable in the graph pattern variables */
+ foreach_ptr(GraphTableElementVariable, var, gpstate->variables)
+ {
+ if (strcmp(var->name, elvarname) == 0)
+ {
+ GraphPropertyRef *gpr = makeNode(GraphPropertyRef);
+ HeapTuple pgptup;
+ Form_pg_propgraph_property pgpform;
+
+ /*
+ * If we are transforming expression in an element pattern,
+ * property references containing only that variable are
+ * allowed.
+ */
+ if (gpstate->cur_gep)
+ {
+ if (!gpstate->cur_gep->variable ||
+ strcmp(elvarname, gpstate->cur_gep->variable) != 0)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("non-local element variable reference is not supported"),
+ parser_errposition(pstate, cref->location));
+ }
- ReleaseSysCache(element_tup);
+ pgptup = SearchSysCache2(PROPGRAPHPROPNAME, ObjectIdGetDatum(gpstate->graphid), CStringGetDatum(propname));
+ if (!HeapTupleIsValid(pgptup))
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("property \"%s\" does not exist", propname));
+ pgpform = (Form_pg_propgraph_property) GETSTRUCT(pgptup);
+ /* Check if this property is available for this variable */
+ if (!list_member_oid(var->properties, pgpform->oid))
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("property \"%s\" is not available for element variable \"%s\"",
+ propname, elvarname));
+
+ gpr->location = cref->location;
+ gpr->elvarname = elvarname;
+ gpr->propid = pgpform->oid;
+ gpr->typeId = pgpform->pgptypid;
+ gpr->typmod = pgpform->pgptypmod;
+ gpr->collation = pgpform->pgpcollation;
+
+ ReleaseSysCache(pgptup);
+
+ return (Node *) gpr;
+ }
+ }
}
- systable_endscan(scan);
- table_close(rel, AccessShareLock);
- return result;
+ return NULL;
}
+
/*
* Transform a label expression.
*
@@ -222,6 +430,7 @@ transformLabelExpr(GraphTableParseState *gpstate, Node *labelexpr, GraphElementP
if (!labelexpr)
{
+ /* Empty label expression, transform into a disjunction of labels */
Relation rel;
SysScanDesc scan;
ScanKeyData key[1];
@@ -235,16 +444,27 @@ transformLabelExpr(GraphTableParseState *gpstate, Node *labelexpr, GraphElementP
F_OIDEQ, ObjectIdGetDatum(gpstate->graphid));
scan = systable_beginscan(rel, PropgraphLabelGraphNameIndexId,
true, NULL, 1, key);
+
while (HeapTupleIsValid(tup = systable_getnext(scan)))
{
Form_pg_propgraph_label label = (Form_pg_propgraph_label) GETSTRUCT(tup);
+ List *elements;
+ List *elem_labels;
GraphLabelRef *lref;
- if (!label_has_elements_of_kind(label->oid, gepkind))
+ get_propgraph_label_elements(label->oid, gepkind, &elements, &elem_labels);
+
+ /*
+ * Only include the labels which have elements associated with it.
+ * Properties of the other labels do not matter.
+ */
+ if (!elements)
continue;
lref = makeNode(GraphLabelRef);
lref->labelid = label->oid;
+ lref->elements = elements;
+ lref->elem_labels = elem_labels;
lref->location = -1;
args = lappend(args, lref);
}
@@ -293,6 +513,15 @@ transformLabelExpr(GraphTableParseState *gpstate, Node *labelexpr, GraphElementP
lref->labelid = labelid;
lref->location = cref->location;
+ get_propgraph_label_elements(labelid, gepkind, &lref->elements, &lref->elem_labels);
+ if (!lref->elements)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("no property graph element of type \"%s\" has label \"%s\" associated with it in property graph \"%s\"",
+ gepkind == VERTEX_PATTERN ? "vertex" : "edge",
+ get_propgraph_label_name(labelid),
+ get_rel_name(gpstate->graphid))));
+
result = (Node *) lref;
break;
}
@@ -329,10 +558,11 @@ transformLabelExpr(GraphTableParseState *gpstate, Node *labelexpr, GraphElementP
* Transform a GraphElementPattern.
*
* Transform the label expression and the where clause in the element pattern
- * given by GraphElementPattern. The variable name in the GraphElementPattern is
- * added to the list of variables in the GraphTableParseState which is used to
- * resolve property references in this element pattern or elsewhere in the
- * GRAPH_TABLE.
+ * given by GraphElementPattern.
+ *
+ * While doing so build the namespace for the graph table, of which this element
+ * is a part. The namespace is saved in GraphTableParseState as a list of
+ * variables and their respective properties.
*/
static Node *
transformGraphElementPattern(ParseState *pstate, GraphElementPattern *gep)
@@ -348,9 +578,29 @@ transformGraphElementPattern(ParseState *pstate, GraphElementPattern *gep)
gpstate->cur_gep = gep;
+ /* Preserve empty label expression status. */
gep->has_empty_labelexpr = !gep->labelexpr;
gep->labelexpr = transformLabelExpr(gpstate, gep->labelexpr, gep->kind);
+ /*
+ * Add the named element pattern to the namespace, squashing together
+ * properties of element patterns with the same variable name. We need to
+ * do this after the label expression is transformed, since we require
+ * label expression to find the properties associated with variables. We
+ * should do it before transforming the WHERE clause which might have
+ * property references which are resolved using the variables.
+ */
+ if (gep->variable)
+ {
+ foreach_ptr(GraphTableElementVariable, var, gpstate->variables)
+ {
+ if (strcmp(var->name, gep->variable) == 0)
+ {
+ var->properties = list_concat_unique_oid(var->properties, get_labelexpr_properties(gep->labelexpr));
+ }
+ }
+ }
+
gep->whereClause = transformExpr(pstate, gep->whereClause, EXPR_KIND_WHERE);
/*
@@ -457,7 +707,26 @@ transformPathPatternList(ParseState *pstate, List *path_pattern)
foreach_node(GraphElementPattern, gep, path_term)
{
if (gep->variable)
- gpstate->variables = list_append_unique(gpstate->variables, makeString(pstrdup(gep->variable)));
+ {
+ bool found = false;
+
+ foreach_ptr(GraphTableElementVariable, var, gpstate->variables)
+ {
+ if (strcmp(var->name, gep->variable) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ {
+ GraphTableElementVariable *new_var = palloc0_object(GraphTableElementVariable);
+
+ new_var->name = gep->variable;
+ new_var->properties = NIL;
+ gpstate->variables = lappend(gpstate->variables, new_var);
+ }
+ }
}
}
diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index a5b1be77a0a..35404563bc3 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -84,6 +84,8 @@ struct path_element
struct path_factor *path_factor;
Oid elemoid;
Oid reloid;
+ List *elem_label_oids; /* OIDs linking labels from the element
+ * pattern to this element. */
/* Source and destination vertex elements for an edge element. */
Oid srcvertexid;
Oid destvertexid;
@@ -101,7 +103,7 @@ static List *generate_queries_for_path_pattern_recurse(RangeTblEntry *rte, List
static Query *generate_query_for_empty_path_pattern(RangeTblEntry *rte);
static Query *generate_union_from_pathqueries(List **pathqueries);
static List *get_path_elements_for_path_factor(Oid propgraphid, struct path_factor *pf);
-static bool is_property_associated_with_label(Oid labeloid, Oid propoid);
+static List *get_path_elements_from_labelexpr(struct path_factor *pf, Node *labelexpr);
static Node *get_element_property_expr(Oid elemoid, Oid propoid, int rtindex);
/*
@@ -769,7 +771,7 @@ generate_setop_from_pathqueries(List *pathqueries, List **rtable, List **targetl
* function returns NULL.
*/
static struct path_element *
-create_pe_for_element(struct path_factor *pf, Oid elemoid)
+create_pe_for_element(struct path_factor *pf, Oid elemoid, Oid elem_label_oid)
{
HeapTuple eletup = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(elemoid));
Form_pg_propgraph_element pgeform;
@@ -779,16 +781,17 @@ create_pe_for_element(struct path_factor *pf, Oid elemoid)
elog(ERROR, "cache lookup failed for property graph element %u", elemoid);
pgeform = ((Form_pg_propgraph_element) GETSTRUCT(eletup));
- if ((pgeform->pgekind == PGEKIND_VERTEX && pf->kind != VERTEX_PATTERN) ||
- (pgeform->pgekind == PGEKIND_EDGE && !IS_EDGE_PATTERN(pf->kind)))
- {
- ReleaseSysCache(eletup);
- return NULL;
- }
+ /*
+ * We make sure that the type of graph element fits the element pattern
+ * kind when collecting elements in get_propgraph_label_elements() itself.
+ */
+ Assert((pgeform->pgekind == PGEKIND_VERTEX && pf->kind == VERTEX_PATTERN) ||
+ (pgeform->pgekind == PGEKIND_EDGE && IS_EDGE_PATTERN(pf->kind)));
pe = palloc0_object(struct path_element);
pe->path_factor = pf;
pe->elemoid = elemoid;
+ pe->elem_label_oids = list_make1_oid(elem_label_oid);
pe->reloid = pgeform->pgerelid;
/*
@@ -828,152 +831,89 @@ create_pe_for_element(struct path_factor *pf, Oid elemoid)
}
/*
- * Returns the list of OIDs of graph labels which the given label expression
- * resolves to in the given property graph.
+ * Return a list of all the graph elements that satisfy the graph element pattern
+ * represented by the given path_factor `pf`.
+ *
+ * The elements that satisfy the given element pattern are collected by walking
+ * the label expression. We create one path_element object representing every
+ * element whose graph element kind qualifies the element pattern kind. A list of
+ * all such path_element objects is returned.
+ *
+ * get_path_elements_for_path_factor() is the entry point for recursively
+ * walking the label expression. The actual recursion is implemented in
+ * get_path_elements_from_labelexpr().
*/
static List *
-get_labels_for_expr(Oid propgraphid, Node *labelexpr)
+get_path_elements_for_path_factor(Oid propgraphid, struct path_factor *pf)
{
- List *label_oids;
+ return get_path_elements_from_labelexpr(pf, pf->labelexpr);
+}
+static List *
+get_path_elements_from_labelexpr(struct path_factor *pf, Node *labelexpr)
+{
+ List *path_elements;
+
+ /* Empty label expressions should have been tranformed already. */
Assert(labelexpr);
if (IsA(labelexpr, GraphLabelRef))
{
GraphLabelRef *glr = castNode(GraphLabelRef, labelexpr);
+ ListCell *lc_elem;
+ ListCell *lc_el;
- label_oids = list_make1_oid(glr->labelid);
+ path_elements = NIL;
+ forboth(lc_elem, glr->elements, lc_el, glr->elem_labels)
+ path_elements = lappend(path_elements,
+ create_pe_for_element(pf, lfirst_oid(lc_elem), lfirst_oid(lc_el)));
}
else if (IsA(labelexpr, BoolExpr))
{
- BoolExpr *be = castNode(BoolExpr, labelexpr);
+ BoolExpr *be = castNode(BoolExpr, pf->labelexpr);
List *label_exprs = be->args;
- label_oids = NIL;
- foreach_node(GraphLabelRef, glr, label_exprs)
- label_oids = lappend_oid(label_oids, glr->labelid);
- }
- else
- {
/*
- * should not reach here since gram.y will not generate a label
- * expression with other node types.
+ * We only support label disjunction. So we just collect the distinct
+ * elements merging element label OIDs of the elements with same OID.
*/
- elog(ERROR, "unsupported label expression node: %d", (int) nodeTag(labelexpr));
- }
-
- return label_oids;
-}
-
-/*
- * Return a list of all the graph elements that satisfy the graph element pattern
- * represented by the given path_factor `pf`.
- *
- * First we find all the graph labels that satisfy the label expression in path
- * factor. Each label is associated with one or more graph elements. A union of
- * all such elements satisfies the element pattern. We create one path_element
- * object representing every element whose graph element kind qualifies the
- * element pattern kind. A list of all such path_element objects is returned.
- *
- * Note that we need to report an error for an explicitly specified label which
- * is not associated with any graph element of the required kind. So we have to
- * treat each label separately. Without that requirement we could have collected
- * all the unique elements first and then created path_element objects for them
- * to simplify the code.
- */
-static List *
-get_path_elements_for_path_factor(Oid propgraphid, struct path_factor *pf)
-{
- List *label_oids = get_labels_for_expr(propgraphid, pf->labelexpr);
- List *elem_oids_seen = NIL;
- List *pf_elem_oids = NIL;
- List *path_elements = NIL;
- Relation rel;
- SysScanDesc scan;
- ScanKeyData key[1];
- HeapTuple tup;
-
- /*
- * A property graph element can be either a vertex or an edge. Other types
- * of path factors like nested path pattern need to be handled separately
- * when supported.
- */
- Assert(pf->kind == VERTEX_PATTERN || IS_EDGE_PATTERN(pf->kind));
+ Assert(be->boolop == OR_EXPR);
- rel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
- foreach_oid(labeloid, label_oids)
- {
- bool found = false;
-
- ScanKeyInit(&key[0],
- Anum_pg_propgraph_element_label_pgellabelid,
- BTEqualStrategyNumber,
- F_OIDEQ, ObjectIdGetDatum(labeloid));
- scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId, true,
- NULL, 1, key);
- while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ path_elements = NIL;
+ foreach_ptr(Node, label_expr, label_exprs)
{
- Form_pg_propgraph_element_label label_elem = (Form_pg_propgraph_element_label) GETSTRUCT(tup);
- Oid elem_oid = label_elem->pgelelid;
+ List *node_path_elements = get_path_elements_from_labelexpr(pf, label_expr);
- if (!list_member_oid(elem_oids_seen, elem_oid))
+ if (path_elements == NIL)
+ path_elements = node_path_elements;
+ else
{
- /*
- * Create path_element object if the new element qualifies the
- * element pattern kind.
- */
- struct path_element *pe = create_pe_for_element(pf, elem_oid);
-
- if (pe)
+ foreach_ptr(struct path_element, npe, node_path_elements)
{
- path_elements = lappend(path_elements, pe);
-
- /* Remember qualified elements. */
- pf_elem_oids = lappend_oid(pf_elem_oids, elem_oid);
- found = true;
+ struct path_element *found = NULL;
+
+ foreach_ptr(struct path_element, pe, path_elements)
+ {
+ if (npe->elemoid == pe->elemoid)
+ {
+ pe->elem_label_oids = list_concat(pe->elem_label_oids,
+ npe->elem_label_oids);
+ found = pe;
+ break;
+ }
+ }
+
+ if (!found)
+ path_elements = lappend(path_elements, npe);
}
-
- /*
- * Remember qualified and unqualified elements processed so
- * far to avoid processing already processed elements again.
- */
- elem_oids_seen = lappend_oid(elem_oids_seen, label_elem->pgelelid);
- }
- else if (list_member_oid(pf_elem_oids, elem_oid))
- {
- /*
- * The graph element is known to qualify the given element
- * pattern. Flag that the current label has at least one
- * qualified element associated with it.
- */
- found = true;
}
}
-
- if (!found)
- {
- /*
- * We did not find any qualified element associated with this
- * label. Throw an error.
- *
- * An empty label expression is replaced by all labels that are
- * associated with at least one element of the required kind. We
- * should not reach here in that case.
- */
- Assert(!pf->has_empty_labelexpr);
-
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("no property graph element of type \"%s\" has label \"%s\" associated with it in property graph \"%s\"",
- pf->kind == VERTEX_PATTERN ? "vertex" : "edge",
- get_propgraph_label_name(labeloid),
- get_rel_name(propgraphid))));
- }
-
- systable_endscan(scan);
}
- table_close(rel, AccessShareLock);
- pf->labeloids = label_oids;
+ else
+ {
+ path_elements = NIL; /* Keep compiler quiet */
+ elog(ERROR, "unsupported label expression node: %d", (int) nodeTag(pf->labelexpr));
+ }
return path_elements;
}
@@ -1013,7 +953,6 @@ replace_property_refs_mutator(Node *node, struct replace_property_refs_context *
Node *n = NULL;
struct path_element *found_mapping = NULL;
struct path_factor *mapping_factor = NULL;
- List *unrelated_labels = NIL;
foreach_ptr(struct path_element, m, context->mappings)
{
@@ -1034,16 +973,11 @@ replace_property_refs_mutator(Node *node, struct replace_property_refs_context *
mapping_factor = found_mapping->path_factor;
/*
- * Find property definition for given element through any of the
+ * Find property definition for the given element through any of the
* associated labels qualifying the given element pattern.
*/
- foreach_oid(labeloid, mapping_factor->labeloids)
+ foreach_oid(elem_labelid, found_mapping->elem_label_oids)
{
- Oid elem_labelid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL,
- Anum_pg_propgraph_element_label_oid,
- ObjectIdGetDatum(found_mapping->elemoid),
- ObjectIdGetDatum(labeloid));
-
if (OidIsValid(elem_labelid))
{
HeapTuple tup = SearchSysCache2(PROPGRAPHLABELPROP, ObjectIdGetDatum(elem_labelid),
@@ -1065,56 +999,26 @@ replace_property_refs_mutator(Node *node, struct replace_property_refs_context *
ReleaseSysCache(tup);
}
- else
- {
- /*
- * Label is not associated with the element but it may be
- * associated with the property through some other element.
- * Save it for later use.
- */
- unrelated_labels = lappend_oid(unrelated_labels, labeloid);
- }
}
/* See if we can resolve the property in some other way. */
if (!n)
{
- bool prop_associated = false;
-
- foreach_oid(loid, unrelated_labels)
- {
- if (is_property_associated_with_label(loid, gpr->propid))
- {
- prop_associated = true;
- break;
- }
- }
-
- if (prop_associated)
- {
- /*
- * The property is associated with at least one of the labels
- * that satisfy given element pattern. If it's associated with
- * the given element (through some other label), use
- * corresponding value expression. Otherwise NULL. Ref.
- * SQL/PGQ standard section 6.5 Property Reference, General
- * Rule 2.b.
- */
- n = get_element_property_expr(found_mapping->elemoid, gpr->propid,
- mapping_factor->factorpos + 1);
-
- if (!n)
- n = (Node *) makeNullConst(gpr->typeId, gpr->typmod, gpr->collation);
- }
+ /*
+ * The property is associated with at least one of the labels that
+ * satisfy given element pattern. If it's associated with the
+ * given element (through some other label), use correspondig
+ * value expression. Otherwise NULL. Ref. SQL/PGQ standard section
+ * 6.5 Property Reference, General Rule 2.b.
+ */
+ n = get_element_property_expr(found_mapping->elemoid, gpr->propid,
+ mapping_factor->factorpos + 1);
+ if (!n)
+ n = (Node *) makeNullConst(gpr->typeId, gpr->typmod, gpr->collation);
}
- if (!n)
- ereport(ERROR,
- errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("property \"%s\" for element variable \"%s\" not found",
- get_propgraph_property_name(gpr->propid), mapping_factor->variable));
-
+ Assert(n);
return n;
}
@@ -1229,43 +1133,6 @@ build_edge_vertex_link_quals(HeapTuple edgetup, int edgerti, int refrti, Oid ref
return quals;
}
-/*
- * Check if the given property is associated with the given label.
- *
- * A label projects the same set of properties through every element it is
- * associated with. Find any of the elements and return true if that element is
- * associated with the given property. False otherwise.
- */
-static bool
-is_property_associated_with_label(Oid labeloid, Oid propoid)
-{
- Relation rel;
- SysScanDesc scan;
- ScanKeyData key[1];
- HeapTuple tup;
- bool associated = false;
-
- rel = table_open(PropgraphElementLabelRelationId, RowShareLock);
- ScanKeyInit(&key[0],
- Anum_pg_propgraph_element_label_pgellabelid,
- BTEqualStrategyNumber,
- F_OIDEQ, ObjectIdGetDatum(labeloid));
- scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId,
- true, NULL, 1, key);
-
- if (HeapTupleIsValid(tup = systable_getnext(scan)))
- {
- Form_pg_propgraph_element_label ele_label = (Form_pg_propgraph_element_label) GETSTRUCT(tup);
-
- associated = SearchSysCacheExists2(PROPGRAPHLABELPROP,
- ObjectIdGetDatum(ele_label->oid), ObjectIdGetDatum(propoid));
- }
- systable_endscan(scan);
- table_close(rel, RowShareLock);
-
- return associated;
-}
-
/*
* If given element has the given property associated with it, through any of
* the associated labels, return value expression of the property. Otherwise
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b98a0758a7b..2e43e983b5f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1052,9 +1052,9 @@ typedef struct GraphElementPattern
const char *variable;
/*
- * If no label expression is specified, we will replace it with a non-NULL
- * expression in transformLabelExpr(). This flag indicates whether the
- * label expression was originally empty.
+ * An empty label expression gets replaced by disjunction of all labels
+ * during transformation. But we need to remember if it was originally
+ * empty for various purposes.
*/
bool has_empty_labelexpr;
Node *labelexpr;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 44f828cbb37..e4006028a9a 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -2187,6 +2187,9 @@ typedef struct GraphLabelRef
{
NodeTag type;
Oid labelid;
+ List *properties pg_node_attr(equal_ignore, query_jumble_ignore);
+ List *elements pg_node_attr(equal_ignore, query_jumble_ignore);
+ List *elem_labels pg_node_attr(equal_ignore, query_jumble_ignore);
ParseLoc location;
} GraphLabelRef;
diff --git a/src/include/parser/parse_graphtable.h b/src/include/parser/parse_graphtable.h
index e52e21512aa..c32abffece1 100644
--- a/src/include/parser/parse_graphtable.h
+++ b/src/include/parser/parse_graphtable.h
@@ -18,7 +18,7 @@
#include "parser/parse_node.h"
extern Node *transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref);
-
+extern Node *transformGraphTableAllPropRef(ParseState *pstate, Node *node, bool *is_all_props_ref);
extern Node *transformGraphPattern(ParseState *pstate, GraphPattern *graph_pattern);
#endif /* PARSE_GRAPHTABLE_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7f4ba6c2a8..0a3340f3d60 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -106,6 +106,12 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
* patterns are transformed. This namespace is used to resolve label and property
* references in the GRAPH_TABLE.
*/
+typedef struct GraphTableElementVariable
+{
+ const char *name;
+ List *properties;
+} GraphTableElementVariable;
+
typedef struct GraphTableParseState
{
Oid graphid; /* OID of the graph being referenced */
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
index 4ab5700ad54..082b509f95d 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -235,7 +235,7 @@ SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders | c
-- property not associated with labels queried results in error
SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders | customer_wishlists ]->(l IS orders | wishlists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name AS product_name, l.list_type)) ORDER BY 1, 2, 3;
-ERROR: property "list_type" for element variable "l" not found
+ERROR: property "list_type" is not available for element variable "l"
-- vertex to vertex connection abbreviation
SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)->(o IS orders) COLUMNS (c.name, o.ordered_when)) ORDER BY 1;
name | ordered_when
@@ -244,6 +244,13 @@ SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)->(o IS orders) COLUMNS
customer2 | 01-02-2024
(2 rows)
+-- all properties reference
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.*));
+ name | customer_id | address
+-----------+-------------+---------
+ customer1 | 1 | US
+(1 row)
+
-- lateral test
-- Use table with a column name same as a property in the property graph so as
-- to test resolution preferences. Property references are preferred over
@@ -449,24 +456,19 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (v1 IS vl2)-(v2) COLUMNS (v1.vname AS v1name
-- Errors
-- vl1 is not associated with property vprop2
SELECT src, src_vprop2, conn, dest FROM GRAPH_TABLE (g1 MATCH (a IS vl1)-[b IS el1]->(c IS vl2 | vl3) COLUMNS (a.vname AS src, a.vprop2 AS src_vprop2, b.ename AS conn, c.vname AS dest));
-ERROR: property "vprop2" for element variable "a" not found
+ERROR: property "vprop2" is not available for element variable "a"
-- property ename is associated with edge labels but not with a vertex label
SELECT * FROM GRAPH_TABLE (g1 MATCH (src)-[conn]->(dest) COLUMNS (src.vname AS svname, src.ename AS sename));
-ERROR: property "ename" for element variable "src" not found
+ERROR: property "ename" is not available for element variable "src"
-- vname is associated vertex labels but not with an edge label
SELECT * FROM GRAPH_TABLE (g1 MATCH (src)-[conn]->(dest) COLUMNS (conn.vname AS cvname, conn.ename AS cename));
-ERROR: property "vname" for element variable "conn" not found
+ERROR: property "vname" is not available for element variable "conn"
-- el1 is associated with only edges, and cannot qualify a vertex
SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS el1)-[conn]->(dest) COLUMNS (conn.ename AS cename));
ERROR: no property graph element of type "vertex" has label "el1" associated with it in property graph "g1"
SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS el1 | vl1)-[conn]->(dest) COLUMNS (conn.ename AS cename));
ERROR: no property graph element of type "vertex" has label "el1" associated with it in property graph "g1"
--- star in COLUMNs is specified but not supported
-SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.*));
-ERROR: "*" is not supported here
-LINE 1: ... = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.*));
- ^
--- star anywhere else is not allowed as a property reference
+-- all properties reference is not allowed outside COLUMNs
SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.* IS NOT NULL)-[IS customer_orders]->(o IS orders) COLUMNS (c.name));
ERROR: "*" not allowed here
LINE 1: ...M GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.* IS NOT...
@@ -521,6 +523,22 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS vl1 | vl2 | vl3)-[conn]->(dest) COLU
v22 | e231 | v32
(5 rows)
+-- all properties reference with label disjunction
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS vl1 | vl2 WHERE src.vprop1 = 10 OR src.vprop1 = 1020) COLUMNS (src.*));
+ vname | vprop1 | vprop2 | lprop1
+-------+--------+--------+----------
+ v11 | 10 | |
+ v22 | 1020 | 1200 | vl2_prop
+(2 rows)
+
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src WHERE src.vprop1 = 10 OR src.vprop1 = 1020 OR src.vprop1 = 2030) COLUMNS (src.*));
+ elname | vname | vprop1 | vprop2 | lprop1
+--------+-------+--------+--------+----------
+ v11 | v11 | 10 | |
+ v22 | v22 | 1020 | 1200 | vl2_prop
+ v33 | v33 | 2030 | | vl3_prop
+(3 rows)
+
-- graph'ical query: find a vertex which is not connected to any other vertex as a source or a destination.
WITH all_connected_vertices AS (SELECT svn, dvn FROM GRAPH_TABLE (g1 MATCH (src)-[conn]->(dest) COLUMNS (src.vname AS svn, dest.vname AS dvn))),
all_vertices AS (SELECT vn FROM GRAPH_TABLE (g1 MATCH (vertex) COLUMNS (vertex.vname AS vn)))
@@ -626,7 +644,7 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (a WHERE a.vprop1 between 20 and 2000)->(b W
(4 rows)
-- labels and elements kinds of element patterns with the same variable name
-SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS l1)-[a IS l1]->(b IS l1) COLUMNS (a.ename AS aename, b.ename AS bename)) ORDER BY 1, 2; -- error
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS l1)-[a IS l1]->(b IS l1) COLUMNS (a.elname AS aename, b.elname AS bename)) ORDER BY 1, 2; -- error
ERROR: element patterns with same variable name "a" but different element pattern types
SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1)->(b)->(a IS vl2) WHERE a.vname <> b.vname COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through; -- error
ERROR: element patterns with same variable name "a" but different label expressions are not supported
@@ -855,7 +873,7 @@ EXECUTE loopstmt;
ALTER PROPERTY GRAPH g1 ALTER EDGE TABLE e3_3 ALTER LABEL l2 DROP PROPERTIES (elname);
EXECUTE loopstmt; -- error
-ERROR: property "elname" for element variable "e" not found
+ERROR: property "elname" is not available for element variable "e"
ALTER PROPERTY GRAPH g1 ALTER EDGE TABLE e3_3 ALTER LABEL l2 ADD PROPERTIES ((ename || '_new')::varchar(10) AS elname);
EXECUTE loopstmt;
loop
@@ -1019,7 +1037,19 @@ ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v1 DROP LABEL l1;
ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v2 DROP LABEL l1;
ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v3 DROP LABEL l1;
SELECT * FROM v_shared_label;
-ERROR: no property graph element of type "vertex" has label "l1" associated with it in property graph "g1"
+ elname
+--------
+
+
+
+
+
+
+
+
+
+(9 rows)
+
ROLLBACK;
-- ruleutils reverse parsing
SELECT pg_get_viewdef('customers_us'::regclass);
@@ -1048,6 +1078,12 @@ SELECT pg_get_viewdef('v_shared_label'::regclass);
FROM GRAPH_TABLE (g1 MATCH (v IS l1) COLUMNS (v.elname AS elname));
(1 row)
+-- property graph with no properties and empty all properties reference
+CREATE PROPERTY GRAPH gnoprop VERTEX TABLES (v1 NO PROPERTIES);
+SELECT * FROM GRAPH_TABLE (gnoprop MATCH (a IS v1) COLUMNS (a.*));
+--
+(3 rows)
+
-- test view/graph nesting
CREATE VIEW customers_view AS SELECT customer_id, 'redacted' || customer_id AS name_redacted, address FROM customers;
SELECT * FROM customers;
diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql
index daf99f484a8..e37ce9a4d3d 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -151,6 +151,8 @@ SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders | c
SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders | customer_wishlists ]->(l IS orders | wishlists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name AS product_name, l.list_type)) ORDER BY 1, 2, 3;
-- vertex to vertex connection abbreviation
SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)->(o IS orders) COLUMNS (c.name, o.ordered_when)) ORDER BY 1;
+-- all properties reference
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.*));
-- lateral test
-- Use table with a column name same as a property in the property graph so as
@@ -302,9 +304,7 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (src)-[conn]->(dest) COLUMNS (conn.vname AS
-- el1 is associated with only edges, and cannot qualify a vertex
SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS el1)-[conn]->(dest) COLUMNS (conn.ename AS cename));
SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS el1 | vl1)-[conn]->(dest) COLUMNS (conn.ename AS cename));
--- star in COLUMNs is specified but not supported
-SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.*));
--- star anywhere else is not allowed as a property reference
+-- all properties reference is not allowed outside COLUMNs
SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.* IS NOT NULL)-[IS customer_orders]->(o IS orders) COLUMNS (c.name));
-- consecutive element patterns with same kind
SELECT * FROM GRAPH_TABLE (g1 MATCH ()() COLUMNS (1 as one));
@@ -322,6 +322,9 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (WHERE b.eprop1 = 10001)-[b]->(c) COLUMNS (b
SELECT * FROM GRAPH_TABLE (g1 MATCH (src)-[conn]->(dest) COLUMNS (src.vname AS svname, conn.ename AS cename, dest.vname AS dvname, src.vprop1 AS svp1, src.vprop2 AS svp2, src.lprop1 AS slp1, dest.vprop1 AS dvp1, dest.vprop2 AS dvp2, dest.lprop1 AS dlp1, conn.eprop1 AS cep1, conn.lprop2 AS clp2));
-- three label disjunction
SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS vl1 | vl2 | vl3)-[conn]->(dest) COLUMNS (src.vname AS svname, conn.ename AS cename, dest.vname AS dvname));
+-- all properties reference with label disjunction
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS vl1 | vl2 WHERE src.vprop1 = 10 OR src.vprop1 = 1020) COLUMNS (src.*));
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src WHERE src.vprop1 = 10 OR src.vprop1 = 1020 OR src.vprop1 = 2030) COLUMNS (src.*));
-- graph'ical query: find a vertex which is not connected to any other vertex as a source or a destination.
WITH all_connected_vertices AS (SELECT svn, dvn FROM GRAPH_TABLE (g1 MATCH (src)-[conn]->(dest) COLUMNS (src.vname AS svn, dest.vname AS dvn))),
all_vertices AS (SELECT vn FROM GRAPH_TABLE (g1 MATCH (vertex) COLUMNS (vertex.vname AS vn)))
@@ -374,7 +377,7 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (a WHERE a.vprop1 < 2000)->(b WHERE b.vprop1
SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b WHERE b.vprop1 > 20)->(a WHERE a.vprop1 between 20 and 2000) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
SELECT * FROM GRAPH_TABLE (g1 MATCH (a WHERE a.vprop1 between 20 and 2000)->(b WHERE b.vprop1 > 20)->(a WHERE a.vprop1 between 20 and 2000) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
-- labels and elements kinds of element patterns with the same variable name
-SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS l1)-[a IS l1]->(b IS l1) COLUMNS (a.ename AS aename, b.ename AS bename)) ORDER BY 1, 2; -- error
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS l1)-[a IS l1]->(b IS l1) COLUMNS (a.elname AS aename, b.elname AS bename)) ORDER BY 1, 2; -- error
SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1)->(b)->(a IS vl2) WHERE a.vname <> b.vname COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through; -- error
SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1)->(b)->(a) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(a IS vl1) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
@@ -583,6 +586,10 @@ SELECT pg_get_viewdef('customers_us'::regclass);
SELECT pg_get_viewdef('v_empty_label'::regclass);
SELECT pg_get_viewdef('v_shared_label'::regclass);
+-- property graph with no properties and empty all properties reference
+CREATE PROPERTY GRAPH gnoprop VERTEX TABLES (v1 NO PROPERTIES);
+SELECT * FROM GRAPH_TABLE (gnoprop MATCH (a IS v1) COLUMNS (a.*));
+
-- test view/graph nesting
CREATE VIEW customers_view AS SELECT customer_id, 'redacted' || customer_id AS name_redacted, address FROM customers;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 85d989f395d..028feeffa76 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1174,6 +1174,7 @@ GraphElementPatternKind
GraphLabelRef
GraphPattern
GraphPropertyRef
+GraphTableElementVariable
GraphTableParseState
Group
GroupByColInfo
--
2.34.1
From 25ad594af5669a37071e3d3a4a1e50c0000bb6ed Mon Sep 17 00:00:00 2001
From: AyoubKAZ <[email protected]>
Date: Fri, 31 Jul 2026 13:12:36 +0200
Subject: [PATCH v1 2/4] View referencing labels shared by vertex and edge
tables
While at it add a test for view containing labels which are shared by
both vertex and edge tables. When such a label is dropped from only
vertex tables or only edge tables, the view may be rendered invalid
because properties only associated with that label can not be resolved.
The fix will need to wait for the SQL/PGQ standard to specify the
behaviour in such a case.
Author: Ashutosh Bapat <[email protected]>
---
src/test/regress/expected/graph_table.out | 20 ++++++++++++++++++++
src/test/regress/sql/graph_table.sql | 13 +++++++++++++
2 files changed, 33 insertions(+)
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
index 97ff6899ba4..4ab5700ad54 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -1008,6 +1008,19 @@ ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v2 DROP LABEL vl2; -- error
ERROR: cannot drop label vl2 of property graph g1 because other objects depend on it
DETAIL: view v_empty_label depends on label vl2 of property graph g1
HINT: Use DROP ... CASCADE to drop the dependent objects too.
+-- l1 is shared by all vertex tables and edge tables. Dropping it from all
+-- vertex tables only renders a view unusable. This is because the standard
+-- differentiates between a vertex label and an edge label even though they
+-- share the same name. Waiting for the standard to clarify the expected
+-- behavior in this case.
+CREATE VIEW v_shared_label AS SELECT * FROM GRAPH_TABLE (g1 MATCH (v IS l1) COLUMNS (v.elname));
+BEGIN;
+ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v1 DROP LABEL l1;
+ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v2 DROP LABEL l1;
+ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v3 DROP LABEL l1;
+SELECT * FROM v_shared_label;
+ERROR: no property graph element of type "vertex" has label "l1" associated with it in property graph "g1"
+ROLLBACK;
-- ruleutils reverse parsing
SELECT pg_get_viewdef('customers_us'::regclass);
pg_get_viewdef
@@ -1028,6 +1041,13 @@ SELECT pg_get_viewdef('v_empty_label'::regclass);
FROM GRAPH_TABLE (g1 MATCH (v IS l1|vl1|vl2|vl3 WHERE (v.vprop1 = 10)) COLUMNS (v.elname AS elname));
(1 row)
+SELECT pg_get_viewdef('v_shared_label'::regclass);
+ pg_get_viewdef
+------------------------------------------------------------------------
+ SELECT elname +
+ FROM GRAPH_TABLE (g1 MATCH (v IS l1) COLUMNS (v.elname AS elname));
+(1 row)
+
-- test view/graph nesting
CREATE VIEW customers_view AS SELECT customer_id, 'redacted' || customer_id AS name_redacted, address FROM customers;
SELECT * FROM customers;
diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql
index 0afcb94eee8..daf99f484a8 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -566,9 +566,22 @@ ALTER PROPERTY GRAPH myshop ALTER VERTEX TABLE products
CREATE VIEW v_empty_label AS SELECT * FROM GRAPH_TABLE (g1 MATCH (v WHERE v.vprop1 = 10) COLUMNS (v.elname));
ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v1 DROP LABEL vl1; -- error
ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v2 DROP LABEL vl2; -- error
+-- l1 is shared by all vertex tables and edge tables. Dropping it from all
+-- vertex tables only renders a view unusable. This is because the standard
+-- differentiates between a vertex label and an edge label even though they
+-- share the same name. Waiting for the standard to clarify the expected
+-- behavior in this case.
+CREATE VIEW v_shared_label AS SELECT * FROM GRAPH_TABLE (g1 MATCH (v IS l1) COLUMNS (v.elname));
+BEGIN;
+ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v1 DROP LABEL l1;
+ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v2 DROP LABEL l1;
+ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v3 DROP LABEL l1;
+SELECT * FROM v_shared_label;
+ROLLBACK;
-- ruleutils reverse parsing
SELECT pg_get_viewdef('customers_us'::regclass);
SELECT pg_get_viewdef('v_empty_label'::regclass);
+SELECT pg_get_viewdef('v_shared_label'::regclass);
-- test view/graph nesting
--
2.34.1