Skip to content

core: Rel.Remap validates none of its indices, so a malformed emit mapping fails as a bare index exception deep in a consumer #1212

Description

@nielspardon

Rel.Remap holds nothing but its index list and validates none of it.

/**
* Reorders and/or selects a relation's output fields by index, producing the emitted record type.
*/
@Value.Immutable
abstract class Remap {
/**
* Returns the output field indices, in emission order.
*
* @return the field indices
*/
public abstract List<Integer> indices();
/**
* Applies this remap to the given record type, selecting and reordering fields by index.
*
* @param initial the record type to remap
* @return the remapped record type
*/
public Type.Struct remap(Type.Struct initial) {
List<Type> types = initial.fields();
return TypeCreator.of(initial.nullable()).struct(indices().stream().map(i -> types.get(i)));
}
/**
* Creates a remap that emits the given field indices in order.
*
* @param fields the field indices to emit
* @return the remap
*/
public static Remap of(Iterable<Integer> fields) {
return ImmutableRemap.builder().addAllIndices(fields).build();
}

ProtoRelConverter copies common.emit.output_mapping through verbatim, so whatever a producer wrote reaches the POJO, and the first thing to fail is whoever indexes with it.

An out-of-range index reaches remap(Type.Struct)'s types.get(i), or isthmus's applyRemap:

private RelNode applyRemap(RelNode relNode, Rel.Remap remap) {
RelDataType rowType = relNode.getRowType();
// By index rather than by name: a virtual table's row type comes straight from its schema,
// which Calcite never uniquifies, so a name can stand for more than one field.
List<RexNode> rexList =
remap.indices().stream()
.map(index -> new RexInputRef(index, rowType.getFieldList().get(index).getType()))
.collect(java.util.stream.Collectors.toList());
return relBuilder.push(relNode).project(rexList).build();
}

For a two-column relation and emit = [5] that is java.lang.IndexOutOfBoundsException: Index 5 out of bounds for length 2, naming neither the relation nor the mapping. An empty index list builds too, and yields a node with no columns that no Substrait relation describes.

remap(Type.Struct) is where the arity is known, so a range check belongs there, naming the index and the arity as an IllegalArgumentException. Whether a repeated index is legal is a spec question rather than one to settle here: it currently converts, and isthmus silently invents a name for the second copy (Remap.of(List.of(0, 0)) over [col1, col2] comes back as [col1, col10] through Calcite's uniquifier).

Found while reviewing #1189.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions