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