Subject: SQL/PGQ: All properties reference

From: Ashutosh Bapat <[email-masked]>
Date: Wed, 18 Mar 2026 15:30:11 +0530

Hi Peter and Pgsql-Hackers,

I am starting a new thread to discuss all properties reference feature
which was not committed with the main patch. [1]

A <variable>.* is called all properties reference and it is allowed
only in COLUMNs clause. Interpreting subclause 9.2 and 9.3 together,
it expands to a list of graph property references <variable>.p1, ...
<variable>.pn where p1, ..., pn are the properties of the labels which
satisfy the label expression in the element pattern identified by
<variable>. The graph property references are added to the COLUMNs
clause in place of the all property reference, just like how <table>.*
expands in SELECT's targetlist.

In the current implementation, we delay resolving graph property
references (<variable>.<property>) till the time query is generated
(generate_query_for_graph_path()). If we delay the all properties
reference till that time, we can not determine the data types and
names of the columns in the COLUMNs list. So we need to do that when
the COLUMNs clause is resolved. This means that the properties
associated with the labels needs to be resolved earlier. Since the
properties are not associated with labels directly but through the
elements, we need to find at least one element for every label in the
label expression. In brief, all the namespace resolution need to
happen before we transform COLUMNs clause. The patch rearranges the
code that way.

I like the resultant code since a. it handles errors more gracefully
and can provide error location as well b. It avoids repeated property
and element label lookups c. the code seems closer to how we create
namespaces for table references. Flip side I think it fetches the
properties which may or may not be needed by the query, similar to how
we compute the tuple descriptor of a table referenced in the query
even though we don't need all the columns. But overall I think it's
better code as well.

There are some things that still need work as below

o. Order of properties in all properties reference
----------------------------------------------------------------
The standard (subclause 9.3) mentions this as implementation
dependent. Since properties are associated with labels which are a
logical entity, I don't think we need to define property numbers like
attribute numbers. Natural order is ordering by the property names
(even across the labels). Do we have any other option?

o. Difference from Oracle
----------------------------------
Consider following query from the patch (please refer the
graph_table.sql for details)
SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS vl1 | vl2 WHERE src.vprop1
= 10 OR src.vprop1 = 1020) COLUMNS (src.*));

The element pattern has only two labels vl1 and vl2 in it. If I
understand subclause 9.2 and 9.3 correctly, only the properties of the
labels that appear in the label expression should be part of all
properties reference. The expected output in the patch is based on
this interpretation - which has columns vname | vprop1 | vprop2 |
lprop1 . However, Oracle includes elname as well which is not
associated with vl1 or vl2. I think, Oracle's output is wrong. But
maybe I am misinterpreting those clauses. Peter, what do you think?

o. pg_node_attrs for new fields
-----------------------------------------
Need to think about pg_node_attrs for the new members of GraphLabelRef
added in patch. Possibly the current annotation is right, but need to
check again.

o. Need to document what a <variable>.* will result into

Planning to work on it after the main patch is somewhat stable.

[1] postgr.es/m/[email-masked]

-- 
Best Wishes,
Ashutosh Bapat
From d8ad0e96e0d15f16e55a9a3f964e376a57f73587 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Wed, 11 Mar 2026 08:53:41 +0530
Subject: [PATCH v20260318] 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-masked]>
Reported by: Henson Choi, Junwang Zhao
---
 src/backend/parser/parse_clause.c         |  61 ++-
 src/backend/parser/parse_graphtable.c     | 453 +++++++++++++++++++---
 src/backend/rewrite/rewriteGraphTable.c   | 352 +++++------------
 src/backend/utils/adt/ruleutils.c         |   2 +-
 src/include/nodes/parsenodes.h            |   7 +
 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 |  48 ++-
 src/test/regress/sql/graph_table.sql      |  15 +-
 src/tools/pgindent/typedefs.list          |   1 +
 11 files changed, 606 insertions(+), 344 deletions(-)

diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 967eea44f1c..cfdede94db7 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -978,29 +978,60 @@ transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt)
 		Node	   *colexpr;
 		TargetEntry *te;
 		char	   *colname;
+		bool		is_all_props_ref;
 
-		colexpr = transformExpr(pstate, rt->val, EXPR_KIND_SELECT_TARGET);
+		/*
+		 * 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);
 
-		if (rt->name)
-			colname = rt->name;
+				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);
+		}
 	}
 
 	table_close(rel, NoLock);
diff --git a/src/backend/parser/parse_graphtable.c b/src/backend/parser/parse_graphtable.c
index bf805b4beb6..49a8d8dae42 100644
--- a/src/backend/parser/parse_graphtable.c
+++ b/src/backend/parser/parse_graphtable.c
@@ -18,7 +18,10 @@
 #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_label_property.h"
 #include "catalog/pg_propgraph_property.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -32,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.
@@ -61,19 +227,103 @@ get_gep_kind_name(GraphElementPatternKind gepkind)
 	return "unknown";
 }
 
+/*
+ * Transform a <variable>.* expression.
+ *
+ * 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 *
+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 = strVal(field1);
+
+		/* Find the variable in the graph pattern variables */
+		foreach_ptr(GraphTableElementVariable, var, gpstate->variables)
+		{
+			if (strcmp(var->name, elvarname) == 0)
+			{
+				GraphPropertyRef *gpr;
+				HeapTuple	pgptup;
+				Form_pg_propgraph_property pgpform;
+
+				if (IsA(field2, A_Star))
+				{
+					List	   *propref_list = NIL;
+
+					*is_all_props_ref = true;
+
+					foreach_oid(propid, var->properties)
+					{
+						gpr = makeNode(GraphPropertyRef);
+						pgptup = SearchSysCache1(PROPGRAPHPROPOID, ObjectIdGetDatum(propid));
+
+						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 = propid;
+						gpr->typeId = pgpform->pgptypid;
+						gpr->typmod = pgpform->pgptypmod;
+						gpr->collation = pgpform->pgpcollation;
+
+						ReleaseSysCache(pgptup);
+						propref_list = lappend(propref_list, gpr);
+					}
+
+					return (Node *) propref_list;
+				}
+			}
+		}
+	}
+
+	return NULL;
+}
+
 /*
  * 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
+ * <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.
+ * 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.
  */
 Node *
 transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref)
@@ -92,45 +342,54 @@ transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref)
 
 		if (IsA(field1, A_Star) || IsA(field2, A_Star))
 		{
-			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));
+			ereport(ERROR,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("\"*\" not allowed here"),
+					parser_errposition(pstate, cref->location));
 		}
 
 		elvarname = strVal(field1);
 		propname = strVal(field2);
 
-		if (list_member(gpstate->variables, field1))
+		/* Find the variable in the graph pattern variables */
+		foreach_ptr(GraphTableElementVariable, var, gpstate->variables)
 		{
-			GraphPropertyRef *gpr = makeNode(GraphPropertyRef);
-			HeapTuple	pgptup;
-			Form_pg_propgraph_property pgpform;
-
-			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);
-
-			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;
+			if (strcmp(var->name, elvarname) == 0)
+			{
+				GraphPropertyRef *gpr = makeNode(GraphPropertyRef);
+				HeapTuple	pgptup;
+				Form_pg_propgraph_property pgpform;
+
+				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;
+			}
 		}
+
+		/*
+		 * No element with the given name found. Can't resolve the given
+		 * ColumnRef as a graph property reference.
+		 */
 	}
 
 	return NULL;
@@ -146,16 +405,65 @@ 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;
 
+	check_stack_depth();
+
 	if (labelexpr == NULL)
-		return NULL;
+	{
+		/* Empty label expression, transform into a disjunction of labels */
+		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);
+			List	   *elements;
+			List	   *elem_labels;
+			GraphLabelRef *lref;
+
+			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);
+		}
+		systable_endscan(scan);
+		table_close(rel, AccessShareLock);
 
-	check_stack_depth();
+		result = (Node *) makeBoolExpr(OR_EXPR, args, -1);
+		return result;
+	}
 
 	switch (nodeTag(labelexpr))
 	{
@@ -179,6 +487,15 @@ transformLabelExpr(GraphTableParseState *gpstate, Node *labelexpr)
 				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;
 			}
@@ -186,14 +503,11 @@ transformLabelExpr(GraphTableParseState *gpstate, Node *labelexpr)
 		case T_BoolExpr:
 			{
 				BoolExpr   *be = (BoolExpr *) labelexpr;
-				ListCell   *lc;
 				List	   *args = NIL;
 
-				foreach(lc, be->args)
+				foreach_ptr(Node, arg, be->args)
 				{
-					Node	   *arg = (Node *) lfirst(lc);
-
-					arg = transformLabelExpr(gpstate, arg);
+					arg = transformLabelExpr(gpstate, arg, gepkind);
 					args = lappend(args, arg);
 				}
 
@@ -215,10 +529,11 @@ transformLabelExpr(GraphTableParseState *gpstate, Node *labelexpr)
  * 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)
