Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add POJOs for ConsistentPartitionWindowRel and WindowRelFunction #231

Merged
merged 4 commits into from
Feb 16, 2024

Conversation

bvolpato
Copy link
Member

Adding the POJO for ConsistentPartitionWindowRel and WindowRelFunction: https://github.com/substrait-io/substrait/blob/main/proto/substrait/algebra.proto#L233-L267

I wasn't sure about the organization/place of some parts since it's not physically under Expression, but there's a lot in common, even highlighted in the proto:

// This message mirrors the WindowFunction message but removes the fields defining the partition,
// sorts, and bounds, since those must be consistent across the various functions in this rel. Refer
// to the WindowFunction message for a description of these fields.

So I tried to keep it as a new base class, but tried to keep the converters as close as they were to make it more concise + simplifying reuse.

--

I'll have an Isthmus change following (this is essentially a cleanup / split of #228), and @vbarua rightfully suggested breaking it down.

t -> t.accept(getExpressionCopyOnWriteVisitor()));
var sorts = transformList(consistentPartitionWindow.getSorts(), this::visitSortField);

return Optional.of(
Copy link
Member

Choose a reason for hiding this comment

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

To follow the pattern of the other copy on write visitors, this should return empty unless any of the Optionals windowFunctions, partitionExpressions and sorts is non-empty.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch

Copy link
Member

@vbarua vbarua left a comment

Choose a reason for hiding this comment

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

Missing a couple field conversions, but overall this looks good.

I wasn't sure about the organization/place of some parts since it's not physically under Expression, but there's a lot in common, even highlighted in the proto:

I can't a think of a alternate organization at this point either. Grouping the WindowRelFunctionInvocation conversion code alongside the equivalent WindowFunctionInvocation conversion code makes sense to me, and at least keeps it together. There might be some nicer abstraction we could use to unify these somehow, but I think that would be a bigger refactor and should be left for another time.

ConsistentPartitionWindow.WindowRelFunctionInvocation.builder()
.arguments(
visitFunctionArguments(windowRelFunctionInvocation.arguments())
.orElse(windowRelFunctionInvocation.arguments()))
Copy link
Member

Choose a reason for hiding this comment

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

This should return empty if visitFunctionArguments returns an empty optional to keep the copy on write behaviour.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done, thanks!

core/src/main/java/io/substrait/relation/RelVisitor.java Outdated Show resolved Hide resolved
@@ -250,6 +219,80 @@ public Expression from(io.substrait.proto.Expression expr) {
};
}

public ImmutableExpression.WindowFunctionInvocation fromWindowFunction(
Copy link
Member

Choose a reason for hiding this comment

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

minor: We should return Expression.WindowFunctionInvocation here. We generally avoid using the Immutable generate classes in signatures.

Good call extracting this out. It's much easier to look at alongside fromWindowRelFunction like this.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks! Overlook when using extract method on IntelliJ

.collect(Collectors.toList());
}

public ImmutableExpression.SortField fromSortField(SortField s) {
Copy link
Member

Choose a reason for hiding this comment

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

minor: This should return Expression.SortField

Copy link
Member Author

Choose a reason for hiding this comment

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

ditto

}

@Value.Immutable
public abstract static class WindowRelFunctionInvocation {
Copy link
Member

Choose a reason for hiding this comment

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

Not for this PR. It might make sense in the future to define an interface of common types that we can share between WindowRelFunctionInvocation and WindowFunctionInvocation.

Constructing a WindowRelFunctionInvocation from that interface would be straightforward (as it would have all the fields), and then construction a WindowRelFunctionInvocation would just required providing the partitions and sorts.

We might also be able to have WindowFunctionInvocation extend from WindowRelFunctionInvocation, but I vaguely recall trying something like that in the past and the Immutable library not liking it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah I agree -- it's subtly different and can be annoying to have concise builders and everything.

@bvolpato
Copy link
Member Author

Thanks for the great review @vbarua 💯.

All good points! Pushed 1bb9836 addressing them.

@bvolpato bvolpato requested a review from vbarua February 15, 2024 20:38
@@ -377,14 +377,8 @@ protected Optional<ConsistentPartitionWindow.WindowRelFunctionInvocation> visitW

return Optional.of(
ConsistentPartitionWindow.WindowRelFunctionInvocation.builder()
.from(windowRelFunctionInvocation)
Copy link
Member

Choose a reason for hiding this comment

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

Using from here both:

  • saves use some lines of code
  • prevents issues if we ever add fields to the ConsistentPartitionWindow (as we would then need to make sure we updated this code to manually copy over those fields)

import java.util.Collections;
import org.junit.jupiter.api.Test;

public class WindowFunctionRoundtripTest extends TestBase {
Copy link
Member

Choose a reason for hiding this comment

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

Renamed to ConsistentPartitionWindowRelRoundtripTest as we're testing the roundtripability of the rel as a whole.

var windowFunctionDeclaration =
defaultExtensionCollection.getWindowFunction(
SimpleExtension.FunctionAnchor.of(
DefaultExtensionCatalog.FUNCTIONS_ARITHMETIC, "lead:any"));
Copy link
Member

Choose a reason for hiding this comment

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

Choose a specific function (in this case lead:any) instead of a random one from defaultExtensionCollection.windowFunctions().get(0). There's no guarantee that a function chosen at random will be valid with the arguments that we choose to pass.

b.namedScan(
Arrays.asList("test"),
Arrays.asList("a", "b", "c"),
Arrays.asList(R.I64, R.I16, R.I32));
Copy link
Member

Choose a reason for hiding this comment

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

Using the builder to setup the input as it's more readable (IMO)

ConsistentPartitionWindow.WindowRelFunctionInvocation.builder()
.declaration(windowFunctionDeclaration)
// lead(a)
.arguments(Arrays.asList(b.fieldReference(input, 0)))
Copy link
Member

Choose a reason for hiding this comment

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

Specify at least one argument to make sure argument conversions are wired in.


io.substrait.proto.Rel protoRel = relProtoConverter.toProto(rel1);
io.substrait.relation.Rel rel2 = protoRelConverter.from(protoRel);
assertEquals(rel1, rel2);
Copy link
Member

Choose a reason for hiding this comment

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

Use the relProtoConverter and protoRelConverter defined in TestBase to avoid having to set them up ourselves.

Copy link
Member

@vbarua vbarua left a comment

Choose a reason for hiding this comment

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

Changes look good.

Made a small refactor in 05476ed and simplified the test in e595bf9, see my PR comments for my reasoning.

@vbarua vbarua merged commit f148bbb into substrait-io:main Feb 16, 2024
8 checks passed
ajegou pushed a commit to ajegou/substrait-java that referenced this pull request Mar 29, 2024
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.

2 participants