public inbox for [email protected]  
help / color / mirror / Atom feed
[Bug][patch]: After dropping the last label from a property graph element, invoking pg_get_propgraphdef() triggers an assertion failure
13+ messages / 4 participants
[nested] [flat]

* [Bug][patch]: After dropping the last label from a property graph element, invoking pg_get_propgraphdef() triggers an assertion failure
@ 2026-04-20 18:12  SATYANARAYANA NARLAPURAM <[email protected]>
  0 siblings, 1 reply; 13+ messages in thread

From: SATYANARAYANA NARLAPURAM @ 2026-04-20 18:12 UTC (permalink / raw)
  To: PostgreSQL Hackers <[email protected]>; Ashutosh Bapat <[email protected]>

Hi hackers,

ALTER PROPERTY GRAPH ... ALTER ... DROP LABEL currently allows removing
the last label from an element, leaving it with zero labels.

On assert-enabled builds, pg_get_propgraphdef() hits
TRAP: failed Assert("count > 0"), File: "ruleutils.c", Line: 1837, PID:
1821840

Repro:

CREATE TABLE t (x int PRIMARY KEY, y int, z int);
CREATE PROPERTY GRAPH g VERTEX TABLES (t KEY (x) LABEL l1 LABEL l2);
ALTER PROPERTY GRAPH g ALTER VERTEX TABLE t DROP LABEL l2;
ALTER PROPERTY GRAPH g ALTER VERTEX TABLE t DROP LABEL l1;
SELECT pg_get_propgraphdef('g'::regclass);

We can fix it two ways, (1) Prevent dropping the last label; (2) handle
zero labels.
I feel it is easier to prevent dropping the last label than handling zero
labels. Thoughts?

The attached patch adds a check in AlterPropGraph() before
performDeletion(). It scans pg_propgraph_element_label to count labels
for the element, and raises an error if only one remains. A regression test
is included
that drops labels down to the last one, verifies the error, then re-adds
them back.

Thanks,
Satya


Attachments:

  [application/octet-stream] 0001-Prevent-dropping-the-last-label-from-a-property-grap.patch (4.3K, 3-0001-Prevent-dropping-the-last-label-from-a-property-grap.patch)
  download | inline diff:
From 8e3373ec7682b881c5b12c0ebf6f68ba860ebb45 Mon Sep 17 00:00:00 2001
From: satyanarayana narlapuram <[email protected]>
Date: Mon, 20 Apr 2026 18:07:40 +0000
Subject: [PATCH] Prevent dropping the last label from a property graph element

ALTER PROPERTY GRAPH ... DROP LABEL allowed removing the last label
from an element, leaving it with zero labels. This causes 
pg_get_propgraphdef() to crash.

Add a check in AlterPropGraph() that counts the element's labels
before deletion and errors out if only one remains.

Reported-by: Satyanarayana Narlapuram <[email protected]>
---
 src/backend/commands/propgraphcmds.c          | 30 +++++++++++++++++++
 .../expected/create_property_graph.out        |  6 ++++
 .../regress/sql/create_property_graph.sql     |  4 +++
 3 files changed, 40 insertions(+)

diff --git a/src/backend/commands/propgraphcmds.c b/src/backend/commands/propgraphcmds.c
index 45d2ff1b..16ef611b 100644
--- a/src/backend/commands/propgraphcmds.c
+++ b/src/backend/commands/propgraphcmds.c
@@ -1522,6 +1522,36 @@ 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.
+		 */
+		{
+			Relation	elrel;
+			SysScanDesc elscan;
+			ScanKeyData elkey[1];
+			int			nlabels = 0;
+
+			elrel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+			ScanKeyInit(&elkey[0],
+						Anum_pg_propgraph_element_label_pgelelid,
+						BTEqualStrategyNumber, F_OIDEQ,
+						ObjectIdGetDatum(peoid));
+			elscan = systable_beginscan(elrel, PropgraphElementLabelElementLabelIndexId,
+										true, NULL, 1, elkey);
+			while (HeapTupleIsValid(systable_getnext(elscan)))
+				nlabels++;
+			systable_endscan(elscan);
+			table_close(elrel, AccessShareLock);
+
+			if (nlabels <= 1)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 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 bc9a596e..740f886c 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -57,6 +57,12 @@ 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.
+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 241f93df..4cf77159 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -52,6 +52,10 @@ 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
+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);
-- 
2.43.0



^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: [Bug][patch]: After dropping the last label from a property graph element, invoking pg_get_propgraphdef() triggers an assertion failure
@ 2026-04-21 05:33  Ashutosh Bapat <[email protected]>
  parent: SATYANARAYANA NARLAPURAM <[email protected]>
  0 siblings, 1 reply; 13+ messages in thread

From: Ashutosh Bapat @ 2026-04-21 05:33 UTC (permalink / raw)
  To: SATYANARAYANA NARLAPURAM <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

On Mon, Apr 20, 2026 at 11:42 PM SATYANARAYANA NARLAPURAM
<[email protected]> wrote:
>
> Hi hackers,
>
> ALTER PROPERTY GRAPH ... ALTER ... DROP LABEL currently allows removing
> the last label from an element, leaving it with zero labels.
>
> On assert-enabled builds, pg_get_propgraphdef() hits
> TRAP: failed Assert("count > 0"), File: "ruleutils.c", Line: 1837, PID: 1821840
>
> Repro:
>
> CREATE TABLE t (x int PRIMARY KEY, y int, z int);
> CREATE PROPERTY GRAPH g VERTEX TABLES (t KEY (x) LABEL l1 LABEL l2);
> ALTER PROPERTY GRAPH g ALTER VERTEX TABLE t DROP LABEL l2;
> ALTER PROPERTY GRAPH g ALTER VERTEX TABLE t DROP LABEL l1;
> SELECT pg_get_propgraphdef('g'::regclass);
>
> We can fix it two ways, (1) Prevent dropping the last label; (2) handle zero labels.
> I feel it is easier to prevent dropping the last label than handling zero labels. Thoughts?
>

SQL/PGQ standard section 11.25 syntax rule 6 says
"Element table descriptor shall include two or more labels, one of
which has an <identifier> that is equivalent to the <label name>
simply contained in the <drop element table label clause>."

IIUC this simply means that the last label can not be dropped. That
agrees with your chosen option.

In the patch,
+ while (HeapTupleIsValid(systable_getnext(elscan)))
+ nlabels++;

It's better to break from the while loop after incrementing nlabels
and avoid scanning the entire table in the worst case. All we want to
check is whether another label exists and not all the labels.

> The attached patch adds a check in AlterPropGraph() before
> performDeletion(). It scans pg_propgraph_element_label to count labels
> for the element, and raises an error if only one remains. A regression test is included
> that drops labels down to the last one, verifies the error, then re-adds them back.

I would add a test to make sure ADD LABEL ... DROP LABEL .. is allowed.

While investigating this, I also looked at the DROP PROPERTIES
specification. It's allowed to drop even the last property.

-- 
Best Wishes,
Ashutosh Bapat





^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: [Bug][patch]: After dropping the last label from a property graph element, invoking pg_get_propgraphdef() triggers an assertion failure
@ 2026-04-21 05:58  SATYANARAYANA NARLAPURAM <[email protected]>
  parent: Ashutosh Bapat <[email protected]>
  0 siblings, 1 reply; 13+ messages in thread

From: SATYANARAYANA NARLAPURAM @ 2026-04-21 05:58 UTC (permalink / raw)
  To: Ashutosh Bapat <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

HI Ashutosh,

On Mon, Apr 20, 2026 at 10:34 PM Ashutosh Bapat <
[email protected]> wrote:

> On Mon, Apr 20, 2026 at 11:42 PM SATYANARAYANA NARLAPURAM
> <[email protected]> wrote:
> >
> > Hi hackers,
> >
> > ALTER PROPERTY GRAPH ... ALTER ... DROP LABEL currently allows removing
> > the last label from an element, leaving it with zero labels.
> >
> > On assert-enabled builds, pg_get_propgraphdef() hits
> > TRAP: failed Assert("count > 0"), File: "ruleutils.c", Line: 1837, PID:
> 1821840
> >
> > Repro:
> >
> > CREATE TABLE t (x int PRIMARY KEY, y int, z int);
> > CREATE PROPERTY GRAPH g VERTEX TABLES (t KEY (x) LABEL l1 LABEL l2);
> > ALTER PROPERTY GRAPH g ALTER VERTEX TABLE t DROP LABEL l2;
> > ALTER PROPERTY GRAPH g ALTER VERTEX TABLE t DROP LABEL l1;
> > SELECT pg_get_propgraphdef('g'::regclass);
> >
> > We can fix it two ways, (1) Prevent dropping the last label; (2) handle
> zero labels.
> > I feel it is easier to prevent dropping the last label than handling
> zero labels. Thoughts?
> >
>
> SQL/PGQ standard section 11.25 syntax rule 6 says
> "Element table descriptor shall include two or more labels, one of
> which has an <identifier> that is equivalent to the <label name>
> simply contained in the <drop element table label clause>."
>
> IIUC this simply means that the last label can not be dropped. That
> agrees with your chosen option.
>
> In the patch,
> + while (HeapTupleIsValid(systable_getnext(elscan)))
> + nlabels++;
>
> It's better to break from the while loop after incrementing nlabels
> and avoid scanning the entire table in the worst case. All we want to
> check is whether another label exists and not all the labels.
>

