Skip to content

Conversation

Cole-Greer
Copy link
Contributor

@Cole-Greer Cole-Greer commented Sep 17, 2025

The TraversalParent methods were not consistently implemented in the new placeholder steps. This commit adds proper implementations in all TraversalParent placeholder steps, and introduces TraversalParentTest.

TraversalParentTest takes traversals with TraversalParents and verifies that getGlobalChildren() and getLocalChildren() are producing the expected children, and that those children have correctly set their parents. These checks are done before and after strategy application. This is primarily intended for placeholder steps to ensure that TraversalParent remains correctly implemented in both placeholder and concrete steps, however it is also useful to verify against interference from strategies.

The new tests identified an existing deficiency DateDiffStep which was also addressed.

VOTE +1

The TraversalParent methods were not consistently implemented in the new placeholder steps.
This commit adds proper implementations in all TraversalParent placeholder steps, and introduces
TraversalParentTest.

TraversalParentTest takes traversals with TraversalParents and verifies that getGlobalChildren() and getLocalChildren()
are producing the expected children, and that those children have correctly set their parents. These checks are done before
and after strategy application. This is primarily intended for placeholder steps to ensure that TraversalParent remains
correctly implemented in both placeholder and concrete steps, however it is also useful to verify against interference from
strategies.

The new tests identified an existing deficiency DateDiffStep which was also addressed
GValueConstantTraversal<S, E> clone = new GValueConstantTraversal<>(GValue.of(this.end.getName(), this.end.get()));
try {
clone.setGValueManager(this.getGValueManager().clone());
} catch (CloneNotSupportedException e) { //TODO:: handle properly
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There needs to be a revisit for cloning of GValues, GValueManager, and StepPlaceholders. I consider this out of scope for this PR and intend to address this soon.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that there are fields in the parent class AbstractLambdaTraversal which are not cloned by this current logic such as bypassTraversal.

@spmallette
Copy link
Contributor

taking some build errors it seems - also, i'd think you need to backport changes for DateDiffStep and TraversalSelectStep.

@Cole-Greer
Copy link
Contributor Author

taking some build errors it seems - also, i'd think you need to backport changes for DateDiffStep and TraversalSelectStep.

Ahh, new issues snuck their in with a last minute tweak to merge. Should be good now.

I'll CTR the fixes to DateDiffStep and TraversalSelectStep back to 3.7-dev separately.

}

@Override
public List<Traversal.Admin<?, ?>> getLocalChildren() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the parent interface generic contract which is defined as List<Traversal.Admin<S, E>> getLocalChildren too narrow since it needs to be modified here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TraversalParent interface itself does not use generics. The S and E type variables are only bound on the getLocalChildren method itself. That means that the S and E defined in public default <S, E> List<Traversal.Admin<S, E>> getLocalChildren() are not the same S and E defined in this implementing step classes.

They should be independent, the addEdge step here uses <S, Edge> (can start with anything and always produces an Edge), but it's child traversals do not produce edges, the from/to traversals will produce a Vertex or some ID type, the label traversal will produce a String...

I think it would be most clear if TraversalParent was updated to just return List<Traversal.Admin<?, ?>> instead, however I considered that out of scope for this PR.

import java.util.Set;

/**
* Indicates that a step can contain child Traversals. Any step which implements this interface should override at least
Copy link
Contributor

@andreachild andreachild Sep 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Likely out of scope for this PR but I feel it would be helpful in general for TinkerPop to have more custom annotations. In this case there could be a custom annotation like @ChildTraversal(scope=Type.LOCAL) to mark step fields that store a child traversal. Then there can be a single common method which handles the presence of child traversals in AbstractStep.java like (pseudocode):

@Override
public void setTraversal(final Traversal.Admin<?, ?> traversal) {
    this.traversal = traversal;
    // retrieve any step fields annotated with @ChildTraversal
    // for each child traversal, call integrateChild
}

And then there could also be an AbstractTravesalParent.java which has:

<S, E> List<Traversal.Admin<S, E>> getGlobalChildren() {
    // use reflection to retrieve fields annotated with @ChildTraversal(type=GLOBAL)
}

<S, E> List<Traversal.Admin<S, E>> getLocalChildren() {
    // use reflection to retrieve fields annotated with @ChildTraversal(type=LOCAL)
}

Then when adding new steps or adding child traversals to an existing step, all you would need to do is add the custom annotation and you wouldn't have to 'remember' to do the boilerplate logic.

If you agree with this suggestion it's possible to use an annotation for these affected classes only and then slowly migrate towards 100% usage of the annotation in future PRs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's definitely a compelling idea and would be an improvement over the status quo. I'm not sure about half starting it here in this PR though. I would rather such a change be made all at once, as I'm worried there are a few steps which will be difficult to adapt to the annotation model, and may require tweaking the design. The steps which come to mind which may be difficult are the ones like AddEdge which currently shove all of their arguments into a Parameters object and use that to manage child Traversals.

I think this might make for a good JIRA. I don't think it's prohibitively large to tackle all at once as a standalone task. There's only about 60 implementations of TraversalParent when you factor out abstract classes and such, and there's really only a handful of unique patterns within those, they are mostly simple and repetitive.

throw new IllegalArgumentException("GValue cannot be used as a property key");
}
if (key instanceof Traversal) {
this.integrateChild(((Traversal<?, ?>) key).asAdmin());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should integrateChild also be called by setLabel method if the label is a traversal?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it should, same goes for setElementId. That appears to be a gap in the testing. I will make that fix and add those cases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the clone logic, it appears that properties can contain traversals. If that is the case then addProperty should also call integrateChild.

throw new IllegalArgumentException("GValue cannot be used as a property key");
}
if (key instanceof Traversal) {
this.integrateChild(((Traversal<?, ?>) key).asAdmin());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does anything need to be done to 'de-integrate' a child traversal if a property is removed?

/**
* Get the property value
* Gets the property value. If the value was originally passed as a {@link GValue<?>}, {@link ConstantTraversal <?>},
* or a literal value, then the literal value is returned. Otherwise, the MergeMap is returned in Traversal form.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a MergeMap in this context?

this.value = ((Traversal<?, ?>) valueObject).asAdmin();
this.integrateChild(((Traversal<?, ?>) valueObject).asAdmin());
} else {
this.value = new ConstantTraversal<>(valueObject);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do ConstantTraversals not need to be integrated as a child traversal?

@Override
public void setTraversal(final Traversal.Admin<?, ?> parentTraversal) {
super.setTraversal(parentTraversal);
this.getLocalChildren().forEach(this::integrateChild);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clarify why setTraversal needs to call integrateChild but the constructor which calls super(traversal) does not need to call integrateChild?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants