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
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-...@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
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