Please find the attached v2 patch.


>
> > The attached patch adds a check in AlterPropGraph() before
> > performDeletion(). It scans pg_propgraph_element_label to count labels
> > for the element, and raises an error if only one remains. A regression
> test is included
> > that drops labels down to the last one, verifies the error, then re-adds
> them back.
>
> I would add a test to make sure ADD LABEL ... DROP LABEL .. is allowed.
>

+ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l1;  -- error:
last label
+ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 ADD LABEL t3l2 PROPERTIES
ALL COLUMNS;

Are you looking for any additional coverage?



>
> While investigating this, I also looked at the DROP PROPERTIES
> specification. It's allowed to drop even the last property.
>

Thanks for checking this.

Thanks,
Satya


Attachments:

  [application/octet-stream] v2-0001-Prevent-dropping-the-last-label-from-a-property-grap.patch (4.4K, 3-v2-0001-Prevent-dropping-the-last-label-from-a-property-grap.patch)
  download | inline diff:
From 16f02ae5d8f6854fe4f9dd318151b4e2e4e5b861 Mon Sep 17 00:00:00 2001
From: satyanarayana narlapuram <[email protected]>
Date: Tue, 21 Apr 2026 05:49:51 +0000
Subject: [PATCH] Prevent dropping the last label from a property graph element

ALTER PROPERTY GRAPH ... DROP LABEL allowed removing the last label
from an element, leaving it with zero labels. This causes
pg_get_propgraphdef() to crash.

Add a check in AlterPropGraph() that counts the element's labels
before deletion and errors out if only one remains.  The label count
loop breaks early once it finds more than one label, avoiding a full
scan of the element_label catalog.

---
 src/backend/commands/propgraphcmds.c          | 34 +++++++++++++++++++
 .../expected/create_property_graph.out        |  6 ++++
 .../regress/sql/create_property_graph.sql     |  4 +++
 3 files changed, 44 insertions(+)

diff --git a/src/backend/commands/propgraphcmds.c b/src/backend/commands/propgraphcmds.c
index 45d2ff1b..b1c5e8ae 100644
--- a/src/backend/commands/propgraphcmds.c
+++ b/src/backend/commands/propgraphcmds.c
@@ -1522,6 +1522,40 @@ 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.
+		 */
+		{
+			Relation	elrel;
+			SysScanDesc elscan;
+			ScanKeyData elkey[1];
+			int			nlabels = 0;
+
+			elrel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+			ScanKeyInit(&elkey[0],
+						Anum_pg_propgraph_element_label_pgelelid,
+						BTEqualStrategyNumber, F_OIDEQ,
+						ObjectIdGetDatum(peoid));
+			elscan = systable_beginscan(elrel, PropgraphElementLabelElementLabelIndexId,
+										true, NULL, 1, elkey);
+			while (HeapTupleIsValid(systable_getnext(elscan)))
+			{
+				nlabels++;
+				if (nlabels > 1)
+					break;
+			}
+			systable_endscan(elscan);
+			table_close(elrel, AccessShareLock);
+
+			if (nlabels <= 1)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 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 bc9a596e..740f886c 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -57,6 +57,12 @@ 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.
+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 241f93df..4cf77159 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -52,6 +52,10 @@ 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
+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);
-- 
2.43.0



^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: [Bug][patch]: After dropping the last label from a property graph element, invoking pg_get_propgraphdef() triggers an assertion failure
@ 2026-04-21 06:58  Ashutosh Bapat <[email protected]>
  parent: SATYANARAYANA NARLAPURAM <[email protected]>
  0 siblings, 1 reply; 13+ messages in thread

From: Ashutosh Bapat @ 2026-04-21 06:58 UTC (permalink / raw)
  To: SATYANARAYANA NARLAPURAM <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

On Tue, Apr 21, 2026 at 11:28 AM SATYANARAYANA NARLAPURAM
<[email protected]> wrote:
>
> HI Ashutosh,
>
> On Mon, Apr 20, 2026 at 10:34 PM Ashutosh Bapat <[email protected]> wrote:
>>
>> On Mon, Apr 20, 2026 at 11:42 PM SATYANARAYANA NARLAPURAM
>> <[email protected]> wrote:
>> >
>> > Hi hackers,
>> >
>> > ALTER PROPERTY GRAPH ... ALTER ... DROP LABEL currently allows removing
>> > the last label from an element, leaving it with zero labels.
>> >
>> > On assert-enabled builds, pg_get_propgraphdef() hits
>> > TRAP: failed Assert("count > 0"), File: "ruleutils.c", Line: 1837, PID: 1821840
>> >
>> > Repro:
>> >
>> > CREATE TABLE t (x int PRIMARY KEY, y int, z int);
>> > CREATE PROPERTY GRAPH g VERTEX TABLES (t KEY (x) LABEL l1 LABEL l2);
>> > ALTER PROPERTY GRAPH g ALTER VERTEX TABLE t DROP LABEL l2;
>> > ALTER PROPERTY GRAPH g ALTER VERTEX TABLE t DROP LABEL l1;
>> > SELECT pg_get_propgraphdef('g'::regclass);
>> >
>> > We can fix it two ways, (1) Prevent dropping the last label; (2) handle zero labels.
>> > I feel it is easier to prevent dropping the last label than handling zero labels. Thoughts?
>> >
>>
>> SQL/PGQ standard section 11.25 syntax rule 6 says
>> "Element table descriptor shall include two or more labels, one of
>> which has an <identifier> that is equivalent to the <label name>
>> simply contained in the <drop element table label clause>."
>>
>> IIUC this simply means that the last label can not be dropped. That
>> agrees with your chosen option.
>>
>> In the patch,
>> + while (HeapTupleIsValid(systable_getnext(elscan)))
>> + nlabels++;
>>
>> It's better to break from the while loop after incrementing nlabels
>> and avoid scanning the entire table in the worst case. All we want to
>> check is whether another label exists and not all the labels.
>
>
> Please find the attached v2 patch.
>
>>
>>
>> > The attached patch adds a check in AlterPropGraph() before
>> > performDeletion(). It scans pg_propgraph_element_label to count labels
>> > for the element, and raises an error if only one remains. A regression test is included
>> > that drops labels down to the last one, verifies the error, then re-adds them back.
>>
>> I would add a test to make sure ADD LABEL ... DROP LABEL .. is allowed.
>
>
> +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l1;  -- error: last label
> +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 ADD LABEL t3l2 PROPERTIES ALL COLUMNS;
>
> Are you looking for any additional coverage?

I thought specifying ADD LABEL and DROP LABEL is supported in the same
DDL. But that's not the case. Sorry.

Will review the patch soon.

Additionally, the patch should update the DDL document to mention that
the DDL does not allow dropping the last label on the given graph
element table.

-- 
Best Wishes,
Ashutosh Bapat





^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: [Bug][patch]: After dropping the last label from a property graph element, invoking pg_get_propgraphdef() triggers an assertion failure
@ 2026-04-21 07:58  SATYANARAYANA NARLAPURAM <[email protected]>
  parent: Ashutosh Bapat <[email protected]>
  0 siblings, 1 reply; 13+ messages in thread

From: SATYANARAYANA NARLAPURAM @ 2026-04-21 07:58 UTC (permalink / raw)
  To: Ashutosh Bapat <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

Hi,

On Mon, Apr 20, 2026 at 11:58 PM Ashutosh Bapat <
[email protected]> wrote:

> On Tue, Apr 21, 2026 at 11:28 AM SATYANARAYANA NARLAPURAM
> <[email protected]> wrote:
> >
> > HI Ashutosh,
> >
> > On Mon, Apr 20, 2026 at 10:34 PM Ashutosh Bapat <
> [email protected]> wrote:
> >>
> >> On Mon, Apr 20, 2026 at 11:42 PM SATYANARAYANA NARLAPURAM
> >> <[email protected]> wrote:
> >> >
> >> > Hi hackers,
> >> >
> >> > ALTER PROPERTY GRAPH ... ALTER ... DROP LABEL currently allows
> removing
> >> > the last label from an element, leaving it with zero labels.
> >> >
> >> > On assert-enabled builds, pg_get_propgraphdef() hits
> >> > TRAP: failed Assert("count > 0"), File: "ruleutils.c", Line: 1837,
> PID: 1821840
> >> >
> >> > Repro:
> >> >
> >> > CREATE TABLE t (x int PRIMARY KEY, y int, z int);
> >> > CREATE PROPERTY GRAPH g VERTEX TABLES (t KEY (x) LABEL l1 LABEL l2);
> >> > ALTER PROPERTY GRAPH g ALTER VERTEX TABLE t DROP LABEL l2;
> >> > ALTER PROPERTY GRAPH g ALTER VERTEX TABLE t DROP LABEL l1;
> >> > SELECT pg_get_propgraphdef('g'::regclass);
> >> >
> >> > We can fix it two ways, (1) Prevent dropping the last label; (2)
> handle zero labels.
> >> > I feel it is easier to prevent dropping the last label than handling
> zero labels. Thoughts?
> >> >
> >>
> >> SQL/PGQ standard section 11.25 syntax rule 6 says
> >> "Element table descriptor shall include two or more labels, one of
> >> which has an <identifier> that is equivalent to the <label name>
> >> simply contained in the <drop element table label clause>."
> >>
> >> IIUC this simply means that the last label can not be dropped. That
> >> agrees with your chosen option.
> >>
> >> In the patch,
> >> + while (HeapTupleIsValid(systable_getnext(elscan)))
> >> + nlabels++;
> >>
> >> It's better to break from the while loop after incrementing nlabels
> >> and avoid scanning the entire table in the worst case. All we want to
> >> check is whether another label exists and not all the labels.
> >
> >
> > Please find the attached v2 patch.
> >
> >>
> >>
> >> > The attached patch adds a check in AlterPropGraph() before
> >> > performDeletion(). It scans pg_propgraph_element_label to count labels
> >> > for the element, and raises an error if only one remains. A
> regression test is included
> >> > that drops labels down to the last one, verifies the error, then
> re-adds them back.
> >>
> >> I would add a test to make sure ADD LABEL ... DROP LABEL .. is allowed.
> >
> >
> > +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l1;  --
> error: last label
> > +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 ADD LABEL t3l2 PROPERTIES
> ALL COLUMNS;
> >
> > Are you looking for any additional coverage?
>
> I thought specifying ADD LABEL and DROP LABEL is supported in the same
> DDL. But that's not the case. Sorry.
>
> Will review the patch soon.
>
> Additionally, the patch should update the DDL document to mention that
> the DDL does not allow dropping the last label on the given graph
> element table.
>

Addressed this in v3 patch.

Thanks,
Satya


Attachments:

  [application/octet-stream] v3-0001-Prevent-dropping-the-last-label-from-a-property-grap.patch (5.2K, 3-v3-0001-Prevent-dropping-the-last-label-from-a-property-grap.patch)
  download | inline diff:
From 2046f489f06685131b4908292f806e1c44f6e1d7 Mon Sep 17 00:00:00 2001
From: satyanarayana narlapuram <[email protected]>
Date: Tue, 21 Apr 2026 07:52:56 +0000
Subject: [PATCH] Prevent dropping the last label from a property graph element

ALTER PROPERTY GRAPH ... DROP LABEL allowed removing the last label
from an element, leaving it with zero labels. This causes
pg_get_propgraphdef() to crash.

Add a check in AlterPropGraph() that counts the element's labels
before deletion and errors out if only one remains.  The label count
loop breaks early once it finds more than one label, avoiding a full
scan of the element_label catalog.

Update the ALTER PROPERTY GRAPH documentation to mention the
restriction.

Reported-by: Satyanarayana Narlapuram <[email protected]>
---
 doc/src/sgml/ref/alter_property_graph.sgml    |  2 ++
 src/backend/commands/propgraphcmds.c          | 34 +++++++++++++++++++
 .../expected/create_property_graph.out        |  6 ++++
 .../regress/sql/create_property_graph.sql     |  4 +++
 4 files changed, 46 insertions(+)

diff --git a/doc/src/sgml/ref/alter_property_graph.sgml b/doc/src/sgml/ref/alter_property_graph.sgml
index 19352c06..8bcca0ec 100644
--- a/doc/src/sgml/ref/alter_property_graph.sgml
+++ b/doc/src/sgml/ref/alter_property_graph.sgml
@@ -100,6 +100,8 @@ ALTER PROPERTY GRAPH [ IF EXISTS ] <replaceable class="parameter">name</replacea
      <listitem>
       <para>
        This form removes a label from an existing vertex or edge table.
+       The last label on an element table cannot be dropped; every element
+       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 45d2ff1b..b1c5e8ae 100644
--- a/src/backend/commands/propgraphcmds.c
+++ b/src/backend/commands/propgraphcmds.c
@@ -1522,6 +1522,40 @@ 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.
+		 */
+		{
+			Relation	elrel;
+			SysScanDesc elscan;
+			ScanKeyData elkey[1];
+			int			nlabels = 0;
+
+			elrel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+			ScanKeyInit(&elkey[0],
+						Anum_pg_propgraph_element_label_pgelelid,
+						BTEqualStrategyNumber, F_OIDEQ,
+						ObjectIdGetDatum(peoid));
+			elscan = systable_beginscan(elrel, PropgraphElementLabelElementLabelIndexId,
+										true, NULL, 1, elkey);
+			while (HeapTupleIsValid(systable_getnext(elscan)))
+			{
+				nlabels++;
+				if (nlabels > 1)
+					break;
+			}
+			systable_endscan(elscan);
+			table_close(elrel, AccessShareLock);
+
+			if (nlabels <= 1)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 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 bc9a596e..740f886c 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -57,6 +57,12 @@ 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.
+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 241f93df..4cf77159 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -52,6 +52,10 @@ 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
+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);
-- 
2.43.0



^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: [Bug][patch]: After dropping the last label from a property graph element, invoking pg_get_propgraphdef() triggers an assertion failure
@ 2026-04-21 12:19  Ashutosh Bapat <[email protected]>
  parent: SATYANARAYANA NARLAPURAM <[email protected]>
  0 siblings, 1 reply; 13+ messages in thread

From: Ashutosh Bapat @ 2026-04-21 12:19 UTC (permalink / raw)
  To: SATYANARAYANA NARLAPURAM <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

On Tue, Apr 21, 2026 at 1:29 PM SATYANARAYANA NARLAPURAM
<[email protected]> wrote:
>
> Hi,
>
> On Mon, Apr 20, 2026 at 11:58 PM Ashutosh Bapat <[email protected]> wrote:
>>
>> On Tue, Apr 21, 2026 at 11:28 AM SATYANARAYANA NARLAPURAM
>> <[email protected]> wrote:
>> >
>> > HI Ashutosh,
>> >
>> > On Mon, Apr 20, 2026 at 10:34 PM Ashutosh Bapat <[email protected]> wrote:
>> >>
>> >> On Mon, Apr 20, 2026 at 11:42 PM SATYANARAYANA NARLAPURAM
>> >> <[email protected]> wrote:
>> >> >
>> >> > Hi hackers,
>> >> >
>> >> > ALTER PROPERTY GRAPH ... ALTER ... DROP LABEL currently allows removing
>> >> > the last label from an element, leaving it with zero labels.
>> >> >
>> >> > On assert-enabled builds, pg_get_propgraphdef() hits
>> >> > TRAP: failed Assert("count > 0"), File: "ruleutils.c", Line: 1837, PID: 1821840
>> >> >
>> >> > Repro:
>> >> >
>> >> > CREATE TABLE t (x int PRIMARY KEY, y int, z int);
>> >> > CREATE PROPERTY GRAPH g VERTEX TABLES (t KEY (x) LABEL l1 LABEL l2);
>> >> > ALTER PROPERTY GRAPH g ALTER VERTEX TABLE t DROP LABEL l2;
>> >> > ALTER PROPERTY GRAPH g ALTER VERTEX TABLE t DROP LABEL l1;
>> >> > SELECT pg_get_propgraphdef('g'::regclass);
>> >> >
>> >> > We can fix it two ways, (1) Prevent dropping the last label; (2) handle zero labels.
>> >> > I feel it is easier to prevent dropping the last label than handling zero labels. Thoughts?
>> >> >
>> >>
>> >> SQL/PGQ standard section 11.25 syntax rule 6 says
>> >> "Element table descriptor shall include two or more labels, one of
>> >> which has an <identifier> that is equivalent to the <label name>
>> >> simply contained in the <drop element table label clause>."
>> >>
>> >> IIUC this simply means that the last label can not be dropped. That
>> >> agrees with your chosen option.
>> >>
>> >> In the patch,
>> >> + while (HeapTupleIsValid(systable_getnext(elscan)))
>> >> + nlabels++;
>> >>
>> >> It's better to break from the while loop after incrementing nlabels
>> >> and avoid scanning the entire table in the worst case. All we want to
>> >> check is whether another label exists and not all the labels.
>> >
>> >
>> > Please find the attached v2 patch.
>> >
>> >>
>> >>
>> >> > The attached patch adds a check in AlterPropGraph() before
>> >> > performDeletion(). It scans pg_propgraph_element_label to count labels
>> >> > for the element, and raises an error if only one remains. A regression test is included
>> >> > that drops labels down to the last one, verifies the error, then re-adds them back.
>> >>
>> >> I would add a test to make sure ADD LABEL ... DROP LABEL .. is allowed.
>> >
>> >
>> > +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l1;  -- error: last label
>> > +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 ADD LABEL t3l2 PROPERTIES ALL COLUMNS;
>> >
>> > Are you looking for any additional coverage?
>>
>> I thought specifying ADD LABEL and DROP LABEL is supported in the same
>> DDL. But that's not the case. Sorry.
>>
>> Will review the patch soon.
>>
>> Additionally, the patch should update the DDL document to mention that
>> the DDL does not allow dropping the last label on the given graph
>> element table.
>
>
> Addressed this in v3 patch.

Thanks.

I questioned whether it makes sense to move the code to check the
existence of at least one other label into its own function so the
code is more readable. AlterPropGraph is already about 500 lines and
this code increases it further. There are two possibilities:
a. The function returns true if the given element has more than two
labels associated with it. The choice of 2 makes sense only in the
drop label context but not otherwise.
b. The function returns true if it finds one label other than the one
with the given label oid. That looks a bit more elegant. But still
seems too tied to drop label context.

So I decided against it. But instead removed the extra indentation.
The earliest variable declaration block is not farther from that code.

I changed the errcode to ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE
since we expect the element to be in a specific state to drop the
label. ERRCODE_INVALID_OBJECT_DEFINITION would mean that the
definition of the element or the label is itself invalid. That's not
the case.

Here's a patch with some minor edits.

-- 
Best Wishes,
Ashutosh Bapat


Attachments:

  [text/x-patch] v20260421-0001-Prevent-dropping-the-last-label-from-a-pro.patch (5.6K, 2-v20260421-0001-Prevent-dropping-the-last-label-from-a-pro.patch)
  download | inline diff:
From 973b724463c0e0755fd59260bd8b5f0f928f524e Mon Sep 17 00:00:00 2001
From: satyanarayana narlapuram <[email protected]>
Date: Tue, 21 Apr 2026 07:52:56 +0000
Subject: [PATCH v20260421 5/5] 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.

Reported By: Satyanarayana Narlapuram <[email protected]>
Author: Satyanarayana Narlapuram <[email protected]>
Reviewed By: Ashutosh Bapat <[email protected]>
---
 doc/src/sgml/ref/alter_property_graph.sgml    |  4 ++-
 src/backend/commands/propgraphcmds.c          | 32 +++++++++++++++++++
 .../expected/create_property_graph.out        |  6 ++++
 .../regress/sql/create_property_graph.sql     |  4 +++
 4 files changed, 45 insertions(+), 1 deletion(-)

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 3842f4a711b..583bd43cd7b 100644
--- a/src/backend/commands/propgraphcmds.c
+++ b/src/backend/commands/propgraphcmds.c
@@ -1493,6 +1493,11 @@ AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt)
 		Oid			labeloid;
 		Oid			ellabeloid;
 		ObjectAddress obj;
+		Relation	elrel;
+		SysScanDesc elscan;
+		ScanKeyData elkey[1];
+		int			nlabels = 0;
+
 
 		if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX)
 			peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1);
@@ -1522,6 +1527,33 @@ 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.
+		 */
+		elrel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+		ScanKeyInit(&elkey[0],
+					Anum_pg_propgraph_element_label_pgelelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(peoid));
+		elscan = systable_beginscan(elrel, PropgraphElementLabelElementLabelIndexId,
+									true, NULL, 1, elkey);
+		while (HeapTupleIsValid(systable_getnext(elscan)))
+		{
+			nlabels++;
+			if (nlabels > 1)
+				break;
+		}
+		systable_endscan(elscan);
+		table_close(elrel, AccessShareLock);
+
+		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 bc9a596ec89..740f886cd83 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -57,6 +57,12 @@ 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.
+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 241f93df302..4cf771596a8 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -52,6 +52,10 @@ 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
+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);
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: [Bug][patch]: After dropping the last label from a property graph element, invoking pg_get_propgraphdef() triggers an assertion failure
@ 2026-04-28 15:02  Ashutosh Bapat <[email protected]>
  parent: Ashutosh Bapat <[email protected]>
  0 siblings, 2 replies; 13+ messages in thread

From: Ashutosh Bapat @ 2026-04-28 15:02 UTC (permalink / raw)
  To: SATYANARAYANA NARLAPURAM <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

On Tue, Apr 21, 2026 at 5:49 PM Ashutosh Bapat
<[email protected]> wrote:
>
> On Tue, Apr 21, 2026 at 1:29 PM SATYANARAYANA NARLAPURAM
> <[email protected]> wrote:
> >
> > Hi,
> >
> > On Mon, Apr 20, 2026 at 11:58 PM Ashutosh Bapat <[email protected]> wrote:
> >>
> >> On Tue, Apr 21, 2026 at 11:28 AM SATYANARAYANA NARLAPURAM
> >> <[email protected]> wrote:
> >> >
> >> > HI Ashutosh,
> >> >
> >> > On Mon, Apr 20, 2026 at 10:34 PM Ashutosh Bapat <[email protected]> wrote:
> >> >>
> >> >> On Mon, Apr 20, 2026 at 11:42 PM SATYANARAYANA NARLAPURAM
> >> >> <[email protected]> wrote:
> >> >> >
> >> >> > Hi hackers,
> >> >> >
> >> >> > ALTER PROPERTY GRAPH ... ALTER ... DROP LABEL currently allows removing
> >> >> > the last label from an element, leaving it with zero labels.
> >> >> >
> >> >> > On assert-enabled builds, pg_get_propgraphdef() hits
> >> >> > TRAP: failed Assert("count > 0"), File: "ruleutils.c", Line: 1837, PID: 1821840
> >> >> >
> >> >> > Repro:
> >> >> >
> >> >> > CREATE TABLE t (x int PRIMARY KEY, y int, z int);
> >> >> > CREATE PROPERTY GRAPH g VERTEX TABLES (t KEY (x) LABEL l1 LABEL l2);
> >> >> > ALTER PROPERTY GRAPH g ALTER VERTEX TABLE t DROP LABEL l2;
> >> >> > ALTER PROPERTY GRAPH g ALTER VERTEX TABLE t DROP LABEL l1;
> >> >> > SELECT pg_get_propgraphdef('g'::regclass);
> >> >> >
> >> >> > We can fix it two ways, (1) Prevent dropping the last label; (2) handle zero labels.
> >> >> > I feel it is easier to prevent dropping the last label than handling zero labels. Thoughts?
> >> >> >
> >> >>
> >> >> SQL/PGQ standard section 11.25 syntax rule 6 says
> >> >> "Element table descriptor shall include two or more labels, one of
> >> >> which has an <identifier> that is equivalent to the <label name>
> >> >> simply contained in the <drop element table label clause>."
> >> >>
> >> >> IIUC this simply means that the last label can not be dropped. That
> >> >> agrees with your chosen option.
> >> >>
> >> >> In the patch,
> >> >> + while (HeapTupleIsValid(systable_getnext(elscan)))
> >> >> + nlabels++;
> >> >>
> >> >> It's better to break from the while loop after incrementing nlabels
> >> >> and avoid scanning the entire table in the worst case. All we want to
> >> >> check is whether another label exists and not all the labels.
> >> >
> >> >
> >> > Please find the attached v2 patch.
> >> >
> >> >>
> >> >>
> >> >> > The attached patch adds a check in AlterPropGraph() before
> >> >> > performDeletion(). It scans pg_propgraph_element_label to count labels
> >> >> > for the element, and raises an error if only one remains. A regression test is included
> >> >> > that drops labels down to the last one, verifies the error, then re-adds them back.
> >> >>
> >> >> I would add a test to make sure ADD LABEL ... DROP LABEL .. is allowed.
> >> >
> >> >
> >> > +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l1;  -- error: last label
> >> > +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 ADD LABEL t3l2 PROPERTIES ALL COLUMNS;
> >> >
> >> > Are you looking for any additional coverage?
> >>
> >> I thought specifying ADD LABEL and DROP LABEL is supported in the same
> >> DDL. But that's not the case. Sorry.
> >>
> >> Will review the patch soon.
> >>
> >> Additionally, the patch should update the DDL document to mention that
> >> the DDL does not allow dropping the last label on the given graph
> >> element table.
> >
> >
> > Addressed this in v3 patch.
>
> Thanks.
>
> I questioned whether it makes sense to move the code to check the
> existence of at least one other label into its own function so the
> code is more readable. AlterPropGraph is already about 500 lines and
> this code increases it further. There are two possibilities:
> a. The function returns true if the given element has more than two
> labels associated with it. The choice of 2 makes sense only in the
> drop label context but not otherwise.
> b. The function returns true if it finds one label other than the one
> with the given label oid. That looks a bit more elegant. But still
> seems too tied to drop label context.
>
> So I decided against it. But instead removed the extra indentation.
> The earliest variable declaration block is not farther from that code.
>
> I changed the errcode to ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE
> since we expect the element to be in a specific state to drop the
> label. ERRCODE_INVALID_OBJECT_DEFINITION would mean that the
> definition of the element or the label is itself invalid. That's not
> the case.
>
> Here's a patch with some minor edits.
>

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.

