From 6b12f87c7b903c058228ce675f8dbb7525daeead Mon Sep 17 00:00:00 2001 From: Seongmin Kim Date: Thu, 26 Mar 2026 19:09:19 +0900 Subject: [PATCH 01/17] feat(core): add MaskExpression POJO and projection support for ReadRel --- .../substrait/expression/MaskExpression.java | 230 ++++++++++++++++ .../proto/MaskExpressionProtoConverter.java | 205 +++++++++++++++ .../substrait/relation/AbstractReadRel.java | 4 +- .../substrait/relation/ProtoRelConverter.java | 17 +- .../substrait/relation/RelProtoConverter.java | 6 + .../type/proto/ReadRelRoundtripTest.java | 245 ++++++++++++++++++ 6 files changed, 702 insertions(+), 5 deletions(-) create mode 100644 core/src/main/java/io/substrait/expression/MaskExpression.java create mode 100644 core/src/main/java/io/substrait/expression/proto/MaskExpressionProtoConverter.java diff --git a/core/src/main/java/io/substrait/expression/MaskExpression.java b/core/src/main/java/io/substrait/expression/MaskExpression.java new file mode 100644 index 000000000..18dab8b5f --- /dev/null +++ b/core/src/main/java/io/substrait/expression/MaskExpression.java @@ -0,0 +1,230 @@ +package io.substrait.expression; + +import java.util.List; +import java.util.Optional; +import org.immutables.value.Value; + +/** + * A mask expression that selectively removes fields from complex types (struct, list, map). + * + *

This corresponds to the {@code Expression.MaskExpression} message in the Substrait protobuf + * specification. It is used in {@code ReadRel} to describe column projection — the subset of a + * relation's schema that should actually be read. + * + * @see Substrait Field References + */ +@Value.Enclosing +public interface MaskExpression { + + // --------------------------------------------------------------------------- + // Top-level MaskExpression value + // --------------------------------------------------------------------------- + + /** The concrete mask expression value holding the top-level struct selection and options. */ + @Value.Immutable + interface MaskExpr { + /** The top-level struct selection describing which fields to include. */ + StructSelect getSelect(); + + /** + * When {@code true}, a struct that has only a single selected field will not be + * unwrapped into its child type. + */ + @Value.Default + default boolean getMaintainSingularStruct() { + return false; + } + + static ImmutableMaskExpression.MaskExpr.Builder builder() { + return ImmutableMaskExpression.MaskExpr.builder(); + } + } + + // --------------------------------------------------------------------------- + // Select – a union of StructSelect | ListSelect | MapSelect + // --------------------------------------------------------------------------- + + /** A selection on a complex type – exactly one of struct, list, or map must be set. */ + @Value.Immutable + interface Select { + Optional getStruct(); + + Optional getList(); + + Optional getMap(); + + static ImmutableMaskExpression.Select.Builder builder() { + return ImmutableMaskExpression.Select.builder(); + } + + static Select ofStruct(StructSelect structSelect) { + return builder().struct(structSelect).build(); + } + + static Select ofList(ListSelect listSelect) { + return builder().list(listSelect).build(); + } + + static Select ofMap(MapSelect mapSelect) { + return builder().map(mapSelect).build(); + } + } + + // --------------------------------------------------------------------------- + // Struct selection + // --------------------------------------------------------------------------- + + /** Selects a subset of fields from a struct type. */ + @Value.Immutable + interface StructSelect { + List getStructItems(); + + static ImmutableMaskExpression.StructSelect.Builder builder() { + return ImmutableMaskExpression.StructSelect.builder(); + } + } + + /** Selects a single field from a struct, with an optional nested child selection. */ + @Value.Immutable + interface StructItem { + /** Zero-based field index within the struct. */ + int getField(); + + /** Optional child selection for nested complex types. */ + Optional getChild(); + + static ImmutableMaskExpression.ListSelect.Builder builder() { + return ImmutableMaskExpression.ListSelect.builder(); + } + } + + /** A single selection within a list – either an element or a slice. */ + @Value.Immutable + interface ListSelectItem { + Optional getItem(); + + Optional getSlice(); + + static ImmutableMaskExpression.ListSelectItem.Builder builder() { + return ImmutableMaskExpression.ListSelectItem.builder(); + } + + static ListSelectItem ofItem(ListElement element) { + return builder().item(element).build(); + } + + static ListSelectItem ofSlice(ListSlice slice) { + return builder().slice(slice).build(); + } + } + + /** Selects a single element from a list by zero-based index. */ + @Value.Immutable + interface ListElement { + int getField(); + + static ImmutableMaskExpression.ListElement.Builder builder() { + return ImmutableMaskExpression.ListElement.builder(); + } + + static ListElement of(int field) { + return builder().field(field).build(); + } + } + + /** Selects a contiguous range of elements from a list. */ + @Value.Immutable + interface ListSlice { + int getStart(); + + int getEnd(); + + static ImmutableMaskExpression.ListSlice.Builder builder() { + return ImmutableMaskExpression.ListSlice.builder(); + } + + static ListSlice of(int start, int end) { + return builder().start(start).end(end).build(); + } + } + + // --------------------------------------------------------------------------- + // Map selection + // --------------------------------------------------------------------------- + + /** Selects entries from a map type by exact key or key expression. */ + @Value.Immutable + interface MapSelect { + Optional getKey(); + + Optional getExpression(); + + /** Optional child selection applied to each selected map value. */ + Optional getChild(); + /** + * Creates a new builder for constructing a StructItem. + * + * @return a new builder instance + */ static ImmutableMaskExpression.StructItem.Builder builder() { return ImmutableMaskExpression.StructItem.builder(); } + /** + * Creates a StructItem for a single field with no nested selection. + * + * @param field the zero-based field index within the struct + * @return a new StructItem instance + */ static StructItem of(int field) { return builder().field(field).build(); } + /** + * Creates a StructItem for a single field with an optional nested selection. + * + * @param field the zero-based field index within the struct + * @param child the nested child selection for complex types + * @return a new StructItem instance + */ static StructItem of(int field, Select child) { return builder().field(field).child(child).build(); } @@ -100,11 +144,21 @@ static StructItem of(int field, Select child) { /** Selects elements from a list type by index or slice. */ @Value.Immutable interface ListSelect extends Select { + /** + * Returns the list of selection items (individual elements or slices). + * + * @return the list of selection items + */ List getSelection(); /** Optional child selection applied to each selected element. */ Optional getChild(); + /** + * Creates a new builder for constructing a MapSelect. + * + * @return a new builder instance + */ static ImmutableMaskExpression.MapSelect.Builder builder() { return ImmutableMaskExpression.MapSelect.builder(); } + /** + * Creates a MapSelect for a single key selection. + * + * @param key the exact key to select + * @return a new MapSelect instance + */ static MapSelect ofKey(MapKey key) { return builder().key(key).build(); } + /** + * Creates a MapSelect for a wildcard key expression selection. + * + * @param expression the key expression to select + * @return a new MapSelect instance + */ static MapSelect ofExpression(MapKeyExpression expression) { return builder().expression(expression).build(); } @@ -202,12 +348,28 @@ default R accept( /** Selects a map entry by an exact key match. */ @Value.Immutable interface MapKey { + /** + * Returns the map key string for exact matching. + * + * @return the map key + */ String getMapKey(); + /** + * Creates a new builder for constructing a MapKey. + * + * @return a new builder instance + */ static ImmutableMaskExpression.MapKey.Builder builder() { return ImmutableMaskExpression.MapKey.builder(); } + /** + * Creates a MapKey for exact key matching. + * + * @param mapKey the key string to match + * @return a new MapKey instance + */ static MapKey of(String mapKey) { return builder().mapKey(mapKey).build(); } @@ -216,12 +378,28 @@ static MapKey of(String mapKey) { /** Selects map entries by a wildcard key expression. */ @Value.Immutable interface MapKeyExpression { + /** + * Returns the wildcard key expression string. + * + * @return the map key expression + */ String getMapKeyExpression(); + /** + * Creates a new builder for constructing a MapKeyExpression. + * + * @return a new builder instance + */ static ImmutableMaskExpression.MapKeyExpression.Builder builder() { return ImmutableMaskExpression.MapKeyExpression.builder(); } + /** + * Creates a MapKeyExpression for wildcard key matching. + * + * @param mapKeyExpression the wildcard expression string + * @return a new MapKeyExpression instance + */ static MapKeyExpression of(String mapKeyExpression) { return builder().mapKeyExpression(mapKeyExpression).build(); } From 70aa512f2b4adbb7f4b76a253031dca82f01fa66 Mon Sep 17 00:00:00 2001 From: Seongmin Kim Date: Wed, 8 Apr 2026 20:11:19 +0900 Subject: [PATCH 12/17] docs(core): rewrite javadoc --- .../substrait/expression/MaskExpression.java | 24 +++++++++++++++---- .../MaskExpressionTypeProjector.java | 7 ++++++ .../proto/MaskExpressionProtoConverter.java | 7 +++++- .../proto/ProtoMaskExpressionConverter.java | 7 +++++- 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/io/substrait/expression/MaskExpression.java b/core/src/main/java/io/substrait/expression/MaskExpression.java index d0ed689f1..a11b4ab63 100644 --- a/core/src/main/java/io/substrait/expression/MaskExpression.java +++ b/core/src/main/java/io/substrait/expression/MaskExpression.java @@ -100,10 +100,18 @@ default R accept( /** Selects a single field from a struct, with an optional nested child selection. */ @Value.Immutable interface StructItem { - /** Zero-based field index within the struct. */ + /** + * Returns the zero-based field index within the struct. + * + * @return the field index + */ int getField(); - /** Optional child selection for nested complex types. */ + /** + * Returns the optional child selection for nested complex types. + * + * @return the optional child selection + */ Optional getChild(); /** @@ -306,7 +318,11 @@ interface MapSelect extends Select { */ Optional getExpression(); - /** Optional child selection applied to each selected map value. */ + /** + * Returns the optional child selection applied to each selected map value. + * + * @return the optional child selection + */ Optional