@@ -235,10 +550,40 @@ transformGraphElementPattern(ParseState *pstate, GraphElementPattern *gep)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("element pattern quantifier is not supported")));
 
+	/* 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)
-		gpstate->variables = lappend(gpstate->variables, makeString(pstrdup(gep->variable)));
+	{
+		bool		add_new_variable = true;
 
-	gep->labelexpr = transformLabelExpr(gpstate, gep->labelexpr);
+		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));
+				add_new_variable = false;
+			}
+		}
+
+		if (add_new_variable)
+		{
+			GraphTableElementVariable *new_var = palloc0_object(GraphTableElementVariable);
+
+			new_var->name = gep->variable;
+			new_var->properties = get_labelexpr_properties(gep->labelexpr);
+			gpstate->variables = lappend(gpstate->variables, new_var);
+		}
+	}
 
 	gep->whereClause = transformExpr(pstate, gep->whereClause, EXPR_KIND_WHERE);
 	assign_expr_collations(pstate, gep->whereClause);
diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index 06f2f3442d8..acebd7623fc 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -59,12 +59,12 @@ struct path_factor
 	GraphElementPatternKind kind;
 	const char *variable;
 	Node	   *labelexpr;
+	bool		has_empty_labelexpr;	/* Copied from the source
+										 * GraphElementPattern */
 	Node	   *whereClause;
 	int			factorpos;		/* Position of this path factor in the list of
 								 * path factors representing a given path
 								 * pattern. */
-	List	   *labeloids;		/* OIDs of all the labels referenced in
-								 * labelexpr. */
 	/* Links to adjacent vertex path factors if this is an edge path factor. */
 	struct path_factor *src_pf;
 	struct path_factor *dest_pf;
@@ -82,6 +82,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;
@@ -98,8 +100,8 @@ static Node *generate_setop_from_pathqueries(List *pathqueries, List **rtable, L
 static List *generate_queries_for_path_pattern_recurse(RangeTblEntry *rte, List *pathqueries, List *cur_path, List *path_pattern_lists, int elempos);
 static Query *generate_query_for_empty_path_pattern(RangeTblEntry *rte);
 static Query *generate_union_from_pathqueries(List **pathqueries);
+static List *get_path_elements_from_labelexpr(struct path_factor *pf, Node *labelexpr);
 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 Node *get_element_property_expr(Oid elemoid, Oid propoid, int rtindex);
 
 /*
@@ -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",
@@ -252,6 +257,7 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
 				pf->factorpos = factorpos++;
 				pf->kind = gep->kind;
 				pf->labelexpr = gep->labelexpr;
+				pf->has_empty_labelexpr = gep->has_empty_labelexpr;
 				pf->variable = gep->variable;
 				pf->whereClause = gep->whereClause;
 
@@ -738,11 +744,13 @@ generate_setop_from_pathqueries(List *pathqueries, List **rtable, List **targetl
  * Construct a path_element object for the graph element given by `elemoid`
  * statisfied by the path factor `pf`.
  *
- * If the type of graph element does not fit the element pattern kind, the
- * function returns NULL.
+ * elem_label_oid is the OID linking the given element with one of the labels in
+ * the label expression of the given path factor. OIDs linking the same element
+ * with other labels in the label expression are added as we examine all the
+ * labels in the label expression in the given path factor.
  */
 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;
@@ -752,16 +760,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;
 
 	/*
@@ -801,188 +810,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);
+}
 
-	if (!labelexpr)
-	{
-		Relation	rel;
-		SysScanDesc scan;
-		ScanKeyData key[1];
-		HeapTuple	tup;
+static List *
+get_path_elements_from_labelexpr(struct path_factor *pf, Node *labelexpr)
+{
+	List	   *path_elements;
 
-		/*
-		 * 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);
+	/* Empty label expressions should have been tranformed already. */
+	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);
+		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;
-}
+		Assert(be->boolop == OR_EXPR);
 
-/*
- * 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;
-	List	   *unresolved_labels = 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));
-
-	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);
 				}
-
-				/*
-				 * Rememeber 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. 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.
-			 */
-			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))));
 		}
-
-		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);
+	else
+	{
+		path_elements = NIL;	/* Keep compiler quiet */
+		elog(ERROR, "unsupported label expression node: %d", (int) nodeTag(pf->labelexpr));
+	}
 
 	return path_elements;
 }
@@ -1022,7 +932,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)
 		{
@@ -1043,16 +952,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),
@@ -1074,55 +978,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
-				 * 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);
-			}
+			/*
+			 * 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;
 	}
 
@@ -1237,43 +1112,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/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 7bc12589e40..c4962e1677a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8053,7 +8053,7 @@ get_path_pattern_expr_def(List *path_pattern_expr, deparse_context *context)
 			sep = " ";
 		}
 
-		if (gep->labelexpr)
+		if (!gep->has_empty_labelexpr)
 		{
 			appendStringInfoString(buf, sep);
 			appendStringInfoString(buf, "IS ");
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ffadd667167..71118f3e346 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1045,7 +1045,14 @@ typedef struct GraphElementPattern
 	NodeTag		type;
 	GraphElementPatternKind kind;
 	const char *variable;
+
+	/*
+	 * 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.
+	 */
 	Node	   *labelexpr;
+	bool		has_empty_labelexpr;
 	List	   *subexpr;
 	Node	   *whereClause;
 	List	   *quantifier;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index b67e56e6c5a..36a1004009f 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -2186,6 +2186,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 fc2cbeb2083..bd270a742d7 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -105,6 +105,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 27c81ec6e42..ef2c0d3803c 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -224,7 +224,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 
@@ -233,6 +233,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
 CREATE TABLE x1 (a int, b text);
 INSERT INTO x1 VALUES (1, 'one'), (2, 'two');
@@ -398,24 +405,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...
@@ -443,6 +445,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)))
@@ -548,7 +566,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
@@ -777,7 +795,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   
@@ -883,6 +901,12 @@ SELECT pg_get_viewdef('customers_us'::regclass);
    ORDER BY customer_name, product_name;
 (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 6d2b08b997b..b8aa95b729a 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -149,6 +149,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
 CREATE TABLE x1 (a int, b text);
@@ -285,9 +287,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));
 
 -- select all the properties across all the labels associated with a given type
@@ -295,6 +295,9 @@ SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.* IS NOT NULL)-[
 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)))
@@ -347,7 +350,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;
@@ -510,6 +513,10 @@ SELECT * FROM GRAPH_TABLE (g4 MATCH (s WHERE s.id = 3)-[e]-(d) COLUMNS (s.val, e
 CREATE VIEW customers_us AS SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[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)) ORDER BY customer_name, product_name;
 SELECT pg_get_viewdef('customers_us'::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 52f8603a7be..be69834719a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1139,6 +1139,7 @@ GraphElementPatternKind
 GraphLabelRef
 GraphPattern
 GraphPropertyRef
+GraphTableElementVariable
 GraphTableParseState
 Group
 GroupByOrdering

base-commit: 905e44152a1d49b90cb8d800dcaaba7981288ae2
-- 
2.34.1

---

From: Henson Choi <[email-masked]>
Date: Fri, 3 Apr 2026 10:53:54 +0900

Hi Ashutosh,

> happen before we transform COLUMNs clause. The patch rearranges the

I tried applying v20260318 on top of master to review it, but ran
into merge conflicts in two files:

- parse_graphtable.c
- rewriteGraphTable.c

The conflicts come from this commit that was added after the main PGQ
commit (2f094e7ac69):

- a0dd0702e46 Fix cross variable references in graph pattern causing
  segfault

Would it be possible to rebase the patch on the current master so
I can review it cleanly?

Best Regards,
Henson

---

From: Ashutosh Bapat <[email-masked]>
Date: Fri, 3 Apr 2026 11:07:03 +0530

> Would it be possible to rebase the patch on the current master so

I want to focus on resizable shared memory structures for PG 19 [1] in
whatever time remains until the feature freeze. If Peter feels that we
should get this in PG 19, I will rebase and finish the patch. I am ok,
if somebody else wants to rebase and finish this for PG 19 as well.
Sorry if this patch slips PG 19, but I will pick it up for PG 20, once
the branch opens.

[1] https://www.postgresql.org/message-id/CAExHW5vM1bneLYfg0wGeAa=[email-masked]

-- 
Best Wishes,
Ashutosh Bapat