This is an automated email from the ASF dual-hosted git repository.
Cole-Greer pushed a commit to branch 3.8-dev
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
The following commit(s) were added to refs/heads/3.8-dev by this push:
new 5f1c592a59 TINKERPOP-3294: AddPropertyStep does not capture the old
property for EventStrategy when cardinality is not specified (#3715)
5f1c592a59 is described below
commit 5f1c592a599bb22a6f1a9da9ff3e9e2233c00103
Author: Paul Lartigau <[email protected]>
AuthorDate: Tue Oct 27 17:59:55 2026 +0100
TINKERPOP-3294: AddPropertyStep does not capture the old property for
EventStrategy when cardinality is not specified (#3715)
captureRemovedProperty() was checking the step's cardinality field, which
is null when property() is called without an explicit cardinality. The old
value passed to vertexPropertyChanged() was then always empty, even though the
property itself was written with the graph's default cardinality.
The effective cardinality (explicit one, or the graph default for the key)
was already computed in sideEffect() but not used. It is now passed to
captureRemovedProperty().
Assisted-by: Claude Code:claude-opus-5-5
---------
Co-authored-by: Paul Lartigau <[email protected]>
---
CHANGELOG.asciidoc | 2 +
.../traversal/step/sideEffect/AddPropertyStep.java | 25 ++--
.../decoration/EventStrategyProcessTest.java | 131 +++++++++++++++++++++
.../gremlin/tinkergraph/TinkerGraphProvider.java | 15 ++-
.../TinkerTransactionGraphProvider.java | 15 ++-
5 files changed, 176 insertions(+), 12 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 913b838581..f568cbec14 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -27,6 +27,8 @@
image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
This release also includes changes from prior 3.7.x releases.
+* Fixed `AddPropertyStep` not passing the old property to `EventStrategy`
listeners when the cardinality was not specified in `property()`.
+
[[release-3-8-2]]
=== TinkerPop 3.8.2 (Release Date: September 1, 2026)
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddPropertyStep.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddPropertyStep.java
index 4223f98bd5..e2ee1ddfaf 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddPropertyStep.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddPropertyStep.java
@@ -104,6 +104,10 @@ public class AddPropertyStep<S extends Element> extends
SideEffectStep<S> implem
"Property cardinality can only be set for a Vertex but the
traversal encountered %s for key: %s",
element.getClass().getSimpleName(), key));
+ final VertexProperty.Cardinality effectiveCardinality =
this.cardinality != null
+ ? this.cardinality
+ : element.graph().features().vertex().getCardinality(key);
+
final Optional<EventStrategy> optEventStrategy =
getTraversal().getStrategies().getStrategy(EventStrategy.class);
final boolean eventingIsConfigured =
EventUtil.hasAnyCallbacks(callbackRegistry)
&& optEventStrategy.isPresent();
@@ -111,17 +115,13 @@ public class AddPropertyStep<S extends Element> extends
SideEffectStep<S> implem
// find property to remove. only need to capture the removedProperty
if eventing is configured
final Property removedProperty = eventingIsConfigured ?
- captureRemovedProperty(element, key, value, es) :
+ captureRemovedProperty(element, key, value,
effectiveCardinality, es) :
VertexProperty.empty();
- final VertexProperty.Cardinality card = this.cardinality != null
- ? this.cardinality
- : element.graph().features().vertex().getCardinality(key);
-
// update property
if (element instanceof Vertex) {
- if (null != card) {
- ((Vertex) element).property(card, key, value,
vertexPropertyKeyValues);
+ if (null != effectiveCardinality) {
+ ((Vertex) element).property(effectiveCardinality, key, value,
vertexPropertyKeyValues);
} else if (vertexPropertyKeyValues.length > 0) {
((Vertex) element).property(key, value,
vertexPropertyKeyValues);
} else {
@@ -140,11 +140,12 @@ public class AddPropertyStep<S extends Element> extends
SideEffectStep<S> implem
}
private Property captureRemovedProperty(final Element element, final
String key, final Object value,
+ final VertexProperty.Cardinality
effectiveCardinality,
final EventStrategy es){
Property removedProperty = VertexProperty.empty();
if (element instanceof Vertex) {
- if (cardinality == VertexProperty.Cardinality.set) {
+ if (effectiveCardinality == VertexProperty.Cardinality.set) {
final Iterator<? extends Property> properties =
element.properties(key);
while (properties.hasNext()) {
final Property property = properties.next();
@@ -153,8 +154,12 @@ public class AddPropertyStep<S extends Element> extends
SideEffectStep<S> implem
break;
}
}
- } else if (cardinality == VertexProperty.Cardinality.single) {
- removedProperty = element.property(key);
+ } else if (effectiveCardinality ==
VertexProperty.Cardinality.single) {
+ // Only tracks the first removed property, potentially missing
events if existing cardinality was Set or List. TINKERPOP-3300
+ final Iterator<? extends Property> existing =
element.properties(key);
+ if (existing.hasNext()) {
+ removedProperty = existing.next();
+ }
}
} else {
removedProperty = element.property(key);
diff --git
a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/EventStrategyProcessTest.java
b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/EventStrategyProcessTest.java
index 0bad338732..4c4c8d434e 100644
---
a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/EventStrategyProcessTest.java
+++
b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/EventStrategyProcessTest.java
@@ -57,6 +57,7 @@ import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
/**
* @author Stephen Mallette (http://stephen.genoprime.com)
@@ -1051,6 +1052,136 @@ public class EventStrategyProcessTest extends
AbstractGremlinProcessTest {
assertThat(triggered.get(), is(true));
}
+ @Test
+ @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
+ public void
shouldCaptureOldVertexPropertyWhenChangedWithDefaultCardinality() {
+ assumeTrue(graph.features().vertex().getCardinality("to-change") ==
VertexProperty.Cardinality.single);
+
+ final AtomicBoolean triggered = new AtomicBoolean(false);
+ final Vertex v = graph.addVertex();
+ v.property("to-change", "blah");
+
+ final MutationListener listener = new AbstractMutationListener() {
+ @Override
+ public void vertexPropertyChanged(final Vertex element, final
VertexProperty oldValue, final Object setValue, final Object...
vertexPropertyKeyValues) {
+ assertEquals("to-change", oldValue.key());
+ assertEquals("blah", oldValue.value());
+ assertEquals("dah", setValue);
+ triggered.set(true);
+ }
+ };
+ final EventStrategy.Builder builder =
EventStrategy.build().addListener(listener);
+
+ if (graph.features().graph().supportsTransactions())
+ builder.eventQueue(new
EventStrategy.TransactionalEventQueue(graph));
+
+ final EventStrategy eventStrategy = builder.create();
+ final GraphTraversalSource gts = create(eventStrategy);
+
+ gts.V(v).property("to-change", "dah").iterate();
+ tryCommit(graph);
+
+ assertEquals(1, IteratorUtils.count(g.V(v).properties()));
+ assertThat(triggered.get(), is(true));
+ }
+
+ @Test
+ @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
+ public void
shouldCaptureOldVertexPropertyWhenChangedWithDefaultSetCardinality() {
+ assumeTrue(graph.features().vertex().getCardinality("to-change") ==
VertexProperty.Cardinality.set);
+
+ final AtomicBoolean triggered = new AtomicBoolean(false);
+ final Vertex v = graph.addVertex();
+ v.property("to-change", "blah");
+
+ final MutationListener listener = new AbstractMutationListener() {
+ @Override
+ public void vertexPropertyChanged(final Vertex element, final
VertexProperty oldValue, final Object setValue, final Object...
vertexPropertyKeyValues) {
+ assertEquals("to-change", oldValue.key());
+ assertEquals("blah", oldValue.value());
+ assertEquals("blah", setValue);
+ triggered.set(true);
+ }
+ };
+ final EventStrategy.Builder builder =
EventStrategy.build().addListener(listener);
+
+ if (graph.features().graph().supportsTransactions())
+ builder.eventQueue(new
EventStrategy.TransactionalEventQueue(graph));
+
+ final EventStrategy eventStrategy = builder.create();
+ final GraphTraversalSource gts = create(eventStrategy);
+
+ gts.V(v).property("to-change", "blah").iterate();
+ tryCommit(graph);
+
+ assertEquals(1, IteratorUtils.count(g.V(v).properties()));
+ assertThat(triggered.get(), is(true));
+ }
+
+ @Test
+ @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
+ public void
shouldNotCaptureOldVertexPropertyWhenValueIsNewWithDefaultSetCardinality() {
+ assumeTrue(graph.features().vertex().getCardinality("to-change") ==
VertexProperty.Cardinality.set);
+
+ final AtomicBoolean triggered = new AtomicBoolean(false);
+ final Vertex v = graph.addVertex();
+ v.property("to-change", "blah");
+
+ final MutationListener listener = new AbstractMutationListener() {
+ @Override
+ public void vertexPropertyChanged(final Vertex element, final
VertexProperty oldValue, final Object setValue, final Object...
vertexPropertyKeyValues) {
+ assertThat(oldValue.isPresent(), is(false));
+ assertEquals("dah", setValue);
+ triggered.set(true);
+ }
+ };
+ final EventStrategy.Builder builder =
EventStrategy.build().addListener(listener);
+
+ if (graph.features().graph().supportsTransactions())
+ builder.eventQueue(new
EventStrategy.TransactionalEventQueue(graph));
+
+ final EventStrategy eventStrategy = builder.create();
+ final GraphTraversalSource gts = create(eventStrategy);
+
+ gts.V(v).property("to-change", "dah").iterate();
+ tryCommit(graph);
+
+ assertEquals(2, IteratorUtils.count(g.V(v).properties()));
+ assertThat(triggered.get(), is(true));
+ }
+
+ @Test
+ @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
+ public void
shouldNotCaptureOldVertexPropertyWhenAddedWithDefaultListCardinality() {
+ assumeTrue(graph.features().vertex().getCardinality("to-change") ==
VertexProperty.Cardinality.list);
+
+ final AtomicBoolean triggered = new AtomicBoolean(false);
+ final Vertex v = graph.addVertex();
+ v.property("to-change", "blah");
+
+ final MutationListener listener = new AbstractMutationListener() {
+ @Override
+ public void vertexPropertyChanged(final Vertex element, final
VertexProperty oldValue, final Object setValue, final Object...
vertexPropertyKeyValues) {
+ assertThat(oldValue.isPresent(), is(false));
+ assertEquals("blah", setValue);
+ triggered.set(true);
+ }
+ };
+ final EventStrategy.Builder builder =
EventStrategy.build().addListener(listener);
+
+ if (graph.features().graph().supportsTransactions())
+ builder.eventQueue(new
EventStrategy.TransactionalEventQueue(graph));
+
+ final EventStrategy eventStrategy = builder.create();
+ final GraphTraversalSource gts = create(eventStrategy);
+
+ gts.V(v).property("to-change", "blah").iterate();
+ tryCommit(graph);
+
+ assertEquals(2, IteratorUtils.count(g.V(v).properties()));
+ assertThat(triggered.get(), is(true));
+ }
+
@Test
@FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
public void shouldDetachVertexPropertyWhenNew() {
diff --git
a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphProvider.java
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphProvider.java
index 6d9dce7c01..ed595ed3dc 100644
---
a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphProvider.java
+++
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphProvider.java
@@ -22,6 +22,7 @@ import org.apache.commons.configuration2.Configuration;
import org.apache.tinkerpop.gremlin.AbstractGraphProvider;
import org.apache.tinkerpop.gremlin.LoadGraphWith;
import org.apache.tinkerpop.gremlin.TestHelper;
+import
org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategyProcessTest;
import org.apache.tinkerpop.gremlin.structure.Graph;
import org.apache.tinkerpop.gremlin.structure.GraphTest;
import org.apache.tinkerpop.gremlin.structure.VertexProperty;
@@ -70,6 +71,8 @@ public class TinkerGraphProvider extends
AbstractGraphProvider {
put(TinkerGraph.GREMLIN_TINKERGRAPH_VERTEX_PROPERTY_ID_MANAGER,
idMaker);
if (requiresListCardinalityAsDefault(loadGraphWith, test,
testMethodName))
put(TinkerGraph.GREMLIN_TINKERGRAPH_DEFAULT_VERTEX_PROPERTY_CARDINALITY,
VertexProperty.Cardinality.list.name());
+ if (requiresSetCardinalityAsDefault(test, testMethodName))
+
put(TinkerGraph.GREMLIN_TINKERGRAPH_DEFAULT_VERTEX_PROPERTY_CARDINALITY,
VertexProperty.Cardinality.set.name());
if (requiresPersistence(test, testMethodName)) {
put(TinkerGraph.GREMLIN_TINKERGRAPH_GRAPH_FORMAT, "gryo");
put(TinkerGraph.GREMLIN_TINKERGRAPH_GRAPH_LOCATION,TestHelper.makeTestDataFile(test,
"temp", testMethodName + ".kryo"));
@@ -109,7 +112,17 @@ public class TinkerGraphProvider extends
AbstractGraphProvider {
final Class<?>
test, final String testMethodName) {
return loadGraphWith == LoadGraphWith.GraphData.CREW
|| (test == StarGraphTest.class &&
testMethodName.equals("shouldAttachWithCreateMethod"))
- || (test == DetachedGraphTest.class &&
testMethodName.equals("testAttachableCreateMethod"));
+ || (test == DetachedGraphTest.class &&
testMethodName.equals("testAttachableCreateMethod"))
+ || (test == EventStrategyProcessTest.class &&
testMethodName.equals("shouldNotCaptureOldVertexPropertyWhenAddedWithDefaultListCardinality"));
+ }
+
+ /**
+ * Determines if a test requires set cardinality as the default.
+ */
+ protected static boolean requiresSetCardinalityAsDefault(final Class<?>
test, final String testMethodName) {
+ return test == EventStrategyProcessTest.class
+ &&
(testMethodName.equals("shouldCaptureOldVertexPropertyWhenChangedWithDefaultSetCardinality")
+ ||
testMethodName.equals("shouldNotCaptureOldVertexPropertyWhenValueIsNewWithDefaultSetCardinality"));
}
/**
diff --git
a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerTransactionGraphProvider.java
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerTransactionGraphProvider.java
index 8248e296c2..e82847148e 100644
---
a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerTransactionGraphProvider.java
+++
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerTransactionGraphProvider.java
@@ -22,6 +22,7 @@ import org.apache.commons.configuration2.Configuration;
import org.apache.tinkerpop.gremlin.AbstractGraphProvider;
import org.apache.tinkerpop.gremlin.LoadGraphWith;
import org.apache.tinkerpop.gremlin.TestHelper;
+import
org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategyProcessTest;
import org.apache.tinkerpop.gremlin.structure.Graph;
import org.apache.tinkerpop.gremlin.structure.GraphTest;
import org.apache.tinkerpop.gremlin.structure.VertexProperty;
@@ -69,6 +70,8 @@ public class TinkerTransactionGraphProvider extends
AbstractGraphProvider {
put(TinkerTransactionGraph.GREMLIN_TINKERGRAPH_VERTEX_PROPERTY_ID_MANAGER,
idMaker);
if (requiresListCardinalityAsDefault(loadGraphWith, test,
testMethodName))
put(TinkerTransactionGraph.GREMLIN_TINKERGRAPH_DEFAULT_VERTEX_PROPERTY_CARDINALITY,
VertexProperty.Cardinality.list.name());
+ if (requiresSetCardinalityAsDefault(test, testMethodName))
+
put(TinkerTransactionGraph.GREMLIN_TINKERGRAPH_DEFAULT_VERTEX_PROPERTY_CARDINALITY,
VertexProperty.Cardinality.set.name());
if (requiresPersistence(test, testMethodName)) {
put(TinkerTransactionGraph.GREMLIN_TINKERGRAPH_GRAPH_FORMAT,
"gryo");
put(TinkerTransactionGraph.GREMLIN_TINKERGRAPH_GRAPH_LOCATION,TestHelper.makeTestDataFile(test,
"temp", testMethodName + ".kryo"));
@@ -108,7 +111,17 @@ public class TinkerTransactionGraphProvider extends
AbstractGraphProvider {
final Class<?>
test, final String testMethodName) {
return loadGraphWith == LoadGraphWith.GraphData.CREW
|| (test == StarGraphTest.class &&
testMethodName.equals("shouldAttachWithCreateMethod"))
- || (test == DetachedGraphTest.class &&
testMethodName.equals("testAttachableCreateMethod"));
+ || (test == DetachedGraphTest.class &&
testMethodName.equals("testAttachableCreateMethod"))
+ || (test == EventStrategyProcessTest.class &&
testMethodName.equals("shouldNotCaptureOldVertexPropertyWhenAddedWithDefaultListCardinality"));
+ }
+
+ /**
+ * Determines if a test requires set cardinality as the default.
+ */
+ protected static boolean requiresSetCardinalityAsDefault(final Class<?>
test, final String testMethodName) {
+ return test == EventStrategyProcessTest.class
+ &&
(testMethodName.equals("shouldCaptureOldVertexPropertyWhenChangedWithDefaultSetCardinality")
+ ||
testMethodName.equals("shouldNotCaptureOldVertexPropertyWhenValueIsNewWithDefaultSetCardinality"));
}
/**