Subject: Wrong query result w/ propgraph single lateral col reference

From: Noah Misch <[email-masked]>
Date: Tue, 30 Jun 2026 10:30:53 -0700

I had Opus 4.8 look for user-visible defects in SQL/PGQ (commit 2f094e7).
Setup:

CREATE TABLE v (flag boolean, id int PRIMARY KEY, name text);
INSERT INTO v VALUES (true,1,'a'), (false,2,'b'), (true,3,'c');
CREATE PROPERTY GRAPH g VERTEX TABLES (v KEY (id) LABEL l PROPERTIES (id, flag, name));

Findings:

0. A WHERE clause that is a single lateral col reference gives wrong query results:

SELECT t.b, gt.name FROM (VALUES (false)) AS t(b),
  GRAPH_TABLE (g MATCH (x IS l) WHERE t.b COLUMNS (x.name AS name)) gt;
-- got 2 rows, want 0

Adding "AND true" corrects the result:

SELECT t.b, gt.name FROM (VALUES (false)) AS t(b),
  GRAPH_TABLE (g MATCH (x IS l) WHERE t.b AND true COLUMNS (x.name AS name)) gt;
-- got 0 rows, want 0

1. A WHERE clause that is a single property reference gives a spurious error:

SELECT name FROM GRAPH_TABLE (g MATCH (x IS l) WHERE x.flag COLUMNS (x.name));
-- ERROR:  unrecognized node type: 63

Adding "AND true" again corrects the result:

SELECT name FROM GRAPH_TABLE (g MATCH (x IS l) WHERE x.flag AND true COLUMNS (x.name));
-- got 2 rows, want 2

For (0) and (1), Opus's opinion is that the right fix is:

--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ replace_property_refs(Oid propgraphid, Node *node, const List *mappings)
 	context.mappings = mappings;
 	context.propgraphid = propgraphid;

-	return expression_tree_mutator(node, replace_property_refs_mutator, &context);
+	return replace_property_refs_mutator(node, &context);
 }

2. A property whose value is an untyped literal stays type unknown.

CREATE PROPERTY GRAPH gu VERTEX TABLES (v KEY (id) LABEL lu PROPERTIES ('hi' AS p));
SELECT p FROM GRAPH_TABLE (gu MATCH (n IS lu) COLUMNS (n.p));
-- ERROR:  failed to find conversion function from unknown to text

3. [cosmetic] Invalid DROP PROPERTIES diagnosed via internal error message

CREATE PROPERTY GRAPH g2 VERTEX TABLES (
  v KEY (id) LABEL la PROPERTIES (flag) LABEL lb PROPERTIES (name));
ALTER PROPERTY GRAPH g2 ALTER VERTEX TABLE v ALTER LABEL la DROP PROPERTIES (name);
-- ERROR:  could not find tuple for label property 0

Opus says propoid is found graph wide, but the label property oid is not
checked before performDeletion(), so the intended "label has no property"
error never fires.

4. [cosmetic] Duplicate property names found only via catalog unique key

CREATE PROPERTY GRAPH g3 VERTEX TABLES (v KEY (id) LABEL lu PROPERTIES (flag, flag));
-- ERROR:  duplicate key value violates unique constraint "pg_propgraph_property_name_index"

Opus thinks this is unintentional and cites lack of CommandCounterIncrement()
between property inserts.

---

From: Ashutosh Bapat <[email-masked]>
Date: Wed, 1 Jul 2026 22:26:14 +0530

> +       return replace_property_refs_mutator(node, &context);

This matches the pattern in the other expression mutators, and also
fixes the problems. Thanks for the report, that was quite subtle.

> -- ERROR:  failed to find conversion function from unknown to text

I think property's data type should be set to text, not unknown. I
think we should combine the fix for this in
https://www.postgresql.org/message-id/[email-masked].

> checked before performDeletion(), so the intended "label has no property"

This has been already reported and being discussed in [1]

> between property inserts.

We get the same error even if we split the duplicate properties across
two commands.
CREATE PROPERTY GRAPH g3 VERTEX TABLES (v KEY (id) LABEL lu PROPERTIES (flag));
alter property graph g3 alter vertex table v alter label lu add
properties(flag);

I think we need to check for duplicate records in
pg_propgraph_label_property catalog in insert_property_record(). The
function already checks for duplicate records in
pg_propgraph_property. Additionally we might need
CommandCounterIncrement().

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

-- 
Best Wishes,
Ashutosh Bapat

---

From: Ashutosh Bapat <[email-masked]>
Date: Fri, 3 Jul 2026 10:11:58 +0530

> fixes the problems. Thanks for the report, that was quite subtle.

Fix attached here.

> https://www.postgresql.org/message-id/[email-masked].

Attaching the fix here. But I will also report it on the other thread
[2] and discussion can continue there.

> CommandCounterIncrement().

Fixed this as well.

I generate all the patches for SQL/PGQ bug fixes from the same branch.
These patches modify the same test and code files and thus conflict
with each other. Since we are talking about multiple issues here, I am
attaching all the patches that may be required to be applied before
applying the fixes being discussed here. I will highlight the patches
that actually fix the issues discussed here and the conflicting
patches. Ofc, the patches can be applied without conflict in the order
in which they are numbered. Each patch also has a link to the thread
which discusses the corresponding issue.

0011 fixes issues 0 and 1. The conflict is in changes to
graph_table.sql. You will need 0005 onwards patches to apply this
patch.

0005 fixes issue 2

0004 fixes issue 3

0003 fixes issue 4. Patches 0001 and 0002 are required to apply this
patch. They fix issues being discussed in [1]. 0002 adds a test file
which 0003 uses.

[1] https://www.postgresql.org/message-id/CAHg+QDeP=[email-masked]
[2] https://www.postgresql.org/message-id/[email-masked].

--
Best Wishes,
Ashutosh Bapat
From a51efd4583f30a1366a992c7d5632b14f77f4e61 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Thu, 2 Jul 2026 10:33:13 +0530
Subject: [PATCH v20260703 11/14] replace_property_refs() ignores the root of
 expression tree

replace_property_refs() called expression_tree_mutator() with the root of the
expression tree as the input node. expression_tree_mutator() does not call the
mutator function on the root node, so the root node remains unchanged. If the
root node is a property reference or a lateral reference -- the two node kinds
that replace_property_refs_mutator() rewrites -- it is returned unchanged.
Modules after the rewriter do not know about property reference nodes resulting
in "ERROR: unrecognized node type: 63". Since varlevelsup of lateral references
is not incremented, they are not resolved correctly in the planner, leading to
many different symptoms. Fix this by calling replace_property_refs_mutator()
directly from replace_property_refs(), similar to how other mutator functions
do.

The only case when a property reference or a lateral reference can be
the root of a GRAPH_TABLE expression tree is when it is a bare property
reference or a bare lateral reference in the WHERE clause. The COLUMNS
clause is passed to replace_property_refs() as a targetlist. Every other
expression has at least one expression node covering the property
reference or a lateral reference in the expression tree. That explains
why this bug was not seen so far.

Author: Ashutosh Bapat <[email-masked]>
Reported by: Noah Misch <[email-masked]>
Fix suggested by: Noah Misch <[email-masked]>
Discussion: https://www.postgresql.org/message-id/[email-masked]
---
 src/backend/rewrite/rewriteGraphTable.c   |  2 +-
 src/test/regress/expected/graph_table.out | 38 +++++++++++++++++------
 src/test/regress/sql/graph_table.sql      | 17 ++++++----
 3 files changed, 41 insertions(+), 16 deletions(-)

diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index 7df7f9fd70a..a5b1be77a0a 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -1129,7 +1129,7 @@ replace_property_refs(Oid propgraphid, Node *node, const List *mappings)
 	context.mappings = mappings;
 	context.propgraphid = propgraphid;

 /*
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
index c0622ab7234..c33712283c1 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -248,12 +248,12 @@ SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)->(o IS orders) COLUMNS
 -- 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
 -- lateral table references.
-CREATE TABLE x1 (a int, address text);
-INSERT INTO x1 VALUES (1, 'one'), (2, 'two');
+CREATE TABLE x1 (a int, address text, flag boolean);
+INSERT INTO x1 VALUES (1, 'one', true), (2, 'two', false);
 SELECT * FROM x1, GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US' AND c.customer_id = x1.a)-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name, c.customer_id AS cid));
- a | address | customer_name | cid 
----+---------+---------------+-----
- 1 | one     | customer1     |   1
+ a | address | flag | customer_name | cid 
+---+---------+------+---------------+-----
+ 1 | one     | t    | customer1     |   1
 (1 row)
 
 SELECT x1.a, g.* FROM x1, GRAPH_TABLE (myshop MATCH (x1 IS customers WHERE x1.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (x1.name AS customer_name, x1.customer_id AS cid, o.order_id)) g;
@@ -263,6 +263,13 @@ SELECT x1.a, g.* FROM x1, GRAPH_TABLE (myshop MATCH (x1 IS customers WHERE x1.ad
  2 | customer1     |   1 |        1
 (2 rows)
 
+-- bare lateral reference in WHERE clause
+SELECT * FROM x1, GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.customer_id = x1.a) WHERE x1.flag COLUMNS (c.name AS customer_name));
+ a | address | flag | customer_name 
+---+---------+------+---------------
+ 1 | one     | t    | customer1
+(1 row)
+
 -- lateral reference with multi-label pattern, which is rewritten as UNION of
 -- path queries
 SELECT x1.a, g.* FROM x1,
@@ -866,12 +873,12 @@ CREATE TABLE cv2 () INHERITS (pv);
 INSERT INTO pv VALUES (1, 10);
 INSERT INTO cv1 VALUES (2, 20);
 INSERT INTO cv2 VALUES (3, 30);
-CREATE TABLE pe (id int, src int, dest int, val int);
+CREATE TABLE pe (id int, src int, dest int, val int, flag boolean);
 CREATE TABLE ce1 () INHERITS (pe);
 CREATE TABLE ce2 () INHERITS (pe);
-INSERT INTO pe VALUES (1, 1, 2, 100);
-INSERT INTO ce1 VALUES (2, 2, 3, 200);
-INSERT INTO ce2 VALUES (3, 3, 1, 300);
+INSERT INTO pe VALUES (1, 1, 2, 100, false);
+INSERT INTO ce1 VALUES (2, 2, 3, 200, false);
+INSERT INTO ce2 VALUES (3, 3, 1, 300, true);
 CREATE PROPERTY GRAPH g3
     NODE TABLES (
         pv KEY (id)
@@ -889,6 +896,19 @@ SELECT * FROM GRAPH_TABLE (g3 MATCH (s IS pv)-[e IS pe]->(d IS pv) COLUMNS (s.va
   30 | 300 |  10
 (3 rows)
 
+-- bare property reference in WHERE clause
+SELECT * FROM GRAPH_TABLE (g3 MATCH (s IS pv)-[e IS pe WHERE e.flag]->(d IS pv) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
+ val | val | val 
+-----+-----+-----
+  30 | 300 |  10
+(1 row)
+
+SELECT * FROM GRAPH_TABLE (g3 MATCH (s IS pv)-[e IS pe]->(d IS pv) WHERE e.flag COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
+ val | val | val 
+-----+-----+-----
+  30 | 300 |  10
+(1 row)
+
 CREATE TEMPORARY PROPERTY GRAPH gtmp
     VERTEX TABLES (
         pv KEY (id)
diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql
index 437fb3b01f2..0bfa8f3ac85 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -156,10 +156,12 @@ SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)->(o IS orders) COLUMNS
 -- 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
 -- lateral table references.
-CREATE TABLE x1 (a int, address text);
-INSERT INTO x1 VALUES (1, 'one'), (2, 'two');
+CREATE TABLE x1 (a int, address text, flag boolean);
+INSERT INTO x1 VALUES (1, 'one', true), (2, 'two', false);
 SELECT * FROM x1, GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US' AND c.customer_id = x1.a)-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name, c.customer_id AS cid));
 SELECT x1.a, g.* FROM x1, GRAPH_TABLE (myshop MATCH (x1 IS customers WHERE x1.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (x1.name AS customer_name, x1.customer_id AS cid, o.order_id)) g;
+-- bare lateral reference in WHERE clause
+SELECT * FROM x1, GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.customer_id = x1.a) WHERE x1.flag COLUMNS (c.name AS customer_name));
 -- lateral reference with multi-label pattern, which is rewritten as UNION of
 -- path queries
 SELECT x1.a, g.* FROM x1,
@@ -485,12 +487,12 @@ CREATE TABLE cv2 () INHERITS (pv);
 INSERT INTO pv VALUES (1, 10);
 INSERT INTO cv1 VALUES (2, 20);
 INSERT INTO cv2 VALUES (3, 30);
-CREATE TABLE pe (id int, src int, dest int, val int);
+CREATE TABLE pe (id int, src int, dest int, val int, flag boolean);
 CREATE TABLE ce1 () INHERITS (pe);
 CREATE TABLE ce2 () INHERITS (pe);
-INSERT INTO pe VALUES (1, 1, 2, 100);
-INSERT INTO ce1 VALUES (2, 2, 3, 200);
-INSERT INTO ce2 VALUES (3, 3, 1, 300);
+INSERT INTO pe VALUES (1, 1, 2, 100, false);
+INSERT INTO ce1 VALUES (2, 2, 3, 200, false);
+INSERT INTO ce2 VALUES (3, 3, 1, 300, true);
 CREATE PROPERTY GRAPH g3
     NODE TABLES (
         pv KEY (id)
@@ -501,6 +503,9 @@ CREATE PROPERTY GRAPH g3
             DESTINATION KEY(dest) REFERENCES pv(id)
     );
 SELECT * FROM GRAPH_TABLE (g3 MATCH (s IS pv)-[e IS pe]->(d IS pv) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
+-- bare property reference in WHERE clause
+SELECT * FROM GRAPH_TABLE (g3 MATCH (s IS pv)-[e IS pe WHERE e.flag]->(d IS pv) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
+SELECT * FROM GRAPH_TABLE (g3 MATCH (s IS pv)-[e IS pe]->(d IS pv) WHERE e.flag COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
 CREATE TEMPORARY PROPERTY GRAPH gtmp
     VERTEX TABLES (
         pv KEY (id)
-- 
2.34.1

From fbe475281f1111a791d2bacab06eb4490f7c93fd Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Wed, 1 Jul 2026 21:34:48 +0530
Subject: [PATCH v20260703 10/14] Using GRANT ... TABLE command on a property
 graph

Property graph can only have SELECT privilege. The privileges can be granted
using GRANT ... PROPERTY GRAPH command or GRANT ... TABLE command. When
privileges are granted using GRANT ... TABLE command, similar to the case of
sequences, ignore the privileges other than SELECT privilege with a warning.
While at it also handle missing RELKIND_PROPGRAPH in pg_class_aclmask_ext() and
ExecGrant_Relation() functions.

Author: Ashutosh Bapat <[email-masked]>
---
 src/backend/catalog/aclchk.c                  | 30 ++++++++++++++++++-
 .../expected/create_property_graph.out        |  2 ++
 .../regress/sql/create_property_graph.sql     |  1 +
 3 files changed, 32 insertions(+), 1 deletion(-)

diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 140cd1302f5..bb102205d5c 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -1784,7 +1784,8 @@ ExecGrant_Attribute(InternalGrant *istmt, Oid relOid, const char *relname,
 }
 
 /*
- *	This processes both sequences and non-sequences.
+ * This processes sequences, property graphs and other relations types in
+ * pg_class catalog.
  */
 static void
 ExecGrant_Relation(InternalGrant *istmt)
@@ -1891,6 +1892,27 @@ ExecGrant_Relation(InternalGrant *istmt)
 					this_privileges &= (AclMode) ACL_ALL_RIGHTS_SEQUENCE;
 				}
 			}
+			else if (pg_class_tuple->relkind == RELKIND_PROPGRAPH)
+			{
+				/*
+				 * For backward compatibility, just throw a warning for
+				 * invalid property graph permissions when using the non
+				 * property graph GRANT syntax.
+				 */
+				if (this_privileges & ~((AclMode) ACL_ALL_RIGHTS_PROPGRAPH))
+				{
+					/*
+					 * Mention the object name because the user needs to know
+					 * which operations succeeded.  This is required because
+					 * WARNING allows the command to continue.
+					 */
+					ereport(WARNING,
+							(errcode(ERRCODE_INVALID_GRANT_OPERATION),
+							 errmsg("property graph \"%s\" only supports SELECT privileges",
+									NameStr(pg_class_tuple->relname))));
+					this_privileges &= (AclMode) ACL_ALL_RIGHTS_PROPGRAPH;
+				}
+			}
 			else
 			{
 				if (this_privileges & ~((AclMode) ACL_ALL_RIGHTS_RELATION))
@@ -1996,6 +2018,9 @@ ExecGrant_Relation(InternalGrant *istmt)
 				case RELKIND_SEQUENCE:
 					objtype = OBJECT_SEQUENCE;
 					break;
+				case RELKIND_PROPGRAPH:
+					objtype = OBJECT_PROPGRAPH;
+					break;
 				default:
 					objtype = OBJECT_TABLE;
 					break;
@@ -3389,6 +3414,9 @@ pg_class_aclmask_ext(Oid table_oid, Oid roleid, AclMode mask,
 			case RELKIND_SEQUENCE:
 				acl = acldefault(OBJECT_SEQUENCE, ownerId);
 				break;
+			case RELKIND_PROPGRAPH:
+				acl = acldefault(OBJECT_PROPGRAPH, ownerId);
+				break;
 			default:
 				acl = acldefault(OBJECT_TABLE, ownerId);
 				break;
diff --git a/src/test/regress/expected/create_property_graph.out b/src/test/regress/expected/create_property_graph.out
index e9f91f77ee9..713966e8a9e 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -251,6 +251,8 @@ SET ROLE regress_graph_user1;
 GRANT SELECT ON PROPERTY GRAPH g1 TO regress_graph_user2;
 GRANT UPDATE ON PROPERTY GRAPH g1 TO regress_graph_user2;  -- fail
 ERROR:  invalid privilege type UPDATE for property graph
+GRANT UPDATE ON TABLE g1 TO regress_graph_user2;  -- warning
+WARNING:  property graph "g1" only supports SELECT privileges
 RESET ROLE;
 -- collation
 CREATE TABLE tc1 (a int, b text);
diff --git a/src/test/regress/sql/create_property_graph.sql b/src/test/regress/sql/create_property_graph.sql
index 08341e13a50..5325d22206e 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -197,6 +197,7 @@ ALTER PROPERTY GRAPH g1 OWNER TO regress_graph_user1;
 SET ROLE regress_graph_user1;
 GRANT SELECT ON PROPERTY GRAPH g1 TO regress_graph_user2;
 GRANT UPDATE ON PROPERTY GRAPH g1 TO regress_graph_user2;  -- fail
+GRANT UPDATE ON TABLE g1 TO regress_graph_user2;  -- warning
 RESET ROLE;
 
 -- collation
-- 
2.34.1

From c8cd321bd86543780be02ad5933724a4febfaf7e Mon Sep 17 00:00:00 2001
From: Ayush Tiwari <[email-masked]>
Date: Tue, 9 Jun 2026 20:14:13 +0530
Subject: [PATCH v20260703 09/14] Prohibit Locking Clauses on GRAPH_TABLE

Specifying a locking clause (FOR UPDATE/SHARE) that names a GRAPH_TABLE
alias currently results in an unhelpful "unrecognized RTE type: 8"
error. This commit explicitly prohibits specifying a locking clause on a
GRAPH_TABLE alias, raising a more user-friendly "FOR ... cannot be
applied to a GRAPH_TABLE" error instead.

Author: SATYANARAYANA NARLAPURAM <[email-masked]>
Author: Ayush Tiwari <[email-masked]>
Reported-by: SATYANARAYANA NARLAPURAM <[email-masked]>
Reviewed-by: Ashutosh Bapat <[email-masked]>
Discussion: https://postgr.es/m/CAHg%2BQDcE9wp6nOEC3SCRQ90nrCO%3DQF%2BOZq1MG8Qc6hnusmogqw%40mail.gmail.com
---
 src/backend/parser/analyze.c              | 15 ++++++++++++++-
 src/test/regress/expected/graph_table.out | 13 +++++++++++++
 src/test/regress/sql/graph_table.sql      |  4 ++++
 3 files changed, 31 insertions(+), 1 deletion(-)

diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 2932d17a107..11e6d790e21 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -3867,7 +3867,11 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 										   allrels, true);
 					break;
 				default:
-					/* ignore JOIN, SPECIAL, FUNCTION, VALUES, CTE RTEs */
+
+					/*
+					 * ignore JOIN, SPECIAL, FUNCTION, VALUES, CTE,
+					 * GRAPH_TABLE
+					 */
 					break;
 			}
 		}
@@ -4002,6 +4006,15 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 											LCS_asString(lc->strength)),
 									 parser_errposition(pstate, thisrel->location)));
 							break;
+						case RTE_GRAPH_TABLE:
+							ereport(ERROR,
+									(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							/*------
+							  translator: %s is a SQL row locking clause such as FOR UPDATE */
+									 errmsg("%s cannot be applied to a GRAPH_TABLE",
+											LCS_asString(lc->strength)),
+									 parser_errposition(pstate, thisrel->location)));
+							break;
 
 							/* Shouldn't be possible to see RTE_RESULT here */
 
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
index c229212cb51..c0622ab7234 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -1129,4 +1129,17 @@ SELECT src.vname, count(*) FROM v1 AS src
  v13   |     1
 (3 rows)
 
+-- Locking clause on GRAPH_TABLE
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS vl1) COLUMNS (src.vname)) gt FOR UPDATE OF gt;  -- not supported
+ERROR:  FOR UPDATE cannot be applied to a GRAPH_TABLE
+LINE 1: ...MATCH (src IS vl1) COLUMNS (src.vname)) gt FOR UPDATE OF gt;
+                                                                    ^
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS vl1) COLUMNS (src.vname)) gt FOR UPDATE; -- ignored
+ vname 
+-------
+ v11
+ v12
+ v13
+(3 rows)
+
 -- leave the objects behind for pg_upgrade/pg_dump tests
diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql
index e30c908dccc..437fb3b01f2 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -637,4 +637,8 @@ SELECT src.vname, count(*) FROM v1 AS src
   HAVING count(*) >= (SELECT count(*) FROM GRAPH_TABLE (g1 MATCH (a IS vl1 | vl2) COLUMNS (a.vname AS n)) WHERE n = src.vname)
   ORDER BY vname;
 
+-- Locking clause on GRAPH_TABLE
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS vl1) COLUMNS (src.vname)) gt FOR UPDATE OF gt;  -- not supported
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS vl1) COLUMNS (src.vname)) gt FOR UPDATE; -- ignored
+
 -- leave the objects behind for pg_upgrade/pg_dump tests
-- 
2.34.1

From 105fb2c4ed247df112053c35c9aa57c637d2e254 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Thu, 28 May 2026 02:45:58 +0530
Subject: [PATCH v20260703 07/14] 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-masked]>
Discussion: https://www.postgresql.org/message-id/CAExHW5twGP5Zuk4Zch4kz8XDrSpckWQipMs=[email-masked]
---
 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     |  29 ++++-
 .../regress/sql/create_property_graph.sql     |   1 +
 src/test/regress/sql/graph_table.sql          |  16 ++-
 7 files changed, 178 insertions(+), 65 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 7db17bec312..7df7f9fd70a 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 e03556399ab..d6d0f7d170d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1051,7 +1051,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 c0299430ee3..e9f91f77ee9 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -975,6 +975,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 c3c4f1a74a5..bc06feb4a7f 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -858,6 +858,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);
@@ -887,7 +889,6 @@ SELECT * FROM GRAPH_TABLE (g3 MATCH (s IS pv)-[e IS pe]->(d IS pv) COLUMNS (s.va
   30 | 300 |  10
 (3 rows)
 
--- temporary property graph
 CREATE TEMPORARY PROPERTY GRAPH gtmp
     VERTEX TABLES (
         pv KEY (id)
@@ -914,8 +915,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)
@@ -971,6 +976,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                                                                                                                                                 
@@ -984,6 +1000,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 ef3a86b8936..08341e13a50 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -389,6 +389,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 5bd9fc8470a..44368fd7b80 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -477,6 +477,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);
@@ -499,7 +501,6 @@ CREATE PROPERTY GRAPH g3
             DESTINATION KEY(dest) REFERENCES pv(id)
     );
 SELECT * FROM GRAPH_TABLE (g3 MATCH (s IS pv)-[e IS pe]->(d IS pv) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
--- temporary property graph
 CREATE TEMPORARY PROPERTY GRAPH gtmp
     VERTEX TABLES (
         pv KEY (id)
@@ -520,8 +521,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)
@@ -551,8 +555,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 136a59042ce47508f2dd3ca6d3154e0751e2938f Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Thu, 28 May 2026 02:47:33 +0530
Subject: [PATCH v20260703 08/14] 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-masked]>
---
 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 bc06feb4a7f..c229212cb51 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -987,6 +987,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                                                                                                                                                 
@@ -1007,6 +1020,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 44368fd7b80..e30c908dccc 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -560,9 +560,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

From 39542c4574ed09c6f5fb33be693fe2977769fc2f Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Wed, 29 Apr 2026 19:07:13 +0530
Subject: [PATCH v20260703 05/14] Resolve unknown-type literals in property
 graphs

The unknown-type literals in the COLUMNS clause of a GRAPH_TABLE were
not resolved to the appropriate types, causing various failures. To fix
this, call resolveTargetListUnknowns() on the columns targetlist to
resolve unknown type literals.

Similarly, when an unknown-type literal is provided as a property
expression, the data type of the property was set to "unknown", which
may lead to various failures when the property is used in GRAPH_TABLE or
when its data type is compared against other properties with the same
name. To fix this, call resolveTargetListUnknowns() on the targetlist of
new properties to resolve unknown type literals.

In both cases call resolveTargetListUnknowns() before assigning
collations so that the resolved types are used for collation assignment.

Reported by: Satya Narlapuram <[email-masked]>
Reported by: Noah Misch <[email-masked]>
Author: Satya Narlapuram <[email-masked]>
Author: Ashutosh Bapat <[email-masked]>
Reviewed by: Junwang Zhao <[email-masked]>
Discussion: https://www.postgresql.org/message-id/[email-masked]
---
 src/backend/commands/propgraphcmds.c          |  2 +
 src/backend/parser/parse_clause.c             |  4 ++
 .../expected/create_property_graph.out        | 37 +++++++++++++++----
 src/test/regress/expected/graph_table.out     |  9 +++++
 .../regress/sql/create_property_graph.sql     |  6 +++
 src/test/regress/sql/graph_table.sql          |  2 +
 6 files changed, 52 insertions(+), 8 deletions(-)

diff --git a/src/backend/commands/propgraphcmds.c b/src/backend/commands/propgraphcmds.c
index 510cc1ccc03..2ff62d98227 100644
--- a/src/backend/commands/propgraphcmds.c
+++ b/src/backend/commands/propgraphcmds.c
@@ -904,6 +904,8 @@ insert_property_records(Oid graphid, Oid ellabeloid, Oid pgerelid, const PropGra
 	table_close(rel, NoLock);
 
 	tp = transformTargetList(pstate, proplist, EXPR_KIND_PROPGRAPH_PROPERTY);
+	if (pstate->p_resolve_unknowns)
+		resolveTargetListUnknowns(pstate, tp);
 	assign_expr_collations(pstate, (Node *) tp);
 
 	/*
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 5fe5257b019..881fba2e7b5 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -1005,6 +1005,10 @@ transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt)
 		columns = lappend(columns, te);
 	}
 
+	/* resolve any still-unresolved output columns as being type text */
+	if (pstate->p_resolve_unknowns)
+		resolveTargetListUnknowns(pstate, columns);
+
 	/*
 	 * Assign collations to column expressions now since
 	 * assign_query_collations() does not process rangetable entries.
diff --git a/src/test/regress/expected/create_property_graph.out b/src/test/regress/expected/create_property_graph.out
index 7f12697bbd5..8863bdd3131 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -346,6 +346,10 @@ CREATE PROPERTY GRAPH gt
             SOURCE KEY (k1) REFERENCES v1(a)
             DESTINATION KEY (k2) REFERENCES v2(m)
     );
+-- data types of constant property values
+CREATE PROPERTY GRAPH glc VERTEX TABLES (
+    v1 KEY (a) LABEL l1 PROPERTIES ('foo' AS p1, 123 AS p2, 3.14 AS p3, true AS p4)
+);
 -- information schema
 SELECT * FROM information_schema.property_graphs ORDER BY property_graph_name;
  property_graph_catalog |    property_graph_schema    | property_graph_name 
@@ -356,8 +360,9 @@ SELECT * FROM information_schema.property_graphs ORDER BY property_graph_name;
  regression             | create_property_graph_tests | g4
  regression             | create_property_graph_tests | g5
  regression             | create_property_graph_tests | gc1
+ regression             | create_property_graph_tests | glc
  regression             | create_property_graph_tests | gt
-(7 rows)
+(8 rows)
 
 SELECT * FROM information_schema.pg_element_tables ORDER BY property_graph_name, element_table_alias;
  property_graph_catalog |    property_graph_schema    | property_graph_name | element_table_alias | element_table_kind | table_catalog |        table_schema         | table_name | element_table_definition 
@@ -382,10 +387,11 @@ SELECT * FROM information_schema.pg_element_tables ORDER BY property_graph_name,
  regression             | create_property_graph_tests | gc1                 | tc1                 | VERTEX             | regression    | create_property_graph_tests | tc1        | 
  regression             | create_property_graph_tests | gc1                 | tc2                 | VERTEX             | regression    | create_property_graph_tests | tc2        | 
  regression             | create_property_graph_tests | gc1                 | tc3                 | VERTEX             | regression    | create_property_graph_tests | tc3        | 
+ regression             | create_property_graph_tests | glc                 | v1                  | VERTEX             | regression    | create_property_graph_tests | v1         | 
  regression             | create_property_graph_tests | gt                  | e                   | EDGE               | regression    | create_property_graph_tests | e          | 
  regression             | create_property_graph_tests | gt                  | v1                  | VERTEX             | regression    | create_property_graph_tests | v1         | 
  regression             | create_property_graph_tests | gt                  | v2                  | VERTEX             | regression    | create_property_graph_tests | v2         | 
-(23 rows)
+(24 rows)
 
 SELECT * FROM information_schema.pg_element_table_key_columns ORDER BY property_graph_name, element_table_alias, ordinal_position;
  property_graph_catalog |    property_graph_schema    | property_graph_name | element_table_alias | column_name | ordinal_position 
@@ -416,11 +422,12 @@ SELECT * FROM information_schema.pg_element_table_key_columns ORDER BY property_
  regression             | create_property_graph_tests | gc1                 | tc1                 | a           |                1
  regression             | create_property_graph_tests | gc1                 | tc2                 | a           |                1
  regression             | create_property_graph_tests | gc1                 | tc3                 | a           |                1
+ regression             | create_property_graph_tests | glc                 | v1                  | a           |                1
  regression             | create_property_graph_tests | gt                  | e                   | k1          |                1
  regression             | create_property_graph_tests | gt                  | e                   | k2          |                2
  regression             | create_property_graph_tests | gt                  | v1                  | a           |                1
  regression             | create_property_graph_tests | gt                  | v2                  | m           |                1
-(30 rows)
+(31 rows)
 
 SELECT * FROM information_schema.pg_edge_table_components ORDER BY property_graph_name, edge_table_alias, edge_end DESC, ordinal_position;
  property_graph_catalog |    property_graph_schema    | property_graph_name | edge_table_alias | vertex_table_alias |  edge_end   | edge_table_column_name | vertex_table_column_name | ordinal_position 
@@ -471,10 +478,11 @@ SELECT * FROM information_schema.pg_element_table_labels ORDER BY property_graph
  regression             | create_property_graph_tests | gc1                 | tc1                 | tc1
  regression             | create_property_graph_tests | gc1                 | tc2                 | tc2
  regression             | create_property_graph_tests | gc1                 | tc3                 | tc3
+ regression             | create_property_graph_tests | glc                 | v1                  | l1
  regression             | create_property_graph_tests | gt                  | e                   | e
  regression             | create_property_graph_tests | gt                  | v1                  | v1
  regression             | create_property_graph_tests | gt                  | v2                  | v2
-(26 rows)
+(27 rows)
 
 SELECT * FROM information_schema.pg_element_table_properties ORDER BY property_graph_name, element_table_alias, property_name;
  property_graph_catalog |    property_graph_schema    | property_graph_name | element_table_alias | property_name |         property_expression          
@@ -526,6 +534,10 @@ SELECT * FROM information_schema.pg_element_table_properties ORDER BY property_g
  regression             | create_property_graph_tests | gc1                 | tc2                 | b             | ((b)::character varying COLLATE "C")
  regression             | create_property_graph_tests | gc1                 | tc3                 | a             | a
  regression             | create_property_graph_tests | gc1                 | tc3                 | b             | (b)::character varying
+ regression             | create_property_graph_tests | glc                 | v1                  | p1            | 'foo'::text
+ regression             | create_property_graph_tests | glc                 | v1                  | p2            | 123
+ regression             | create_property_graph_tests | glc                 | v1                  | p3            | 3.14
+ regression             | create_property_graph_tests | glc                 | v1                  | p4            | true
  regression             | create_property_graph_tests | gt                  | e                   | c             | c
  regression             | create_property_graph_tests | gt                  | e                   | k1            | k1
  regression             | create_property_graph_tests | gt                  | e                   | k2            | k2
@@ -533,7 +545,7 @@ SELECT * FROM information_schema.pg_element_table_properties ORDER BY property_g
  regression             | create_property_graph_tests | gt                  | v1                  | b             | b
  regression             | create_property_graph_tests | gt                  | v2                  | m             | m
  regression             | create_property_graph_tests | gt                  | v2                  | n             | n
-(54 rows)
+(58 rows)
 
 SELECT * FROM information_schema.pg_label_properties ORDER BY property_graph_name, label_name, property_name;
  property_graph_catalog |    property_graph_schema    | property_graph_name | label_name | property_name 
@@ -592,6 +604,10 @@ SELECT * FROM information_schema.pg_label_properties ORDER BY property_graph_nam
  regression             | create_property_graph_tests | gc1                 | tc2        | b
  regression             | create_property_graph_tests | gc1                 | tc3        | a
  regression             | create_property_graph_tests | gc1                 | tc3        | b
+ regression             | create_property_graph_tests | glc                 | l1         | p1
+ regression             | create_property_graph_tests | glc                 | l1         | p2
+ regression             | create_property_graph_tests | glc                 | l1         | p3
+ regression             | create_property_graph_tests | glc                 | l1         | p4
  regression             | create_property_graph_tests | gt                  | e          | c
  regression             | create_property_graph_tests | gt                  | e          | k1
  regression             | create_property_graph_tests | gt                  | e          | k2
@@ -599,7 +615,7 @@ SELECT * FROM information_schema.pg_label_properties ORDER BY property_graph_nam
  regression             | create_property_graph_tests | gt                  | v1         | b
  regression             | create_property_graph_tests | gt                  | v2         | m
  regression             | create_property_graph_tests | gt                  | v2         | n
-(61 rows)
+(65 rows)
 
 SELECT * FROM information_schema.pg_labels ORDER BY property_graph_name, label_name;
  property_graph_catalog |    property_graph_schema    | property_graph_name | label_name 
@@ -627,10 +643,11 @@ SELECT * FROM information_schema.pg_labels ORDER BY property_graph_name, label_n
  regression             | create_property_graph_tests | gc1                 | tc1
  regression             | create_property_graph_tests | gc1                 | tc2
  regression             | create_property_graph_tests | gc1                 | tc3
+ regression             | create_property_graph_tests | glc                 | l1
  regression             | create_property_graph_tests | gt                  | e
  regression             | create_property_graph_tests | gt                  | v1
  regression             | create_property_graph_tests | gt                  | v2
-(26 rows)
+(27 rows)
 
 SELECT * FROM information_schema.pg_property_data_types ORDER BY property_graph_name, property_name;
  property_graph_catalog |    property_graph_schema    | property_graph_name | property_name |     data_type     | character_maximum_length | character_octet_length | character_set_catalog | character_set_schema | character_set_name | collation_catalog | collation_schema | collation_name | numeric_precision | numeric_precision_radix | numeric_scale | datetime_precision | interval_type | interval_precision | user_defined_type_catalog | user_defined_type_schema | user_defined_type_name | scope_catalog | scope_schema | scope_name | maximum_cardinality | dtd_identifier 
@@ -667,6 +684,10 @@ SELECT * FROM information_schema.pg_property_data_types ORDER BY property_graph_
  regression             | create_property_graph_tests | gc1                 | eb            | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | eb
  regression             | create_property_graph_tests | gc1                 | ek1           | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | ek1
  regression             | create_property_graph_tests | gc1                 | ek2           | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | ek2
+ regression             | create_property_graph_tests | glc                 | p1            | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | p1
+ regression             | create_property_graph_tests | glc                 | p2            | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | p2
+ regression             | create_property_graph_tests | glc                 | p3            | numeric           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | numeric                |               |              |            |                     | p3
+ regression             | create_property_graph_tests | glc                 | p4            | boolean           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | bool                   |               |              |            |                     | p4
  regression             | create_property_graph_tests | gt                  | a             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
  regression             | create_property_graph_tests | gt                  | b             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | b
  regression             | create_property_graph_tests | gt                  | c             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | c
@@ -674,7 +695,7 @@ SELECT * FROM information_schema.pg_property_data_types ORDER BY property_graph_
  regression             | create_property_graph_tests | gt                  | k2            | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | k2
  regression             | create_property_graph_tests | gt                  | m             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | m
  regression             | create_property_graph_tests | gt                  | n             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | n
-(39 rows)
+(43 rows)
 
 SELECT * FROM information_schema.pg_property_graph_privileges WHERE grantee LIKE 'regress%' ORDER BY property_graph_name, grantor, grantee, privilege_type;
        grantor       |       grantee       | property_graph_catalog |    property_graph_schema    | property_graph_name | privilege_type | is_grantable 
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
index 70d986e8ab0..c3c4f1a74a5 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -160,6 +160,15 @@ SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (c.name));
  customer3
 (3 rows)
 
+-- Unknown type resolution
+SELECT *, pg_typeof(unknown_col) AS unknown_col_type, pg_typeof(null_col) AS null_col_type FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (c.name, 'unknown-literal' AS unknown_col, NULL AS null_col));
+   name    |   unknown_col   | null_col | unknown_col_type | null_col_type 
+-----------+-----------------+----------+------------------+---------------
+ customer1 | unknown-literal |          | text             | text
+ customer2 | unknown-literal |          | text             | text
+ customer3 | unknown-literal |          | text             | text
+(3 rows)
+
 SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name));
    name    
 -----------
diff --git a/src/test/regress/sql/create_property_graph.sql b/src/test/regress/sql/create_property_graph.sql
index 171260ac69f..1bd2192d4a3 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -285,6 +285,12 @@ CREATE PROPERTY GRAPH gt
             DESTINATION KEY (k2) REFERENCES v2(m)
     );
 
+-- data types of constant property values
+
+CREATE PROPERTY GRAPH glc VERTEX TABLES (
+    v1 KEY (a) LABEL l1 PROPERTIES ('foo' AS p1, 123 AS p2, 3.14 AS p3, true AS p4)
+);
+
 -- information schema
 
 SELECT * FROM information_schema.property_graphs ORDER BY property_graph_name;
diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql
index 0b44f70d7e7..5bd9fc8470a 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -134,6 +134,8 @@ INSERT INTO wishlist_items (wishlist_items_id, wishlist_id, product_no) VALUES
 
 -- single element path pattern
 SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (c.name));
+-- Unknown type resolution
+SELECT *, pg_typeof(unknown_col) AS unknown_col_type, pg_typeof(null_col) AS null_col_type FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (c.name, 'unknown-literal' AS unknown_col, NULL AS null_col));
 SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name));
 -- graph element specification without label or variable
 SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[]->(o IS orders) COLUMNS (c.name AS customer_name));
-- 
2.34.1

From ea854be90d2d5d47dc9370d5e42fba460139a1c5 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Wed, 10 Jun 2026 22:08:49 +0530
Subject: [PATCH v20260703 06/14] pg_propgraph_property entries orphaned by
 dropping a label

AlterPropGraph() cleans up pg_propgraph_property entries that are
orphaned by dropping an element or by dropping properties associated
with an element. But it doesn't clean up pg_propgraph_property entries
that are orphaned by dropping labels associated with an element. Fix
this missing case.

Author: Ashutosh Bapat <[email-masked]>
Author: zengman <[email-masked]>.
Reported by: zengman <[email-masked]>
---
 src/backend/commands/propgraphcmds.c          |  3 ++-
 .../expected/create_property_graph.out        | 24 +++++++++----------
 .../regress/sql/create_property_graph.sql     |  6 +++++
 3 files changed, 20 insertions(+), 13 deletions(-)

diff --git a/src/backend/commands/propgraphcmds.c b/src/backend/commands/propgraphcmds.c
index 2ff62d98227..3159cfe46ce 100644
--- a/src/backend/commands/propgraphcmds.c
+++ b/src/backend/commands/propgraphcmds.c
@@ -1704,7 +1704,8 @@ AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt)
 	}
 
 	/* Remove any orphaned pg_propgraph_property entries */
-	if (stmt->drop_properties || stmt->drop_vertex_tables || stmt->drop_edge_tables)
+	if (stmt->drop_properties || stmt->drop_vertex_tables ||
+		stmt->drop_edge_tables || stmt->drop_label)
 	{
 		foreach_oid(propoid, get_graph_property_ids(pgrelid))
 		{
diff --git a/src/test/regress/expected/create_property_graph.out b/src/test/regress/expected/create_property_graph.out
index 8863bdd3131..c0299430ee3 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -91,6 +91,12 @@ ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 ADD PROPERTIES (k *
 ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 DROP PROPERTIES (k);
 ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 DROP PROPERTIES (yy);  -- error
 ERROR:  property graph "g4" element "t2" label "t2" has no property "yy"
+-- Dropping a label should drop only orphaned properties. Dropping label t3l1
+-- should also drop zz because it is only associated with label t3l2. But x is
+-- not dropped, even if it is associated with t3l2, because it remains
+-- associated with t3l1. zz will not appear in the information schema queries
+-- outputs below, but x will.
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t3 DROP LABEL t3l2;
 CREATE TABLE t11 (a int PRIMARY KEY);
 CREATE TABLE t12 (b int PRIMARY KEY);
 CREATE TABLE t13 (
@@ -469,7 +475,6 @@ SELECT * FROM information_schema.pg_element_table_labels ORDER BY property_graph
  regression             | create_property_graph_tests | g4                  | t1                  | t1
  regression             | create_property_graph_tests | g4                  | t2                  | t2
  regression             | create_property_graph_tests | g4                  | t3                  | t3l1
- regression             | create_property_graph_tests | g4                  | t3                  | t3l2
  regression             | create_property_graph_tests | g5                  | t11                 | t11
  regression             | create_property_graph_tests | g5                  | t12                 | t12
  regression             | create_property_graph_tests | g5                  | t13                 | t13
@@ -482,7 +487,7 @@ SELECT * FROM information_schema.pg_element_table_labels ORDER BY property_graph
  regression             | create_property_graph_tests | gt                  | e                   | e
  regression             | create_property_graph_tests | gt                  | v1                  | v1
  regression             | create_property_graph_tests | gt                  | v2                  | v2
-(27 rows)
+(26 rows)
 
 SELECT * FROM information_schema.pg_element_table_properties ORDER BY property_graph_name, element_table_alias, property_name;
  property_graph_catalog |    property_graph_schema    | property_graph_name | element_table_alias | property_name |         property_expression          
@@ -516,7 +521,6 @@ SELECT * FROM information_schema.pg_element_table_properties ORDER BY property_g
  regression             | create_property_graph_tests | g4                  | t2                  | kk            | (k * 2)
  regression             | create_property_graph_tests | g4                  | t3                  | x             | x
  regression             | create_property_graph_tests | g4                  | t3                  | yy            | y
- regression             | create_property_graph_tests | g4                  | t3                  | zz            | z
  regression             | create_property_graph_tests | g5                  | t11                 | a             | a
  regression             | create_property_graph_tests | g5                  | t12                 | b             | b
  regression             | create_property_graph_tests | g5                  | t13                 | c             | c
@@ -545,7 +549,7 @@ SELECT * FROM information_schema.pg_element_table_properties ORDER BY property_g
  regression             | create_property_graph_tests | gt                  | v1                  | b             | b
  regression             | create_property_graph_tests | gt                  | v2                  | m             | m
  regression             | create_property_graph_tests | gt                  | v2                  | n             | n
-(58 rows)
+(57 rows)
 
 SELECT * FROM information_schema.pg_label_properties ORDER BY property_graph_name, label_name, property_name;
  property_graph_catalog |    property_graph_schema    | property_graph_name | label_name | property_name 
@@ -585,8 +589,6 @@ SELECT * FROM information_schema.pg_label_properties ORDER BY property_graph_nam
  regression             | create_property_graph_tests | g4                  | t2         | kk
  regression             | create_property_graph_tests | g4                  | t3l1       | x
  regression             | create_property_graph_tests | g4                  | t3l1       | yy
- regression             | create_property_graph_tests | g4                  | t3l2       | x
- regression             | create_property_graph_tests | g4                  | t3l2       | zz
  regression             | create_property_graph_tests | g5                  | t11        | a
  regression             | create_property_graph_tests | g5                  | t12        | b
  regression             | create_property_graph_tests | g5                  | t13        | c
@@ -615,7 +617,7 @@ SELECT * FROM information_schema.pg_label_properties ORDER BY property_graph_nam
  regression             | create_property_graph_tests | gt                  | v1         | b
  regression             | create_property_graph_tests | gt                  | v2         | m
  regression             | create_property_graph_tests | gt                  | v2         | n
-(65 rows)
+(63 rows)
 
 SELECT * FROM information_schema.pg_labels ORDER BY property_graph_name, label_name;
  property_graph_catalog |    property_graph_schema    | property_graph_name | label_name 
@@ -634,7 +636,6 @@ SELECT * FROM information_schema.pg_labels ORDER BY property_graph_name, label_n
  regression             | create_property_graph_tests | g4                  | t1
  regression             | create_property_graph_tests | g4                  | t2
  regression             | create_property_graph_tests | g4                  | t3l1
- regression             | create_property_graph_tests | g4                  | t3l2
  regression             | create_property_graph_tests | g5                  | t11
  regression             | create_property_graph_tests | g5                  | t12
  regression             | create_property_graph_tests | g5                  | t13
@@ -647,7 +648,7 @@ SELECT * FROM information_schema.pg_labels ORDER BY property_graph_name, label_n
  regression             | create_property_graph_tests | gt                  | e
  regression             | create_property_graph_tests | gt                  | v1
  regression             | create_property_graph_tests | gt                  | v2
-(27 rows)
+(26 rows)
 
 SELECT * FROM information_schema.pg_property_data_types ORDER BY property_graph_name, property_name;
  property_graph_catalog |    property_graph_schema    | property_graph_name | property_name |     data_type     | character_maximum_length | character_octet_length | character_set_catalog | character_set_schema | character_set_name | collation_catalog | collation_schema | collation_name | numeric_precision | numeric_precision_radix | numeric_scale | datetime_precision | interval_type | interval_precision | user_defined_type_catalog | user_defined_type_schema | user_defined_type_name | scope_catalog | scope_schema | scope_name | maximum_cardinality | dtd_identifier 
@@ -673,7 +674,6 @@ SELECT * FROM information_schema.pg_property_data_types ORDER BY property_graph_
  regression             | create_property_graph_tests | g4                  | t             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | t
  regression             | create_property_graph_tests | g4                  | x             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | x
  regression             | create_property_graph_tests | g4                  | yy            | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | yy
- regression             | create_property_graph_tests | g4                  | zz            | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | zz
  regression             | create_property_graph_tests | g5                  | a             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
  regression             | create_property_graph_tests | g5                  | b             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | b
  regression             | create_property_graph_tests | g5                  | c             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | c
@@ -695,7 +695,7 @@ SELECT * FROM information_schema.pg_property_data_types ORDER BY property_graph_
  regression             | create_property_graph_tests | gt                  | k2            | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | k2
  regression             | create_property_graph_tests | gt                  | m             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | m
  regression             | create_property_graph_tests | gt                  | n             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | n
-(43 rows)
+(42 rows)
 
 SELECT * FROM information_schema.pg_property_graph_privileges WHERE grantee LIKE 'regress%' ORDER BY property_graph_name, grantor, grantee, privilege_type;
        grantor       |       grantee       | property_graph_catalog |    property_graph_schema    | property_graph_name | privilege_type | is_grantable 
@@ -845,7 +845,7 @@ CREATE PROPERTY GRAPH create_property_graph_tests.g4
     VERTEX TABLES (
         t1 KEY (a) NO PROPERTIES,
         t2 KEY (i) PROPERTIES ((i + j) AS i_j, (k * 2) AS kk),
-        t3 KEY (x) LABEL t3l1 PROPERTIES (x, y AS yy) LABEL t3l2 PROPERTIES (x, z AS zz)
+        t3 KEY (x) LABEL t3l1 PROPERTIES (x, y AS yy)
     )
     EDGE TABLES (
         e1 KEY (a, i) SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (i) REFERENCES t2 (i) PROPERTIES (a, i, t),
diff --git a/src/test/regress/sql/create_property_graph.sql b/src/test/regress/sql/create_property_graph.sql
index 1bd2192d4a3..ef3a86b8936 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -81,6 +81,12 @@ CREATE PROPERTY GRAPH g4
 ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 ADD PROPERTIES (k * 2 AS kk);
 ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 DROP PROPERTIES (k);
 ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 DROP PROPERTIES (yy);  -- error
+-- Dropping a label should drop only orphaned properties. Dropping label t3l1
+-- should also drop zz because it is only associated with label t3l2. But x is
+-- not dropped, even if it is associated with t3l2, because it remains
+-- associated with t3l1. zz will not appear in the information schema queries
+-- outputs below, but x will.
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t3 DROP LABEL t3l2;
 
 CREATE TABLE t11 (a int PRIMARY KEY);
 CREATE TABLE t12 (b int PRIMARY KEY);
-- 
2.34.1

From 1868ec904d5bc65676079b488d1dc06e75c7669d Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Fri, 19 Jun 2026 17:08:06 +0530
Subject: [PATCH v20260703 02/14] Test concurrent modifications to a property
 graph

We hold an exclusive lock when modifying a property graph. Hence
modifications to a property graph will be serialized and there is no way
that conflicting modifications may render the property graph
inconsistent. However there is not testcase to verify this behaviour.
Add an isolation test for the same.

Author: Ashutosh Bapat <[email-masked]>
Discussion: https://www.postgresql.org/message-id/CAExHW5tMT71BwGzA7VCi3_6ZVYh-P2JCVrUW84DD-jw%2B7%2B3KuA%40mail.gmail.com
---
 .../expected/alter-propgraph-concurrently.out | 127 ++++++++++++++++++
 src/test/isolation/isolation_schedule         |   1 +
 .../specs/alter-propgraph-concurrently.spec   |  58 ++++++++
 3 files changed, 186 insertions(+)
 create mode 100644 src/test/isolation/expected/alter-propgraph-concurrently.out
 create mode 100644 src/test/isolation/specs/alter-propgraph-concurrently.spec

diff --git a/src/test/isolation/expected/alter-propgraph-concurrently.out b/src/test/isolation/expected/alter-propgraph-concurrently.out
new file mode 100644
index 00000000000..a75e75b3ca3
--- /dev/null
+++ b/src/test/isolation/expected/alter-propgraph-concurrently.out
@@ -0,0 +1,127 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1b s1dlabel s2dlabel s1c s2pgdef
+step s1b: BEGIN;
+step s1dlabel: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 DROP LABEL pgl1;
+step s2dlabel: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 DROP LABEL pgl2; <waiting ...>
+step s1c: COMMIT;
+step s2dlabel: <... completed>
+ERROR:  cannot drop the last label from element "pgt1"
+step s2pgdef: SELECT pg_get_propgraphdef('pgg1'::regclass);
+pg_get_propgraphdef                                                                                          
+-------------------------------------------------------------------------------------------------------------
+CREATE PROPERTY GRAPH public.pgg1
+    VERTEX TABLES (
+        pgt1 KEY (a) LABEL pgl2 PROPERTIES (a, b)
+    )
+(1 row)
+
+
+starting permutation: s1b s1dlabel s2dlabel s1r s2pgdef
+step s1b: BEGIN;
+step s1dlabel: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 DROP LABEL pgl1;
+step s2dlabel: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 DROP LABEL pgl2; <waiting ...>
+step s1r: ROLLBACK;
+step s2dlabel: <... completed>
+step s2pgdef: SELECT pg_get_propgraphdef('pgg1'::regclass);
+pg_get_propgraphdef                                                                                       
+----------------------------------------------------------------------------------------------------------
+CREATE PROPERTY GRAPH public.pgg1
+    VERTEX TABLES (
+        pgt1 KEY (a) LABEL pgl1 PROPERTIES (b)
+    )
+(1 row)
+
+
+starting permutation: s1b s1dlabel s2addp s1c s2pgdef
+step s1b: BEGIN;
+step s1dlabel: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 DROP LABEL pgl1;
+step s2addp: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 ALTER LABEL pgl1 ADD PROPERTIES (a + b AS c); <waiting ...>
+step s1c: COMMIT;
+step s2addp: <... completed>
+ERROR:  property graph "pgg1" element "pgt1" has no label "pgl1"
+step s2pgdef: SELECT pg_get_propgraphdef('pgg1'::regclass);
+pg_get_propgraphdef                                                                                          
+-------------------------------------------------------------------------------------------------------------
+CREATE PROPERTY GRAPH public.pgg1
+    VERTEX TABLES (
+        pgt1 KEY (a) LABEL pgl2 PROPERTIES (a, b)
+    )
+(1 row)
+
+
+starting permutation: s1b s1dlabel s2addp s1r s2pgdef
+step s1b: BEGIN;
+step s1dlabel: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 DROP LABEL pgl1;
+step s2addp: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 ALTER LABEL pgl1 ADD PROPERTIES (a + b AS c); <waiting ...>
+step s1r: ROLLBACK;
+step s2addp: <... completed>
+step s2pgdef: SELECT pg_get_propgraphdef('pgg1'::regclass);
+pg_get_propgraphdef                                                                                                                                  
+-----------------------------------------------------------------------------------------------------------------------------------------------------
+CREATE PROPERTY GRAPH public.pgg1
+    VERTEX TABLES (
+        pgt1 KEY (a) LABEL pgl1 PROPERTIES (b, (a + b) AS c) LABEL pgl2 PROPERTIES (a, b)
+    )
+(1 row)
+
+
+starting permutation: s1b s1delem s2alabel s1c s2pgdef
+step s1b: BEGIN;
+step s1delem: ALTER PROPERTY GRAPH pgg1 DROP VERTEX TABLES (pgt1);
+step s2alabel: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 ADD LABEL pgl3 PROPERTIES ALL COLUMNS; <waiting ...>
+step s1c: COMMIT;
+step s2alabel: <... completed>
+ERROR:  property graph "pgg1" has no element with alias "pgt1"
+step s2pgdef: SELECT pg_get_propgraphdef('pgg1'::regclass);
+pg_get_propgraphdef              
+---------------------------------
+CREATE PROPERTY GRAPH public.pgg1
+(1 row)
+
+
+starting permutation: s1b s1delem s2alabel s1r s2pgdef
+step s1b: BEGIN;
+step s1delem: ALTER PROPERTY GRAPH pgg1 DROP VERTEX TABLES (pgt1);
+step s2alabel: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 ADD LABEL pgl3 PROPERTIES ALL COLUMNS; <waiting ...>
+step s1r: ROLLBACK;
+step s2alabel: <... completed>
+step s2pgdef: SELECT pg_get_propgraphdef('pgg1'::regclass);
+pg_get_propgraphdef                                                                                                                                                 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------
+CREATE PROPERTY GRAPH public.pgg1
+    VERTEX TABLES (
+        pgt1 KEY (a) LABEL pgl1 PROPERTIES (b) LABEL pgl2 PROPERTIES (a, b) LABEL pgl3 PROPERTIES (a, b)
+    )
+(1 row)
+
+
+starting permutation: s1b s1delem s2addp s1c s2pgdef
+step s1b: BEGIN;
+step s1delem: ALTER PROPERTY GRAPH pgg1 DROP VERTEX TABLES (pgt1);
+step s2addp: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 ALTER LABEL pgl1 ADD PROPERTIES (a + b AS c); <waiting ...>
+step s1c: COMMIT;
+step s2addp: <... completed>
+ERROR:  property graph "pgg1" has no element with alias "pgt1"
+step s2pgdef: SELECT pg_get_propgraphdef('pgg1'::regclass);
+pg_get_propgraphdef              
+---------------------------------
+CREATE PROPERTY GRAPH public.pgg1
+(1 row)
+
+
+starting permutation: s1b s1delem s2addp s1r s2pgdef
+step s1b: BEGIN;
+step s1delem: ALTER PROPERTY GRAPH pgg1 DROP VERTEX TABLES (pgt1);
+step s2addp: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 ALTER LABEL pgl1 ADD PROPERTIES (a + b AS c); <waiting ...>
+step s1r: ROLLBACK;
+step s2addp: <... completed>
+step s2pgdef: SELECT pg_get_propgraphdef('pgg1'::regclass);
+pg_get_propgraphdef                                                                                                                                  
+-----------------------------------------------------------------------------------------------------------------------------------------------------
+CREATE PROPERTY GRAPH public.pgg1
+    VERTEX TABLES (
+        pgt1 KEY (a) LABEL pgl1 PROPERTIES (b, (a + b) AS c) LABEL pgl2 PROPERTIES (a, b)
+    )
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index b8ebe92553c..13829724a11 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -128,3 +128,4 @@ test: matview-write-skew
 test: lock-nowait
 test: for-portion-of
 test: ddl-dependency-locking
+test: alter-propgraph-concurrently
diff --git a/src/test/isolation/specs/alter-propgraph-concurrently.spec b/src/test/isolation/specs/alter-propgraph-concurrently.spec
new file mode 100644
index 00000000000..50b7e0efe05
--- /dev/null
+++ b/src/test/isolation/specs/alter-propgraph-concurrently.spec
@@ -0,0 +1,58 @@
+# ALTER PROPERTY GRAPH - concurrent
+#
+# Verify that concurrent modifications to the property graph don't end up in an
+# inconsistent state. For example, adding properties to a label being dropped
+# should not be allowed because otherwise we will end up with an orphaned
+# property in the property graph.
+
+setup
+{
+ CREATE TABLE pgt1 (a int, b int);
+ CREATE PROPERTY GRAPH pgg1
+   VERTEX TABLES (pgt1 KEY (a)
+                  LABEL pgl1 PROPERTIES (b)
+                  LABEL pgl2 PROPERTIES (a, b));
+}
+
+teardown
+{
+ DROP PROPERTY GRAPH pgg1;
+ DROP TABLE pgt1;
+}
+
+session s1
+step s1b	{ BEGIN; }
+step s1dlabel	{ ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 DROP LABEL pgl1; }
+step s1delem { ALTER PROPERTY GRAPH pgg1 DROP VERTEX TABLES (pgt1); }
+step s1c	{ COMMIT; }
+step s1r	{ ROLLBACK; }
+
+session s2
+step s2pgdef { SELECT pg_get_propgraphdef('pgg1'::regclass); }
+step s2dlabel	{ ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 DROP LABEL pgl2; }
+step s2addp { ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 ALTER LABEL pgl1 ADD PROPERTIES (a + b AS c); }
+step s2alabel { ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 ADD LABEL pgl3 PROPERTIES ALL COLUMNS; }
+
+# s2dlabel fails since by then pgl2 is the only remaining label on pgt1
+permutation s1b s1dlabel s2dlabel s1c s2pgdef
+
+# s2dlabel succeeds since rollback leaves pgl1 behind
+permutation s1b s1dlabel s2dlabel s1r s2pgdef
+
+# s2addp fails since by then pgl1 is gone
+permutation s1b s1dlabel s2addp s1c s2pgdef
+
+# s2addp succeeds since the rollback leaves pgl1 behind
+permutation s1b s1dlabel s2addp s1r s2pgdef
+
+# s2alabel fails since by then pgt1 is gone from the graph
+permutation s1b s1delem s2alabel s1c s2pgdef
+
+# s2alabel succeeds since rollback leaves pgt1 in the graph
+permutation s1b s1delem s2alabel s1r s2pgdef
+
+# s2addp fails since by then pgt1 is gone from the graph
+permutation s1b s1delem s2addp s1c s2pgdef
+
+# s2addp succeeds since rollback leaves pgt1 in the graph
+permutation s1b s1delem s2addp s1r s2pgdef
-- 
2.34.1

From b2754115b76231183ded49f608a9db1f806c10e4 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Tue, 28 Apr 2026 12:43:40 +0530
Subject: [PATCH v20260703 04/14] Dropping a property not associated with the
 given label

When dropping a property by name from a label, the code checked only
whether the property existed in the graph's property catalog. It did not
verify that the property was actually associated with the given label,
resulting in passing InvalidOid to performDeletion(). Fix it by
explicilty checking the label property association.

While at it also rearrange the code so as to avoid multiple ereport
calls for the same error in the same block.

Reported by: Chao Li <[email-masked]>
Author: Chao Li <[email-masked]>
Reviewed by: Ashutosh Bapat <[email-masked]>
Discussion: https://postgr.es/m/[email-masked]
---
 src/backend/commands/propgraphcmds.c          | 47 ++++++++-----------
 .../expected/create_property_graph.out        |  2 +
 .../regress/sql/create_property_graph.sql     |  1 +
 3 files changed, 22 insertions(+), 28 deletions(-)

diff --git a/src/backend/commands/propgraphcmds.c b/src/backend/commands/propgraphcmds.c
index 4c0fe763b5c..510cc1ccc03 100644
--- a/src/backend/commands/propgraphcmds.c
+++ b/src/backend/commands/propgraphcmds.c
@@ -1611,7 +1611,7 @@ AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt)
 		Oid			peoid;
 		Oid			pgerelid;
 		Oid			labeloid;
-		Oid			ellabeloid;
+		Oid			ellabeloid = InvalidOid;
 
 		if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX)
 			peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1);
@@ -1622,17 +1622,11 @@ AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt)
 								   Anum_pg_propgraph_label_oid,
 								   ObjectIdGetDatum(pgrelid),
 								   CStringGetDatum(stmt->alter_label));
-		if (!labeloid)
-			ereport(ERROR,
-					errcode(ERRCODE_UNDEFINED_OBJECT),
-					errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
-						   get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label),
-					parser_errposition(pstate, -1));
-
-		ellabeloid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL,
-									 Anum_pg_propgraph_element_label_oid,
-									 ObjectIdGetDatum(peoid),
-									 ObjectIdGetDatum(labeloid));
+		if (labeloid)
+			ellabeloid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL,
+										 Anum_pg_propgraph_element_label_oid,
+										 ObjectIdGetDatum(peoid),
+										 ObjectIdGetDatum(labeloid));
 		if (!ellabeloid)
 			ereport(ERROR,
 					errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -1653,7 +1647,7 @@ AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt)
 	{
 		Oid			peoid;
 		Oid			labeloid;
-		Oid			ellabeloid;
+		Oid			ellabeloid = InvalidOid;
 		ObjectAddress obj;
 
 		if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX)
@@ -1665,17 +1659,11 @@ AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt)
 								   Anum_pg_propgraph_label_oid,
 								   ObjectIdGetDatum(pgrelid),
 								   CStringGetDatum(stmt->alter_label));
-		if (!labeloid)
-			ereport(ERROR,
-					errcode(ERRCODE_UNDEFINED_OBJECT),
-					errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
-						   get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label),
-					parser_errposition(pstate, -1));
-
-		ellabeloid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL,
-									 Anum_pg_propgraph_element_label_oid,
-									 ObjectIdGetDatum(peoid),
-									 ObjectIdGetDatum(labeloid));
+		if (labeloid)
+			ellabeloid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL,
+										 Anum_pg_propgraph_element_label_oid,
+										 ObjectIdGetDatum(peoid),
+										 ObjectIdGetDatum(labeloid));
 
 		if (!ellabeloid)
 			ereport(ERROR,
@@ -1688,21 +1676,24 @@ AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt)
 		{
 			char	   *propname = strVal(lfirst(lc));
 			Oid			propoid;
-			Oid			plpoid;
+			Oid			plpoid = InvalidOid;
 
 			propoid = GetSysCacheOid2(PROPGRAPHPROPNAME,
 									  Anum_pg_propgraph_property_oid,
 									  ObjectIdGetDatum(pgrelid),
 									  CStringGetDatum(propname));
-			if (!propoid)
+			if (propoid)
+				plpoid = GetSysCacheOid2(PROPGRAPHLABELPROP,
+										 Anum_pg_propgraph_label_property_oid,
+										 ObjectIdGetDatum(ellabeloid),
+										 ObjectIdGetDatum(propoid));
+			if (!plpoid)
 				ereport(ERROR,
 						errcode(ERRCODE_UNDEFINED_OBJECT),
 						errmsg("property graph \"%s\" element \"%s\" label \"%s\" has no property \"%s\"",
 							   get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label, propname),
 						parser_errposition(pstate, -1));
 
-			plpoid = GetSysCacheOid2(PROPGRAPHLABELPROP, Anum_pg_propgraph_label_property_oid, ObjectIdGetDatum(ellabeloid), ObjectIdGetDatum(propoid));
-
 			ObjectAddressSet(obj, PropgraphLabelPropertyRelationId, plpoid);
 			performDeletion(&obj, stmt->drop_behavior, 0);
 		}
diff --git a/src/test/regress/expected/create_property_graph.out b/src/test/regress/expected/create_property_graph.out
index 5930d844319..7f12697bbd5 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -89,6 +89,8 @@ CREATE PROPERTY GRAPH g4
     );
 ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 ADD PROPERTIES (k * 2 AS kk);
 ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 DROP PROPERTIES (k);
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 DROP PROPERTIES (yy);  -- error
+ERROR:  property graph "g4" element "t2" label "t2" has no property "yy"
 CREATE TABLE t11 (a int PRIMARY KEY);
 CREATE TABLE t12 (b int PRIMARY KEY);
 CREATE TABLE t13 (
diff --git a/src/test/regress/sql/create_property_graph.sql b/src/test/regress/sql/create_property_graph.sql
index a4285ef8846..171260ac69f 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -80,6 +80,7 @@ CREATE PROPERTY GRAPH g4
 
 ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 ADD PROPERTIES (k * 2 AS kk);
 ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 DROP PROPERTIES (k);
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 DROP PROPERTIES (yy);  -- error
 
 CREATE TABLE t11 (a int PRIMARY KEY);
 CREATE TABLE t12 (b int PRIMARY KEY);
-- 
2.34.1

From 9758fdad9c71e36e26a10350ad01102f7288d42e Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Thu, 2 Jul 2026 15:27:15 +0530
Subject: [PATCH v20260703 03/14] Report duplicate property and label names
 with a proper error

Adding a property with the same name multiple times to a label on an
element, either within a single PROPERTIES clause or across statements
via ALTER PROPERTY GRAPH ... ADD PROPERTIES, previously failed with a
unique-index violation on pg_propgraph_label_property. The same class
of bug existed for labels: listing the same label multiple times on one
element in CREATE PROPERTY GRAPH, or adding a label to an element that
already has it via ALTER PROPERTY GRAPH ... ADD LABEL, failed with a
unique-index violation on pg_propgraph_element_label. Detect the
duplicates up front and raise a friendlier error in both cases.

For properties, the cross-statement duplicate is caught by a syscache
probe before insertion. The in-clause duplicate could have been caught
by the same probe if we issued a CommandCounterIncrement() between
property inserts, but that forces a catalog invalidation per property
purely to detect a condition we can check for free on the in-memory
target list. The list-based check is only needed when the properties
are listed explicitly; when they are derived from the table's
attributes, names are already unique.

For labels, a single syscache probe on pg_propgraph_element_label
suffices for both the in-clause and cross-statement cases because
insert_element_record() already issues CommandCounterIncrement()
between successive label inserts.

Add regression tests exercising both bugs, and extend the existing
alter-propgraph-concurrently isolation spec with permutations that add
the same property, and the same label, from two concurrent sessions;
the second session now sees the friendly duplicate-object error rather
than a unique-index violation.

Author: Ashutosh Bapat <[email-masked]>
Reported by: Noah Misch <[email-masked]>
Discussion: https://www.postgresql.org/message-id/[email-masked]
---
 src/backend/commands/propgraphcmds.c          | 37 ++++++++++-
 .../expected/alter-propgraph-concurrently.out | 66 +++++++++++++++++++
 .../specs/alter-propgraph-concurrently.spec   | 14 ++++
 .../expected/create_property_graph.out        | 14 ++++
 .../regress/sql/create_property_graph.sql     | 12 ++++
 5 files changed, 140 insertions(+), 3 deletions(-)

diff --git a/src/backend/commands/propgraphcmds.c b/src/backend/commands/propgraphcmds.c
index 62f91a08410..4c0fe763b5c 100644
--- a/src/backend/commands/propgraphcmds.c
+++ b/src/backend/commands/propgraphcmds.c
@@ -784,6 +784,13 @@ insert_label_record(Oid graphid, Oid peoid, const char *label)
 	/*
 	 * Insert into pg_propgraph_element_label
 	 */
+	if (SearchSysCacheExists2(PROPGRAPHELEMENTLABELELEMENTLABEL,
+							  ObjectIdGetDatum(peoid),
+							  ObjectIdGetDatum(labeloid)))
+		ereport(ERROR,
+				errcode(ERRCODE_DUPLICATE_OBJECT),
+				errmsg("label \"%s\" already exists", label));
+	else
 	{
 		Relation	rel;
 		Datum		values[Natts_pg_propgraph_element_label] = {0};
@@ -899,12 +906,30 @@ insert_property_records(Oid graphid, Oid ellabeloid, Oid pgerelid, const PropGra
 	tp = transformTargetList(pstate, proplist, EXPR_KIND_PROPGRAPH_PROPERTY);
 	assign_expr_collations(pstate, (Node *) tp);
 
-	foreach(lc, tp)
+	/*
+	 * When properties are derived from the table's attributes, names are
+	 * already unique. Reject duplicate property names within an explicit
+	 * PROPERTIES clause. Do this after transformTargetList() so that any
+	 * names derived by transformTargetList() are considered.
+	 */
+	if (!properties->all)
 	{
-		TargetEntry *te = lfirst_node(TargetEntry, lc);
+		List	   *seen = NIL;
 
-		insert_property_record(graphid, ellabeloid, pgerelid, te->resname, te->expr);
+		foreach_node(TargetEntry, te, tp)
+		{
+			String	   *name = makeString(te->resname);
+
+			if (list_member(seen, name))
+				ereport(ERROR,
+						errcode(ERRCODE_DUPLICATE_OBJECT),
+						errmsg("property \"%s\" specified more than once", te->resname));
+			seen = lappend(seen, name);
+		}
 	}
+
+	foreach_node(TargetEntry, te, tp)
+		insert_property_record(graphid, ellabeloid, pgerelid, te->resname, te->expr);
 }
 
 /*
@@ -1001,6 +1026,12 @@ insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *pr
 	/*
 	 * Insert into pg_propgraph_label_property
 	 */
+	if (SearchSysCacheExists2(PROPGRAPHLABELPROP, ObjectIdGetDatum(ellabeloid),
+							  ObjectIdGetDatum(propoid)))
+		ereport(ERROR,
+				errcode(ERRCODE_DUPLICATE_OBJECT),
+				errmsg("property \"%s\" already exists", propname));
+	else
 	{
 		Relation	rel;
 		Datum		values[Natts_pg_propgraph_label_property] = {0};
diff --git a/src/test/isolation/expected/alter-propgraph-concurrently.out b/src/test/isolation/expected/alter-propgraph-concurrently.out
index a75e75b3ca3..5bc8c58e298 100644
--- a/src/test/isolation/expected/alter-propgraph-concurrently.out
+++ b/src/test/isolation/expected/alter-propgraph-concurrently.out
@@ -125,3 +125,69 @@ CREATE PROPERTY GRAPH public.pgg1
     )
 (1 row)
 
+
+starting permutation: s1b s1addp s2addp s1c s2pgdef
+step s1b: BEGIN;
+step s1addp: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 ALTER LABEL pgl1 ADD PROPERTIES (a + b AS c);
+step s2addp: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 ALTER LABEL pgl1 ADD PROPERTIES (a + b AS c); <waiting ...>
+step s1c: COMMIT;
+step s2addp: <... completed>
+ERROR:  property "c" already exists
+step s2pgdef: SELECT pg_get_propgraphdef('pgg1'::regclass);
+pg_get_propgraphdef                                                                                                                                  
+-----------------------------------------------------------------------------------------------------------------------------------------------------
+CREATE PROPERTY GRAPH public.pgg1
+    VERTEX TABLES (
+        pgt1 KEY (a) LABEL pgl1 PROPERTIES (b, (a + b) AS c) LABEL pgl2 PROPERTIES (a, b)
+    )
+(1 row)
+
+
+starting permutation: s1b s1addp s2addp s1r s2pgdef
+step s1b: BEGIN;
+step s1addp: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 ALTER LABEL pgl1 ADD PROPERTIES (a + b AS c);
+step s2addp: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 ALTER LABEL pgl1 ADD PROPERTIES (a + b AS c); <waiting ...>
+step s1r: ROLLBACK;
+step s2addp: <... completed>
+step s2pgdef: SELECT pg_get_propgraphdef('pgg1'::regclass);
+pg_get_propgraphdef                                                                                                                                  
+-----------------------------------------------------------------------------------------------------------------------------------------------------
+CREATE PROPERTY GRAPH public.pgg1
+    VERTEX TABLES (
+        pgt1 KEY (a) LABEL pgl1 PROPERTIES (b, (a + b) AS c) LABEL pgl2 PROPERTIES (a, b)
+    )
+(1 row)
+
+
+starting permutation: s1b s1alabel s2alabel s1c s2pgdef
+step s1b: BEGIN;
+step s1alabel: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 ADD LABEL pgl3 PROPERTIES ALL COLUMNS;
+step s2alabel: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 ADD LABEL pgl3 PROPERTIES ALL COLUMNS; <waiting ...>
+step s1c: COMMIT;
+step s2alabel: <... completed>
+ERROR:  label "pgl3" already exists
+step s2pgdef: SELECT pg_get_propgraphdef('pgg1'::regclass);
+pg_get_propgraphdef                                                                                                                                                 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------
+CREATE PROPERTY GRAPH public.pgg1
+    VERTEX TABLES (
+        pgt1 KEY (a) LABEL pgl1 PROPERTIES (b) LABEL pgl2 PROPERTIES (a, b) LABEL pgl3 PROPERTIES (a, b)
+    )
+(1 row)
+
+
+starting permutation: s1b s1alabel s2alabel s1r s2pgdef
+step s1b: BEGIN;
+step s1alabel: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 ADD LABEL pgl3 PROPERTIES ALL COLUMNS;
+step s2alabel: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 ADD LABEL pgl3 PROPERTIES ALL COLUMNS; <waiting ...>
+step s1r: ROLLBACK;
+step s2alabel: <... completed>
+step s2pgdef: SELECT pg_get_propgraphdef('pgg1'::regclass);
+pg_get_propgraphdef                                                                                                                                                 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------
+CREATE PROPERTY GRAPH public.pgg1
+    VERTEX TABLES (
+        pgt1 KEY (a) LABEL pgl1 PROPERTIES (b) LABEL pgl2 PROPERTIES (a, b) LABEL pgl3 PROPERTIES (a, b)
+    )
+(1 row)
+
diff --git a/src/test/isolation/specs/alter-propgraph-concurrently.spec b/src/test/isolation/specs/alter-propgraph-concurrently.spec
index 50b7e0efe05..61f7de92fed 100644
--- a/src/test/isolation/specs/alter-propgraph-concurrently.spec
+++ b/src/test/isolation/specs/alter-propgraph-concurrently.spec
@@ -24,6 +24,8 @@ session s1
 step s1b	{ BEGIN; }
 step s1dlabel	{ ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 DROP LABEL pgl1; }
 step s1delem { ALTER PROPERTY GRAPH pgg1 DROP VERTEX TABLES (pgt1); }
+step s1addp { ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 ALTER LABEL pgl1 ADD PROPERTIES (a + b AS c); }
+step s1alabel { ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 ADD LABEL pgl3 PROPERTIES ALL COLUMNS; }
 step s1c	{ COMMIT; }
 step s1r	{ ROLLBACK; }
 
@@ -56,3 +58,15 @@ permutation s1b s1delem s2addp s1c s2pgdef
 
 # s2addp succeeds since rollback leaves pgt1 in the graph
 permutation s1b s1delem s2addp s1r s2pgdef
+
+# s2addp fails since by then s1 has added the same property
+permutation s1b s1addp s2addp s1c s2pgdef
+
+# s2addp succeeds since rollback leaves pgl1 without the added property
+permutation s1b s1addp s2addp s1r s2pgdef
+
+# s2alabel fails since by then s1 has added the same label
+permutation s1b s1alabel s2alabel s1c s2pgdef
+
+# s2alabel succeeds since rollback leaves pgt1 without the added label
+permutation s1b s1alabel s2alabel s1r s2pgdef
diff --git a/src/test/regress/expected/create_property_graph.out b/src/test/regress/expected/create_property_graph.out
index f9960e5e0ae..5930d844319 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -200,6 +200,20 @@ CREATE PROPERTY GRAPH gx
     );
 DROP PROPERTY GRAPH gx;
 DROP TABLE t1x, t2x;
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) LABEL l1 PROPERTIES (a AS p, a AS p)  -- duplicate property on label
+    );
+ERROR:  property "p" specified more than once
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 ADD PROPERTIES (k * 2 AS i_j);  -- duplicate property on label
+ERROR:  property "i_j" already exists
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) LABEL l1 LABEL l1  -- duplicate label on element
+    );
+ERROR:  label "l1" already exists
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t3 ADD LABEL t3l1 NO PROPERTIES;  -- duplicate label on element
+ERROR:  label "t3l1" already exists
 CREATE PROPERTY GRAPH gx
     VERTEX TABLES (
         t1 KEY (a) LABEL l1 PROPERTIES (a, a AS aa),
diff --git a/src/test/regress/sql/create_property_graph.sql b/src/test/regress/sql/create_property_graph.sql
index b10d7191506..a4285ef8846 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -155,6 +155,18 @@ CREATE PROPERTY GRAPH gx
 DROP PROPERTY GRAPH gx;
 DROP TABLE t1x, t2x;
 
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) LABEL l1 PROPERTIES (a AS p, a AS p)  -- duplicate property on label
+    );
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 ADD PROPERTIES (k * 2 AS i_j);  -- duplicate property on label
+
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) LABEL l1 LABEL l1  -- duplicate label on element
+    );
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t3 ADD LABEL t3l1 NO PROPERTIES;  -- duplicate label on element
+
 CREATE PROPERTY GRAPH gx
     VERTEX TABLES (
         t1 KEY (a) LABEL l1 PROPERTIES (a, a AS aa),
-- 
2.34.1

From 285b5cc14ac83cec5bd973b0985f1ca9d4549b1e Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Fri, 19 Jun 2026 17:06:29 +0530
Subject: [PATCH v20260703 01/14] Prevent dropping the last label from a
 property graph element

Per SQL/PGQ standard, every graph element must have at least one label.
When dropping a label from a graph element, ensure that there exists at
least one other label on the element. If the label being dropped is the
only label on the element, raise an error.

We hold an exclusive lock when modifying a property graph. Hence the
label will not be dropped even when multiple labels are being dropped
concurrently.

Reported By: Satyanarayana Narlapuram <[email-masked]>
Author: Ashutosh Bapat <[email-masked]>
Author: Satyanarayana Narlapuram <[email-masked]>
Reviewed by: Ashutosh Bapat <[email-masked]>
Discussion: https://www.postgresql.org/message-id/CAHg+QDeP=[email-masked]
---
 doc/src/sgml/ref/alter_property_graph.sgml    |  4 +-
 src/backend/commands/propgraphcmds.c          | 52 +++++++++++++++++--
 .../expected/create_property_graph.out        |  7 +++
 .../regress/sql/create_property_graph.sql     |  5 ++
 4 files changed, 62 insertions(+), 6 deletions(-)

diff --git a/doc/src/sgml/ref/alter_property_graph.sgml b/doc/src/sgml/ref/alter_property_graph.sgml
index f517f2b2d7a..2fd0c138e12 100644
--- a/doc/src/sgml/ref/alter_property_graph.sgml
+++ b/doc/src/sgml/ref/alter_property_graph.sgml
@@ -99,7 +99,9 @@ ALTER PROPERTY GRAPH [ IF EXISTS ] <replaceable class="parameter">name</replacea
      <term><literal>ALTER {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE ... DROP LABEL</literal></term>
      <listitem>
       <para>
-       This form removes a label from an existing vertex or edge table.
+       This form removes a label from an existing vertex or edge table. The
+       last label on an element table cannot be dropped; every vertex or edge
+       table must have at least one label.
       </para>
      </listitem>
     </varlistentry>
diff --git a/src/backend/commands/propgraphcmds.c b/src/backend/commands/propgraphcmds.c
index cc516e27020..62f91a08410 100644
--- a/src/backend/commands/propgraphcmds.c
+++ b/src/backend/commands/propgraphcmds.c
@@ -1491,8 +1491,14 @@ AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt)
 	{
 		Oid			peoid;
 		Oid			labeloid;
-		Oid			ellabeloid;
+		Oid			ellabeloid = InvalidOid;
 		ObjectAddress obj;
+		Relation	ellabelrel;
+		SysScanDesc ellabelscan;
+		ScanKeyData ellabelkey[1];
+		int			nlabels;
+		HeapTuple	tuple;
+
 
 		if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX)
 			peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1);
@@ -1510,10 +1516,35 @@ AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt)
 						   get_rel_name(pgrelid), stmt->element_alias, stmt->drop_label),
 					parser_errposition(pstate, -1));
 
-		ellabeloid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL,
-									 Anum_pg_propgraph_element_label_oid,
-									 ObjectIdGetDatum(peoid),
-									 ObjectIdGetDatum(labeloid));
+		/*
+		 * Is the given label associated with the element? Is this the only
+		 * label associated with the element? Scan the
+		 * pg_propgraph_element_label table to find answers to these
+		 * questions. Stop scanning midway when both answers come out to be
+		 * "yes".
+		 */
+		ellabelrel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+		ScanKeyInit(&ellabelkey[0],
+					Anum_pg_propgraph_element_label_pgelelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(peoid));
+		ellabelscan = systable_beginscan(ellabelrel, PropgraphElementLabelElementLabelIndexId,
+										 true, NULL, 1, ellabelkey);
+		nlabels = 0;
+		while (HeapTupleIsValid(tuple = systable_getnext(ellabelscan)))
+		{
+			Form_pg_propgraph_element_label ellabelform = (Form_pg_propgraph_element_label) GETSTRUCT(tuple);
+
+			nlabels++;
+
+			if (ellabelform->pgellabelid == labeloid)
+				ellabeloid = ellabelform->oid;
+
+			if (nlabels > 1 && ellabeloid)
+				break;
+		}
+		systable_endscan(ellabelscan);
+		table_close(ellabelrel, AccessShareLock);
 
 		if (!ellabeloid)
 			ereport(ERROR,
@@ -1522,6 +1553,17 @@ AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt)
 						   get_rel_name(pgrelid), stmt->element_alias, stmt->drop_label),
 					parser_errposition(pstate, -1));
 
+		/*
+		 * Prevent dropping the last label from an element. Every element must
+		 * have at least one label associated with it.
+		 */
+		if (nlabels == 1)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot drop the last label from element \"%s\"",
+							stmt->element_alias),
+					 errhint("Every element must have at least one label.")));
+
 		ObjectAddressSet(obj, PropgraphElementLabelRelationId, ellabeloid);
 		performDeletion(&obj, stmt->drop_behavior, 0);
 
diff --git a/src/test/regress/expected/create_property_graph.out b/src/test/regress/expected/create_property_graph.out
index 2f06c7ce5a8..f9960e5e0ae 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -57,6 +57,13 @@ ALTER PROPERTY GRAPH g3
 ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l3x;  -- error
 ERROR:  property graph "g3" element "t3" has no label "t3l3x"
 ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l3;
+-- Test that the last label on an element cannot be dropped
+ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l2;
+ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l1;  -- error: last label
+ERROR:  cannot drop the last label from element "t3"
+HINT:  Every element must have at least one label.
+-- Restore the dropped label for further tests
+ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 ADD LABEL t3l2 PROPERTIES ALL COLUMNS;
 ALTER PROPERTY GRAPH g3 DROP VERTEX TABLES (t2);  -- fail
 ERROR:  cannot drop vertex t2 of property graph g3 because other objects depend on it
 DETAIL:  edge e1 of property graph g3 depends on vertex t2 of property graph g3
diff --git a/src/test/regress/sql/create_property_graph.sql b/src/test/regress/sql/create_property_graph.sql
index 85088ae632c..b10d7191506 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -52,6 +52,11 @@ ALTER PROPERTY GRAPH g3
         ADD LABEL t3l3 PROPERTIES ALL COLUMNS;
 ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l3x;  -- error
 ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l3;
+-- Test that the last label on an element cannot be dropped
+ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l2;
+ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l1;  -- error: last label
+-- Restore the dropped label for further tests
+ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 ADD LABEL t3l2 PROPERTIES ALL COLUMNS;
 ALTER PROPERTY GRAPH g3 DROP VERTEX TABLES (t2);  -- fail
 ALTER PROPERTY GRAPH g3 DROP VERTEX TABLES (t2) CASCADE;
 ALTER PROPERTY GRAPH g3 DROP EDGE TABLES (e2);

base-commit: a5422fe3bd7ecd9c64cf4a8acf5f510b8da676c5
-- 
2.34.1

---

From: Ashutosh Bapat <[email-masked]>
Date: Wed, 8 Jul 2026 12:24:28 +0530

Hi,
Peter has committed several other patches. Here are rebased patches
for this thread.

On Fri, Jul 3, 2026 at 10:11 AM Ashutosh Bapat
<[email-masked]> wrote:

Issue 1

> > fixes the problems. Thanks for the report, that was quite subtle.

That's 0003 patch attached here.

Issue 2
> [2] and discussion can continue there.

The patch in that thread was committed. Attaching fix for this issue
as 0002 patch.

Issue 3
> > CommandCounterIncrement().

When investigating this issue I found that duplicate labels also have
the same issue. Fixed both the issues in patch 0001.

I have added a noop else in insert_label_record() after ereport()
since it ties the following code block well with the if condition. But
we can remove it if it's not improving readability.

There's slight inconsistency in the error messages for duplicate
labels and properties. In case of labels, we always report "label ...
already exists". But in case of properties we report two different
errors. We report "property \"%s\" specified more than once" when
duplicate properties appear in the same command. But we report
"property \"%s\" already exists" when duplicate properties appear
across commands. I think the difference is minor enough that we can
entirely ignore it and avoid spending more code on it. If we feel
strongly about being consistent, either we can detect duplicate labels
in the same command and report "label ... specified more than once" or
we can make the same error being reported in case of properties in
both the cases.

-- 
Best Wishes,
Ashutosh Bapat
From be38b872ec90bebdeed6fdf1a035378dc8f6e944 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Thu, 2 Jul 2026 10:33:13 +0530
Subject: [PATCH v20260708 3/7] replace_property_refs() ignores the root of
 expression tree

diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index 7db17bec312..cdb1f4c0dca 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -1159,7 +1159,7 @@ replace_property_refs(Oid propgraphid, Node *node, const List *mappings)
 	context.mappings = mappings;
 	context.propgraphid = propgraphid;

 /*
index bd603ea0d77..a3d78a7ac43 100644

@@ -864,12 +871,12 @@ CREATE TABLE cv2 () INHERITS (pv);
@@ -887,6 +894,19 @@ SELECT * FROM GRAPH_TABLE (g3 MATCH (s IS pv)-[e IS pe]->(d IS pv) COLUMNS (s.va
   30 | 300 |  10
 (3 rows)
 
 -- temporary property graph
index 5c8049ed242..6aacc2d4aa5 100644
@@ -483,12 +485,12 @@ CREATE TABLE cv2 () INHERITS (pv);
@@ -499,6 +501,9 @@ CREATE PROPERTY GRAPH g3
 -- temporary property graph

From 4195478723038fb6f9fb17e4a166dc5c0c0f690b Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Wed, 29 Apr 2026 19:07:13 +0530
Subject: [PATCH v20260708 2/7] Resolve unknown-type literals in property
 expressions

When a string literal is provided as a property expression, the data
type of the property was set to "unknown", which may lead to various
failures when the property is used in GRAPH_TABLE or when its data type
is compared against other properties with the same name. To fix this,
call resolveTargetListUnknowns() on the targetlist of new properties
being added to resolve unknown type literals.

Reported by: Noah Misch <[email-masked]>
Author: Ashutosh Bapat <[email-masked]>
Discussion: https://www.postgresql.org/message-id/[email-masked]
---
 src/backend/commands/propgraphcmds.c          |  2 +
 .../expected/create_property_graph.out        | 37 +++++++++++++++----
 .../regress/sql/create_property_graph.sql     |  5 +++
 3 files changed, 36 insertions(+), 8 deletions(-)

index 107c0449302..9c9f4f2b299 100644

 	/*
index f8730e96340..4a8b3d91219 100644
@@ -351,6 +351,10 @@ CREATE PROPERTY GRAPH gt
@@ -361,8 +365,9 @@ SELECT * FROM information_schema.property_graphs ORDER BY property_graph_name;
 
@@ -387,10 +392,11 @@ SELECT * FROM information_schema.pg_element_tables ORDER BY property_graph_name,
 
@@ -421,11 +427,12 @@ SELECT * FROM information_schema.pg_element_table_key_columns ORDER BY property_
 
@@ -475,10 +482,11 @@ SELECT * FROM information_schema.pg_element_table_labels ORDER BY property_graph
-(25 rows)
+(26 rows)
 
@@ -529,6 +537,10 @@ SELECT * FROM information_schema.pg_element_table_properties ORDER BY property_g
@@ -536,7 +548,7 @@ SELECT * FROM information_schema.pg_element_table_properties ORDER BY property_g
-(53 rows)
+(57 rows)
 
@@ -593,6 +605,10 @@ SELECT * FROM information_schema.pg_label_properties ORDER BY property_graph_nam
@@ -600,7 +616,7 @@ SELECT * FROM information_schema.pg_label_properties ORDER BY property_graph_nam
-(59 rows)
+(63 rows)
 
-(25 rows)
+(26 rows)
 
@@ -666,6 +683,10 @@ SELECT * FROM information_schema.pg_property_data_types ORDER BY property_graph_
@@ -673,7 +694,7 @@ SELECT * FROM information_schema.pg_property_data_types ORDER BY property_graph_
-(38 rows)
+(42 rows)
 
 SELECT * FROM information_schema.pg_property_graph_privileges WHERE grantee LIKE 'regress%' ORDER BY property_graph_name, grantor, grantee, privilege_type;
        grantor       |       grantee       | property_graph_catalog |    property_graph_schema    | property_graph_name | privilege_type | is_grantable 
diff --git a/src/test/regress/sql/create_property_graph.sql b/src/test/regress/sql/create_property_graph.sql
index 72885be949a..1667896f558 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -290,6 +290,11 @@ CREATE PROPERTY GRAPH gt
             DESTINATION KEY (k2) REFERENCES v2(m)
     );

From cb435e0f7d8f5936a2ec05bf8b9f9eaa2bd0b008 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Thu, 2 Jul 2026 15:27:15 +0530
Subject: [PATCH v20260708 1/7] Report duplicate property and label names with
 a proper error

Author: Ashutosh Bapat <[email-masked]>
Reported by: Noah Misch <[email-masked]>
Discussion: https://www.postgresql.org/message-id/[email-masked]
---
 src/backend/commands/propgraphcmds.c          | 37 +++++++++++++++++--
 .../expected/create_property_graph.out        | 14 +++++++
 .../regress/sql/create_property_graph.sql     | 12 ++++++
 3 files changed, 60 insertions(+), 3 deletions(-)

index 6939a448895..107c0449302 100644
 
-	foreach(lc, tp)
+	/*

 /*
index 3638f7f9f68..f8730e96340 100644
@@ -207,6 +207,20 @@ CREATE PROPERTY GRAPH gx
index 5262971342c..72885be949a 100644
@@ -161,6 +161,18 @@ CREATE PROPERTY GRAPH gx
 DROP PROPERTY GRAPH gx;
 DROP TABLE t1x, t2x;

base-commit: 8daeaa9b642c3c23cbc516da80d50aade6f4dc07
-- 
2.34.1

---

From: Peter Eisentraut <[email-masked]>
Date: Wed, 8 Jul 2026 11:05:40 +0200

> That's 0003 patch attached here.

committed

> The patch in that thread was committed. Attaching fix for this issue

committed

> we can make the same error being reported in case of properties in

I would like this code to be organized differently.  Note that the 
existing insert_*_record functions don't do any error checking; they are 
just there to make catalog modifications.  There are various check_* 
functions that pretty much correspond to syntax rule checks, but these 
are run after the catalog changes, hence the present issue.  Maybe we 
should have a set of "pre-check" functions in addition?

---

From: Ashutosh Bapat <[email-masked]>
Date: Wed, 8 Jul 2026 18:59:33 +0530

> should have a set of "pre-check" functions in addition?

I chose to perform the checks in insert_*_record() functions to keep
them at a central place instead of dispersing all over like the
check_* functions. The latter need to peform their checks considering
the overall shape of the property graph, however, the specification
checks are fairly local - like duplicate property or label names
specified in the same command or duplicate labels being inserted -
they won't usually need checks across labels or elements for example.
I am afraid we might have to sprinkle pre_check_* functions at
multiple places - thus leading to a risk of missing places as this
code evolves.

Do you expect insert_element_record() to perform sanity checks of
labels or insert_label_record() to perform sanity checks on
properties? Or do you expect a hierarchy of pre_check_ functions
cascading from elements to labels to properties?

-- 
Best Wishes,
Ashutosh Bapat