public void onMatch(RelOptRuleCall call) {
    final HiveTableFunctionScan tfs = call.rel(0);
    RelNode inputRel = tfs.getInput(0);
    RexCall lateralCall = (RexCall) tfs.getCall();
    RexCall inlineCall = (RexCall) lateralCall.getOperands().get(0);
    RexCall arrayCall = (RexCall) inlineCall.getOperands().get(0);
    RelOptCluster cluster = tfs.getCluster();

    List<RexNode> inputRefs = Lists.transform(inputRel.getRowType().getFieldList(),
        input -> new RexInputRef(input.getIndex(), input.getType()));
    List<RexNode> newStructExprs = new ArrayList<>();

    for (RexNode currentStructOperand : arrayCall.getOperands()) {
      List<RexNode> allOperands = new ArrayList<>(inputRefs);
      RexCall structCall = (RexCall) currentStructOperand;
      allOperands.addAll(structCall.getOperands());
      newStructExprs.add(cluster.getRexBuilder().makeCall(structCall.op, allOperands));
    }

    List<RelDataType> returnTypes = new ArrayList<>(
        Lists.transform(inputRel.getRowType().getFieldList(), RelDataTypeField::getType));
    RexCall firstStructCall = (RexCall) arrayCall.getOperands().get(0);
    returnTypes.addAll(Lists.transform(firstStructCall.getOperands(), RexNode::getType));

    List<RexNode> newArrayCall = Collections.singletonList(
        cluster.getRexBuilder().makeCall(arrayCall.op, newStructExprs));
    RexNode newInlineCall =
        cluster.getRexBuilder().makeCall(tfs.getRowType(), inlineCall.op, newArrayCall);

    // Use empty listfor columnMappings. The return row type of the RelNode now comprises of
    // all the fields within the UDTF, so there is no mapping from the output fields
    // directly to the input fields anymore.
    final RelNode newTableFunctionScanNode = tfs.copy(tfs.getTraitSet(),
        tfs.getInputs(), newInlineCall, tfs.getElementType(), tfs.getRowType(),
        Collections.emptySet());

    call.transformTo(newTableFunctionScanNode);
  }
This rule optimizes the inline udtf in a HiveTableFunctionScan when it
has an array of structures. The RelNode for a HiveTableFunctionScan places
the input references as the first elements in the return type followed by
the udtf return value which represents the items in the generated table. Take the
case where the base (input) table has col1, and the inline function is represented by:
inline(array( struct1(col2, col3), struct2(col2, col3), struct3(col2, col3), etc...)),
...and the return value for the table scan node is (col1, col2, col3). In this case,
the same col1 value is joined with the structures within the inline array for the
col2 and col3 values.

The optimization is to put the "col1" value within the inline array, resulting in
in the new structure:
inline(array(struct1(col1, col2, col3), struct2(col1, col2, col3), ...)
By doing this, we avoid creating a lateral view join operator and a lateral view forward
operator at runtime.
ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/rules/HiveOptimizeInlineArrayTableFunctionRule.java