This patch causes a conflict with the patch posted at [1]. Hence
posting both the patches together here, so that they can be committed
without conflict.

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



--
Best Wishes,
Ashutosh Bapat


Attachments:

  [text/x-patch] v20260428-0001-Prevent-dropping-the-last-label-from-a-pro.patch (6.9K, 2-v20260428-0001-Prevent-dropping-the-last-label-from-a-pro.patch)
  download | inline diff:
From 85c7dcb3719b03a0ee657af5ab5df504ea5ef8fe Mon Sep 17 00:00:00 2001
From: satyanarayana narlapuram <[email protected]>
Date: Tue, 21 Apr 2026 07:52:56 +0000
Subject: [PATCH v20260428 1/3] 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.

Reported By: Satyanarayana Narlapuram <[email protected]>
Author: Satyanarayana Narlapuram <[email protected]>
Author: Ashutosh Bapat <[email protected]>
Reviewed by: Ashutosh Bapat <[email protected]>
Discussion: https://www.postgresql.org/message-id/CAHg+QDeP=mTHTV48R23zKMy1SBmCKZ_L7-z5zKnYyw+K0x-gCg@mail.gmail.com
---
 doc/src/sgml/ref/alter_property_graph.sgml    |  4 +-
 src/backend/commands/propgraphcmds.c          | 54 +++++++++++++++++--
 .../expected/create_property_graph.out        |  6 +++
 .../regress/sql/create_property_graph.sql     |  4 ++
 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 6d509fccf46..7836e7ab63e 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	elrel;
+		SysScanDesc elscan;
+		ScanKeyData elkey[1];
+		int			nlabels = 0;
+		HeapTuple	tuple;
+
 
 		if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX)
 			peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1);
@@ -1510,11 +1516,38 @@ 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?
+		 */
+		elrel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+		ScanKeyInit(&elkey[0],
+					Anum_pg_propgraph_element_label_pgelelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(peoid));
+		elscan = systable_beginscan(elrel, PropgraphElementLabelElementLabelIndexId,
+									true, NULL, 1, elkey);
+		while (HeapTupleIsValid(tuple = systable_getnext(elscan)))
+		{
+			Form_pg_propgraph_element_label elform = (Form_pg_propgraph_element_label) GETSTRUCT(tuple);
+
+			nlabels++;
 
+			if (elform->pgellabelid == labeloid)
+				ellabeloid = elform->oid;
+
+			/*
+			 * If we find more than one label associated with the given
+			 * element and also found the label we are going to drop, we are
+			 * done.
+			 */
+			if (nlabels > 1 && OidIsValid(ellabeloid))
+				break;
+		}
+		systable_endscan(elscan);
+		table_close(elrel, AccessShareLock);
+
+		/* Given label is not associated with the element. */
 		if (!ellabeloid)
 			ereport(ERROR,
 					errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -1522,6 +1555,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.
+		 */
+		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 bc9a596ec89..740f886cd83 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -57,6 +57,12 @@ 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.
+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 241f93df302..4cf771596a8 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -52,6 +52,10 @@ 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
+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: c210647aeb17692c138014235c7e7a2d9af73b87
-- 
2.34.1



  [text/x-patch] v20260428-0002-Dropping-a-property-not-associated-with-th.patch (6.2K, 3-v20260428-0002-Dropping-a-property-not-associated-with-th.patch)
  download | inline diff:
From e859ce76cd8fa0dde5d939c47dde5477503eeb82 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email protected]>
Date: Tue, 28 Apr 2026 12:43:40 +0530
Subject: [PATCH v20260428 2/3] 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.

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 7836e7ab63e..d3c0443c336 100644
--- a/src/backend/commands/propgraphcmds.c
+++ b/src/backend/commands/propgraphcmds.c
@@ -1582,7 +1582,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);
@@ -1593,17 +1593,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),
@@ -1624,7 +1618,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)
@@ -1636,17 +1630,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,
@@ -1659,21 +1647,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 740f886cd83..7c3f25a3e0d 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -88,6 +88,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 4cf771596a8..191412a6a33 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -79,6 +79,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



^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: [Bug][patch]: After dropping the last label from a property graph element, invoking pg_get_propgraphdef() triggers an assertion failure
@ 2026-04-29 12:58  Chao Li <[email protected]>
  parent: Ashutosh Bapat <[email protected]>
  1 sibling, 1 reply; 13+ messages in thread

From: Chao Li @ 2026-04-29 12:58 UTC (permalink / raw)
  To: Ashutosh Bapat <[email protected]>; +Cc: SATYANARAYANA NARLAPURAM <[email protected]>; PostgreSQL Hackers <[email protected]>



> On Apr 28, 2026, at 23:02, Ashutosh Bapat <[email protected]> wrote:
> 
> On Tue, Apr 21, 2026 at 5:49 PM Ashutosh Bapat
> <[email protected]> wrote:
>> 
>> On Tue, Apr 21, 2026 at 1:29 PM SATYANARAYANA NARLAPURAM
>> <[email protected]> wrote:
>>> 
>>> Hi,
>>> 
>>> On Mon, Apr 20, 2026 at 11:58 PM Ashutosh Bapat <[email protected]> wrote:
>>>> 
>>>> On Tue, Apr 21, 2026 at 11:28 AM SATYANARAYANA NARLAPURAM
>>>> <[email protected]> wrote:
>>>>> 
>>>>> HI Ashutosh,
>>>>> 
>>>>> On Mon, Apr 20, 2026 at 10:34 PM Ashutosh Bapat <[email protected]> wrote:
>>>>>> 
>>>>>> On Mon, Apr 20, 2026 at 11:42 PM SATYANARAYANA NARLAPURAM
>>>>>> <[email protected]> wrote:
>>>>>>> 
>>>>>>> Hi hackers,
>>>>>>> 
>>>>>>> ALTER PROPERTY GRAPH ... ALTER ... DROP LABEL currently allows removing
>>>>>>> the last label from an element, leaving it with zero labels.
>>>>>>> 
>>>>>>> On assert-enabled builds, pg_get_propgraphdef() hits
>>>>>>> TRAP: failed Assert("count > 0"), File: "ruleutils.c", Line: 1837, PID: 1821840
>>>>>>> 
>>>>>>> Repro:
>>>>>>> 
>>>>>>> CREATE TABLE t (x int PRIMARY KEY, y int, z int);
>>>>>>> CREATE PROPERTY GRAPH g VERTEX TABLES (t KEY (x) LABEL l1 LABEL l2);
>>>>>>> ALTER PROPERTY GRAPH g ALTER VERTEX TABLE t DROP LABEL l2;
>>>>>>> ALTER PROPERTY GRAPH g ALTER VERTEX TABLE t DROP LABEL l1;
>>>>>>> SELECT pg_get_propgraphdef('g'::regclass);
>>>>>>> 
>>>>>>> We can fix it two ways, (1) Prevent dropping the last label; (2) handle zero labels.
>>>>>>> I feel it is easier to prevent dropping the last label than handling zero labels. Thoughts?
>>>>>>> 
>>>>>> 
>>>>>> SQL/PGQ standard section 11.25 syntax rule 6 says
>>>>>> "Element table descriptor shall include two or more labels, one of
>>>>>> which has an <identifier> that is equivalent to the <label name>
>>>>>> simply contained in the <drop element table label clause>."
>>>>>> 
>>>>>> IIUC this simply means that the last label can not be dropped. That
>>>>>> agrees with your chosen option.
>>>>>> 
>>>>>> In the patch,
>>>>>> + while (HeapTupleIsValid(systable_getnext(elscan)))
>>>>>> + nlabels++;
>>>>>> 
>>>>>> It's better to break from the while loop after incrementing nlabels
>>>>>> and avoid scanning the entire table in the worst case. All we want to
>>>>>> check is whether another label exists and not all the labels.
>>>>> 
>>>>> 
>>>>> Please find the attached v2 patch.
>>>>> 
>>>>>> 
>>>>>> 
>>>>>>> The attached patch adds a check in AlterPropGraph() before
>>>>>>> performDeletion(). It scans pg_propgraph_element_label to count labels
>>>>>>> for the element, and raises an error if only one remains. A regression test is included
>>>>>>> that drops labels down to the last one, verifies the error, then re-adds them back.
>>>>>> 
>>>>>> I would add a test to make sure ADD LABEL ... DROP LABEL .. is allowed.
>>>>> 
>>>>> 
>>>>> +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l1;  -- error: last label
>>>>> +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 ADD LABEL t3l2 PROPERTIES ALL COLUMNS;
>>>>> 
>>>>> Are you looking for any additional coverage?
>>>> 
>>>> I thought specifying ADD LABEL and DROP LABEL is supported in the same
>>>> DDL. But that's not the case. Sorry.
>>>> 
>>>> Will review the patch soon.
>>>> 
>>>> Additionally, the patch should update the DDL document to mention that
>>>> the DDL does not allow dropping the last label on the given graph
>>>> element table.
>>> 
>>> 
>>> Addressed this in v3 patch.
>> 
>> Thanks.
>> 
>> I questioned whether it makes sense to move the code to check the
>> existence of at least one other label into its own function so the
>> code is more readable. AlterPropGraph is already about 500 lines and
>> this code increases it further. There are two possibilities:
>> a. The function returns true if the given element has more than two
>> labels associated with it. The choice of 2 makes sense only in the
>> drop label context but not otherwise.
>> b. The function returns true if it finds one label other than the one
>> with the given label oid. That looks a bit more elegant. But still
>> seems too tied to drop label context.
>> 
>> So I decided against it. But instead removed the extra indentation.
>> The earliest variable declaration block is not farther from that code.
>> 
>> I changed the errcode to ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE
>> since we expect the element to be in a specific state to drop the
>> label. ERRCODE_INVALID_OBJECT_DEFINITION would mean that the
>> definition of the element or the label is itself invalid. That's not
>> the case.
>> 
>> Here's a patch with some minor edits.
>> 
> 
> 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.
> 
> This patch causes a conflict with the patch posted at [1]. Hence
> posting both the patches together here, so that they can be committed
> without conflict.
> 
> [1] https://www.postgresql.org/message-id/[email protected]
> 
> 
> 
> --
> Best Wishes,
> Ashutosh Bapat
> <v20260428-0001-Prevent-dropping-the-last-label-from-a-pro.patch><v20260428-0002-Dropping-a-property-not-associated-with-th.patch>

As Alvaro commented at [1], I added 0003 to use OidIsValid macro. 0001 and 0002 are unchanged.

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

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/






Attachments:

  [application/octet-stream] v20260429-0001-Prevent-dropping-the-last-label-from-a-pro.patch (6.8K, 2-v20260429-0001-Prevent-dropping-the-last-label-from-a-pro.patch)
  download | inline diff:
From 9e903a0e21c0c793b632515b6f4b533dc2bee5a8 Mon Sep 17 00:00:00 2001
From: satyanarayana narlapuram <[email protected]>
Date: Tue, 21 Apr 2026 07:52:56 +0000
Subject: [PATCH v20260429 1/3] 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.

Reported By: Satyanarayana Narlapuram <[email protected]>
Author: Satyanarayana Narlapuram <[email protected]>
Author: Ashutosh Bapat <[email protected]>
Reviewed by: Ashutosh Bapat <[email protected]>
Discussion: https://www.postgresql.org/message-id/CAHg+QDeP=mTHTV48R23zKMy1SBmCKZ_L7-z5zKnYyw+K0x-gCg@mail.gmail.com
---
 doc/src/sgml/ref/alter_property_graph.sgml    |  4 +-
 src/backend/commands/propgraphcmds.c          | 54 +++++++++++++++++--
 .../expected/create_property_graph.out        |  6 +++
 .../regress/sql/create_property_graph.sql     |  4 ++
 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 6d509fccf46..7836e7ab63e 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	elrel;
+		SysScanDesc elscan;
+		ScanKeyData elkey[1];
+		int			nlabels = 0;
+		HeapTuple	tuple;
+
 
 		if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX)
 			peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1);
@@ -1510,11 +1516,38 @@ 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?
+		 */
+		elrel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+		ScanKeyInit(&elkey[0],
+					Anum_pg_propgraph_element_label_pgelelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(peoid));
+		elscan = systable_beginscan(elrel, PropgraphElementLabelElementLabelIndexId,
+									true, NULL, 1, elkey);
+		while (HeapTupleIsValid(tuple = systable_getnext(elscan)))
+		{
+			Form_pg_propgraph_element_label elform = (Form_pg_propgraph_element_label) GETSTRUCT(tuple);
+
+			nlabels++;
 
+			if (elform->pgellabelid == labeloid)
+				ellabeloid = elform->oid;
+
+			/*
+			 * If we find more than one label associated with the given
+			 * element and also found the label we are going to drop, we are
+			 * done.
+			 */
+			if (nlabels > 1 && OidIsValid(ellabeloid))
+				break;
+		}
+		systable_endscan(elscan);
+		table_close(elrel, AccessShareLock);
+
+		/* Given label is not associated with the element. */
 		if (!ellabeloid)
 			ereport(ERROR,
 					errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -1522,6 +1555,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.
+		 */
+		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 bc9a596ec89..740f886cd83 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -57,6 +57,12 @@ 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.
+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 241f93df302..4cf771596a8 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -52,6 +52,10 @@ 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
+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);
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v20260429-0002-Dropping-a-property-not-associated-with-th.patch (6.2K, 3-v20260429-0002-Dropping-a-property-not-associated-with-th.patch)
  download | inline diff:
From 9c0b86a35698662138fd2da4212a00c92ffdbcc7 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email protected]>
Date: Tue, 28 Apr 2026 12:43:40 +0530
Subject: [PATCH v20260429 2/3] 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.

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 7836e7ab63e..d3c0443c336 100644
--- a/src/backend/commands/propgraphcmds.c
+++ b/src/backend/commands/propgraphcmds.c
@@ -1582,7 +1582,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);
@@ -1593,17 +1593,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),
@@ -1624,7 +1618,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)
@@ -1636,17 +1630,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,
@@ -1659,21 +1647,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 740f886cd83..7c3f25a3e0d 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -88,6 +88,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 4cf771596a8..191412a6a33 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -79,6 +79,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.50.1 (Apple Git-155)



  [application/octet-stream] v20260429-0003-Use-OidIsValid-macro-in-propgraphcmds.c.patch (6.9K, 4-v20260429-0003-Use-OidIsValid-macro-in-propgraphcmds.c.patch)
  download | inline diff:
From 9c0d71a61d0d604b99fa3ee9b009ab87e2d1e830 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Wed, 29 Apr 2026 20:29:00 +0800
Subject: [PATCH v20260429 3/3] Use OidIsValid macro in propgraphcmds.c

Author: Chao Li <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
 src/backend/commands/propgraphcmds.c | 44 ++++++++++++++--------------
 1 file changed, 22 insertions(+), 22 deletions(-)

diff --git a/src/backend/commands/propgraphcmds.c b/src/backend/commands/propgraphcmds.c
index d3c0443c336..04830262a54 100644
--- a/src/backend/commands/propgraphcmds.c
+++ b/src/backend/commands/propgraphcmds.c
@@ -206,16 +206,16 @@ CreatePropGraph(ParseState *pstate, const CreatePropGraphStmt *stmt)
 			if (strcmp(vinfo->aliasname, edge->edestvertex) == 0)
 				destrelid = vinfo->relid;
 
-			if (srcrelid && destrelid)
+			if (OidIsValid(srcrelid) && OidIsValid(destrelid))
 				break;
 		}
-		if (!srcrelid)
+		if (!OidIsValid(srcrelid))
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 					 errmsg("source vertex \"%s\" of edge \"%s\" does not exist",
 							edge->esrcvertex, einfo->aliasname),
 					 parser_errposition(pstate, edge->location)));
-		if (!destrelid)
+		if (!OidIsValid(destrelid))
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 					 errmsg("destination vertex \"%s\" of edge \"%s\" does not exist",
@@ -296,13 +296,13 @@ CreatePropGraph(ParseState *pstate, const CreatePropGraphStmt *stmt)
 				einfo->destvertexid = vinfo->elementid;
 				einfo->destrelid = vinfo->relid;
 			}
-			if (einfo->srcvertexid && einfo->destvertexid)
+			if (OidIsValid(einfo->srcvertexid) && OidIsValid(einfo->destvertexid))
 				break;
 		}
-		Assert(einfo->srcvertexid);
-		Assert(einfo->destvertexid);
-		Assert(einfo->srcrelid);
-		Assert(einfo->destrelid);
+		Assert(OidIsValid(einfo->srcvertexid));
+		Assert(OidIsValid(einfo->destvertexid));
+		Assert(OidIsValid(einfo->srcrelid));
+		Assert(OidIsValid(einfo->destrelid));
 		peoid = insert_element_record(pgaddress, einfo);
 		element_oids = lappend_oid(element_oids, peoid);
 	}
@@ -330,7 +330,7 @@ propgraph_element_get_key(ParseState *pstate, const List *key_clause, Relation e
 	{
 		Oid			pkidx = RelationGetPrimaryKeyIndex(element_rel, false);
 
-		if (!pkidx)
+		if (!OidIsValid(pkidx))
 			ereport(ERROR,
 					errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 					errmsg("no key specified and no suitable primary key exists for definition of element \"%s\"", aliasname),
@@ -681,7 +681,7 @@ insert_element_record(ObjectAddress pgaddress, struct element_info *einfo)
 	 * Add dependencies on vertices and equality operators used for key
 	 * comparison.
 	 */
-	if (einfo->srcvertexid)
+	if (OidIsValid(einfo->srcvertexid))
 	{
 		ObjectAddressSet(referenced, PropgraphElementRelationId, einfo->srcvertexid);
 		add_exact_object_address(&referenced, addrs);
@@ -689,7 +689,7 @@ insert_element_record(ObjectAddress pgaddress, struct element_info *einfo)
 		array_of_attnums_to_objectaddrs(einfo->srcrelid, einfo->srcref, addrs);
 		array_of_opers_to_objectaddrs(einfo->srceqop, addrs);
 	}
-	if (einfo->destvertexid)
+	if (OidIsValid(einfo->destvertexid))
 	{
 		ObjectAddressSet(referenced, PropgraphElementRelationId, einfo->destvertexid);
 		add_exact_object_address(&referenced, addrs);
@@ -751,7 +751,7 @@ insert_label_record(Oid graphid, Oid peoid, const char *label)
 	 * Insert into pg_propgraph_label if not already existing.
 	 */
 	labeloid = GetSysCacheOid2(PROPGRAPHLABELNAME, Anum_pg_propgraph_label_oid, ObjectIdGetDatum(graphid), CStringGetDatum(label));
-	if (!labeloid)
+	if (!OidIsValid(labeloid))
 	{
 		Relation	rel;
 		Datum		values[Natts_pg_propgraph_label] = {0};
@@ -1239,7 +1239,7 @@ check_element_label_properties(Oid ellabeloid)
 	/*
 	 * If there is no previous definition of this label, then we are done.
 	 */
-	if (!ref_ellabeloid)
+	if (!OidIsValid(ref_ellabeloid))
 		return;
 
 	/*
@@ -1299,7 +1299,7 @@ AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt)
 									   stmt->missing_ok ? RVR_MISSING_OK : 0,
 									   RangeVarCallbackOwnsRelation,
 									   NULL);
-	if (pgrelid == InvalidOid)
+	if (!OidIsValid(pgrelid))
 	{
 		ereport(NOTICE,
 				(errmsg("relation \"%s\" does not exist, skipping",
@@ -1509,7 +1509,7 @@ AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt)
 								   Anum_pg_propgraph_label_oid,
 								   ObjectIdGetDatum(pgrelid),
 								   CStringGetDatum(stmt->drop_label));
-		if (!labeloid)
+		if (!OidIsValid(labeloid))
 			ereport(ERROR,
 					errcode(ERRCODE_UNDEFINED_OBJECT),
 					errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
@@ -1548,7 +1548,7 @@ AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt)
 		table_close(elrel, AccessShareLock);
 
 		/* Given label is not associated with the element. */
-		if (!ellabeloid)
+		if (!OidIsValid(ellabeloid))
 			ereport(ERROR,
 					errcode(ERRCODE_UNDEFINED_OBJECT),
 					errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
@@ -1593,12 +1593,12 @@ AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt)
 								   Anum_pg_propgraph_label_oid,
 								   ObjectIdGetDatum(pgrelid),
 								   CStringGetDatum(stmt->alter_label));
-		if (labeloid)
+		if (OidIsValid(labeloid))
 			ellabeloid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL,
 										 Anum_pg_propgraph_element_label_oid,
 										 ObjectIdGetDatum(peoid),
 										 ObjectIdGetDatum(labeloid));
-		if (!ellabeloid)
+		if (!OidIsValid(ellabeloid))
 			ereport(ERROR,
 					errcode(ERRCODE_UNDEFINED_OBJECT),
 					errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
@@ -1630,13 +1630,13 @@ AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt)
 								   Anum_pg_propgraph_label_oid,
 								   ObjectIdGetDatum(pgrelid),
 								   CStringGetDatum(stmt->alter_label));
-		if (labeloid)
+		if (OidIsValid(labeloid))
 			ellabeloid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL,
 										 Anum_pg_propgraph_element_label_oid,
 										 ObjectIdGetDatum(peoid),
 										 ObjectIdGetDatum(labeloid));
 
-		if (!ellabeloid)
+		if (!OidIsValid(ellabeloid))
 			ereport(ERROR,
 					errcode(ERRCODE_UNDEFINED_OBJECT),
 					errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
@@ -1653,12 +1653,12 @@ AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt)
 									  Anum_pg_propgraph_property_oid,
 									  ObjectIdGetDatum(pgrelid),
 									  CStringGetDatum(propname));
-			if (propoid)
+			if (OidIsValid(propoid))
 				plpoid = GetSysCacheOid2(PROPGRAPHLABELPROP,
 										 Anum_pg_propgraph_label_property_oid,
 										 ObjectIdGetDatum(ellabeloid),
 										 ObjectIdGetDatum(propoid));
-			if (!plpoid)
+			if (!OidIsValid(plpoid))
 				ereport(ERROR,
 						errcode(ERRCODE_UNDEFINED_OBJECT),
 						errmsg("property graph \"%s\" element \"%s\" label \"%s\" has no property \"%s\"",
-- 
2.50.1 (Apple Git-155)



^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: [Bug][patch]: After dropping the last label from a property graph element, invoking pg_get_propgraphdef() triggers an assertion failure
@ 2026-05-04 14:46  Peter Eisentraut <[email protected]>
  parent: Ashutosh Bapat <[email protected]>
  1 sibling, 1 reply; 13+ messages in thread

From: Peter Eisentraut @ 2026-05-04 14:46 UTC (permalink / raw)
  To: Ashutosh Bapat <[email protected]>; SATYANARAYANA NARLAPURAM <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

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.

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.






^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: [Bug][patch]: After dropping the last label from a property graph element, invoking pg_get_propgraphdef() triggers an assertion failure
@ 2026-05-04 14:47  Peter Eisentraut <[email protected]>
  parent: Chao Li <[email protected]>
  0 siblings, 1 reply; 13+ messages in thread

From: Peter Eisentraut @ 2026-05-04 14:47 UTC (permalink / raw)
  To: Chao Li <[email protected]>; Ashutosh Bapat <[email protected]>; +Cc: SATYANARAYANA NARLAPURAM <[email protected]>; PostgreSQL Hackers <[email protected]>

On 29.04.26 14:58, Chao Li wrote:
> As Alvaro commented at [1], I added 0003 to use OidIsValid macro. 0001 and 0002 are unchanged.
> 
> [1]https://www.postgresql.org/message-id/02fe13db-4fba-4e9d-9b4c- 
> [email protected]

I'm personally not on board with that coding style rule.  At least I 
don't consider this patch necessary right now as we're working toward 
stabilizing the current code.






^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: [Bug][patch]: After dropping the last label from a property graph element, invoking pg_get_propgraphdef() triggers an assertion failure
@ 2026-05-05 03:01  Chao Li <[email protected]>
  parent: Peter Eisentraut <[email protected]>
  0 siblings, 0 replies; 13+ messages in thread

From: Chao Li @ 2026-05-05 03:01 UTC (permalink / raw)
  To: Peter Eisentraut <[email protected]>; +Cc: Ashutosh Bapat <[email protected]>; SATYANARAYANA NARLAPURAM <[email protected]>; PostgreSQL Hackers <[email protected]>



> On May 4, 2026, at 22:47, Peter Eisentraut <[email protected]> wrote:
> 
> On 29.04.26 14:58, Chao Li wrote:
>> As Alvaro commented at [1], I added 0003 to use OidIsValid macro. 0001 and 0002 are unchanged.
>> [1]https://www.postgresql.org/message-id/02fe13db-4fba-4e9d-9b4c- [email protected]
> 
> I'm personally not on board with that coding style rule.  At least I don't consider this patch necessary right now as we're working toward stabilizing the current code.
> 

In the current code in propgraphcmds.c, the style is already inconsistent. I see three forms:

At line 434:
```
            if (OidIsValid(opc))
```

At line 754:
```
    if (!labeloid)
```

At lne 1302:
```
    if (pgrelid == InvalidOid)
```

Anyway, I don’t have a strong opinion here, so please feel free to discard 0003.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/









^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: [Bug][patch]: After dropping the last label from a property graph element, invoking pg_get_propgraphdef() triggers an assertion failure
@ 2026-05-12 01:35  Ashutosh Bapat <[email protected]>
  parent: Peter Eisentraut <[email protected]>
  0 siblings, 1 reply; 13+ messages in thread

From: Ashutosh Bapat @ 2026-05-12 01:35 UTC (permalink / raw)
  To: Peter Eisentraut <[email protected]>; +Cc: SATYANARAYANA NARLAPURAM <[email protected]>; PostgreSQL Hackers <[email protected]>

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?

-- 
Best Wishes,
Ashutosh Bapat





^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: [Bug][patch]: After dropping the last label from a property graph element, invoking pg_get_propgraphdef() triggers an assertion failure
@ 2026-05-12 07:01  Ashutosh Bapat <[email protected]>
  parent: Ashutosh Bapat <[email protected]>
  0 siblings, 0 replies; 13+ messages in thread

From: Ashutosh Bapat @ 2026-05-12 07:01 UTC (permalink / raw)
  To: Peter Eisentraut <[email protected]>; +Cc: SATYANARAYANA NARLAPURAM <[email protected]>; PostgreSQL Hackers <[email protected]>

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.

-- 
Best Wishes,
Ashutosh Bapat


Attachments:

  [text/x-patch] v20260512-0001-Prevent-dropping-the-last-label-from-a-pro.patch (10.1K, 2-v20260512-0001-Prevent-dropping-the-last-label-from-a-pro.patch)
  download | inline diff:
From dafd688d335d417c7c1e05418646000fb635d87f Mon Sep 17 00:00:00 2001
From: satyanarayana narlapuram <[email protected]>
Date: Tue, 21 Apr 2026 07:52:56 +0000
Subject: [PATCH v20260512 1/4] 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.

Add a test to make sure that dropping labels concurrently from the same
element has the same effect.

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-gCg@mail.gmail.com
---
 doc/src/sgml/ref/alter_property_graph.sgml    |  4 +-
 src/backend/commands/propgraphcmds.c          | 54 +++++++++++++++++--
 .../drop-propgraph-label-concurrently.out     | 20 +++++++
 src/test/isolation/isolation_schedule         |  1 +
 .../drop-propgraph-label-concurrently.spec    | 36 +++++++++++++
 .../expected/create_property_graph.out        |  6 +++
 .../regress/sql/create_property_graph.sql     |  4 ++
 7 files changed, 119 insertions(+), 6 deletions(-)
 create mode 100644 src/test/isolation/expected/drop-propgraph-label-concurrently.out
 create mode 100644 src/test/isolation/specs/drop-propgraph-label-concurrently.spec

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..59b2f76ca4f 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 = 0;
+		HeapTuple	tuple;
+
 
 		if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX)
 			peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1);
@@ -1510,11 +1516,38 @@ 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?
+		 */
+		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);
+		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 we find more than one label associated with the given
+			 * element and also found the label we are going to drop, we are
+			 * done.
+			 */
+			if (nlabels > 1 && ellabeloid)
+				break;
+		}
+		systable_endscan(ellabelscan);
+		table_close(ellabelrel, AccessShareLock);
+
+		/* Given label is not associated with the element. */
 		if (!ellabeloid)
 			ereport(ERROR,
 					errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -1522,6 +1555,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.
+		 */
+		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/isolation/expected/drop-propgraph-label-concurrently.out b/src/test/isolation/expected/drop-propgraph-label-concurrently.out
new file mode 100644
index 00000000000..a3fc89fbac1
--- /dev/null
+++ b/src/test/isolation/expected/drop-propgraph-label-concurrently.out
@@ -0,0 +1,20 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1b s1drop1 s2b s2drop2 s1c s2c
+step s1b: BEGIN;
+step s1drop1: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 DROP LABEL pgl1;
+step s2b: BEGIN;
+step s2drop2: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 DROP LABEL pgl2; <waiting ...>
+step s1c: COMMIT;
+step s2drop2: <... completed>
+ERROR:  cannot drop the last label from element "pgt1"
+step s2c: COMMIT;
+
+starting permutation: s1b s1drop1 s2b s2drop2 s1r s2c
+step s1b: BEGIN;
+step s1drop1: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 DROP LABEL pgl1;
+step s2b: BEGIN;
+step s2drop2: ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 DROP LABEL pgl2; <waiting ...>
+step s1r: ROLLBACK;
+step s2drop2: <... completed>
+step s2c: COMMIT;
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 1578ba191c8..9c64ee43641 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -126,3 +126,4 @@ test: serializable-parallel-3
 test: matview-write-skew
 test: lock-nowait
 test: for-portion-of
+test: drop-propgraph-label-concurrently
diff --git a/src/test/isolation/specs/drop-propgraph-label-concurrently.spec b/src/test/isolation/specs/drop-propgraph-label-concurrently.spec
new file mode 100644
index 00000000000..ce8f4351364
--- /dev/null
+++ b/src/test/isolation/specs/drop-propgraph-label-concurrently.spec
@@ -0,0 +1,36 @@
+# ALTER PROPERTY GRAPH ... DROP LABEL - concurrent
+#
+# Verify that two concurrent transactions cannot both drop a label from
+# the same element such that the element ends up with no labels.
+
+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 IF EXISTS pgg1;
+ DROP TABLE pgt1;
+}
+
+session s1
+step s1b	{ BEGIN; }
+step s1drop1	{ ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 DROP LABEL pgl1; }
+step s1c	{ COMMIT; }
+step s1r	{ ROLLBACK; }
+
+session s2
+step s2b	{ BEGIN; }
+step s2drop2	{ ALTER PROPERTY GRAPH pgg1 ALTER VERTEX TABLE pgt1 DROP LABEL pgl2; }
+step s2c	{ COMMIT; }
+
+# s2drop2 fails since by then pgl2 is the only remaining label on pgt1
+permutation s1b s1drop1 s2b s2drop2 s1c s2c
+
+# s2drop2 succeeds since rollback leaves pgl1 behind
+permutation s1b s1drop1 s2b s2drop2 s1r s2c
diff --git a/src/test/regress/expected/create_property_graph.out b/src/test/regress/expected/create_property_graph.out
index f625524abce..dd6c67167e5 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -57,6 +57,12 @@ 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.
+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 241f93df302..4cf771596a8 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -52,6 +52,10 @@ 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
+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: 8268e41aca23ae3414360b0a1dc6ae99ea7b43f4
-- 
2.34.1



  [text/x-patch] v20260512-0002-Dropping-a-property-not-associated-with-th.patch (6.3K, 3-v20260512-0002-Dropping-a-property-not-associated-with-th.patch)
  download | inline diff:
From cab09376bc8f5b6729e75b67124adb3fc76c7cd8 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email protected]>
Date: Tue, 28 Apr 2026 12:43:40 +0530
Subject: [PATCH v20260512 2/4] 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 59b2f76ca4f..fc998b3e244 100644
--- a/src/backend/commands/propgraphcmds.c
+++ b/src/backend/commands/propgraphcmds.c
@@ -1582,7 +1582,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);
@@ -1593,17 +1593,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),
@@ -1624,7 +1618,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)
@@ -1636,17 +1630,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,
@@ -1659,21 +1647,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 dd6c67167e5..d294b8b9a7e 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -88,6 +88,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 4cf771596a8..191412a6a33 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -79,6 +79,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



^ permalink  raw  reply  [nested|flat] 13+ messages in thread


end of thread, other threads:[~2026-05-12 07:01 UTC | newest]

Thread overview: 13+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-04-20 18:12 [Bug][patch]: After dropping the last label from a property graph element, invoking pg_get_propgraphdef() triggers an assertion failure SATYANARAYANA NARLAPURAM <[email protected]>
2026-04-21 05:33 ` Ashutosh Bapat <[email protected]>
2026-04-21 05:58   ` SATYANARAYANA NARLAPURAM <[email protected]>
2026-04-21 06:58     ` Ashutosh Bapat <[email protected]>
2026-04-21 07:58       ` SATYANARAYANA NARLAPURAM <[email protected]>
2026-04-21 12:19         ` Ashutosh Bapat <[email protected]>
2026-04-28 15:02           ` Ashutosh Bapat <[email protected]>
2026-04-29 12:58             ` Chao Li <[email protected]>
2026-05-04 14:47               ` Peter Eisentraut <[email protected]>
2026-05-05 03:01                 ` Chao Li <[email protected]>
2026-05-04 14:46             ` Peter Eisentraut <[email protected]>
2026-05-12 01:35               ` Ashutosh Bapat <[email protected]>
2026-05-12 07:01                 ` Ashutosh Bapat <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox