On Tue, May 12, 2026 at 12:31 PM Ashutosh Bapat
<[email protected]> wrote:
>
> On Tue, May 12, 2026 at 7:05 AM Ashutosh Bapat
> <[email protected]> wrote:
> >
> > On Mon, May 4, 2026 at 8:16 PM Peter Eisentraut <[email protected]> 
> > wrote:
> > >
> > > On 28.04.26 17:02, Ashutosh Bapat wrote:
> > > > We are looking up element label catalogs twice in this patch - first
> > > > to find the label to be dropped and then to find the number of labels
> > > > associated with the given element. I combined these two into a single
> > > > while loop.
> > >
> > > That looks okay, but I think the names of the local variables are now a
> > > bit off.  I would expect elrel and elscan to refer to
> > > pg_propgraph_element, not pg_propgraph_element_label.  Maybe use
> > > ellabelrel etc.
> >
> > Done.
> >
> > >
> > > Also, I think this code needs to think a bit about locking to handle the
> > > situation where more than one DROP LABEL operation happens concurrently.
> > >
> >
> > AlterPropGraph already takes ShareRowExclusiveLock at the beginning so
> > only one label can be dropped at a time. I have added an isolation
> > test to test the scenario. We could further add some more tests to
> > make sure that properties can not be added to a label being dropped,
> > adding label to an element being dropped, adding label to an element
> > being added etc. Would that be an overkill?
>
> Here's the patchset without the extra tests.

I decided to go ahead and added those extra tests as a separate patch.
They don't cover all the possible concurrent modifications, but the
ones which may render a property or a label orphan.

0001 - patch to avoid dropping last label from an element
0002 - patch to test concurrent ALTER PROPERTY GRAPH
0003 - patch from [1] which would conflict with 0001, if not included here.

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

-- 
Best Wishes,
Ashutosh Bapat
From 85de3fc21cd3678a5c328e6e6375b37731d0df99 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email protected]>
Date: Tue, 28 Apr 2026 12:43:40 +0530
Subject: [PATCH v20260619 03/11] 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 protected]>
Author: Chao Li <[email protected]>
Reviewed by: Ashutosh Bapat <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
 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 62f91a08410..8868bd7576a 100644
--- a/src/backend/commands/propgraphcmds.c
+++ b/src/backend/commands/propgraphcmds.c
@@ -1580,7 +1580,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);
@@ -1591,17 +1591,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),
@@ -1622,7 +1616,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)
@@ -1634,17 +1628,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,
@@ -1657,21 +1645,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 e074c3cbd69..a1cf6e0f0b3 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 b10d7191506..3b27d4170bb 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 b8839a128dc1074d973c2b217da25c437ea7f3bf Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email protected]>
Date: Fri, 19 Jun 2026 17:08:06 +0530
Subject: [PATCH v20260619 02/11] 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 protected]>
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 3249b14557f795568b7f62d22a8305a3c7e40b04 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email protected]>
Date: Fri, 19 Jun 2026 17:06:29 +0530
Subject: [PATCH v20260619 01/11] 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 protected]>
Author: Ashutosh Bapat <[email protected]>
Author: Satyanarayana Narlapuram <[email protected]>
Reviewed by: Ashutosh Bapat <[email protected]>
Discussion: https://www.postgresql.org/message-id/CAHg+QDeP=mthtv48r23zkmy1sbmckz_l7-z5zknyyw+k0x-...@mail.gmail.com
---
 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 86c0957dcc7..e074c3cbd69 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: dc5116780846951a409d788479a9e9fa6edd9f07
-- 
2.34.1

Reply via email to