Source:
public void onMatch(RelOptRuleCall call) { LogicalProject project = call.rel(0); LogicalTableScan scan = call.rel(1); int[] usedFields = RexNodeExtractor.extractRefInputFields(project.getProjects()); if (scan.getRowType().getFieldCount() == usedFields.length) { return; } TableSourceTable oldTableSourceTable = scan.getTable().unwrap(TableSourceTable.class); DynamicTableSource newTableSource = oldTableSourceTable.tableSource().copy(); SupportsProjectionPushDown newProjectPushDownSource = (SupportsProjectionPushDown) newTableSource; int[][] projectedFields = new int[usedFields.length][]; List<String> fieldNames = new ArrayList<>(); for (int i = 0; i < usedFields.length; ++i) { int usedField = usedFields[i]; projectedFields[i] = new int[] { usedField }; fieldNames.add(scan.getRowType().getFieldNames().get(usedField)); } newProjectPushDownSource.applyProjection(projectedFields); FlinkTypeFactory flinkTypeFactory = (FlinkTypeFactory) oldTableSourceTable.getRelOptSchema().getTypeFactory(); RelDataType newRowType = flinkTypeFactory.projectStructType(oldTableSourceTable.getRowType(), usedFields); TableSourceTable newTableSourceTable = oldTableSourceTable.copy( newTableSource, newRowType, new String[] { ("project=[" + String.join(", ", fieldNames) + "]") }); LogicalTableScan newScan = new LogicalTableScan( scan.getCluster(), scan.getTraitSet(), scan.getHints(), newTableSourceTable); List<RexNode> newProjects = RexNodeRewriter.rewriteWithNewFieldInput(project.getProjects(), usedFields); LogicalProject newProject = project.copy( project.getTraitSet(), newScan, newProjects, project.getRowType()); if (ProjectRemoveRule.isTrivial(newProject)) { call.transformTo(newScan); } else { call.transformTo(newProject); } }

Target:
public void onMatch(RelOptRuleCall call) { LogicalProject project = call.rel(0); LogicalTableScan scan = call.rel(1); int[] usedFields = RexNodeExtractor.extractRefInputFields(project.getProjects()); if (scan.getRowType().getFieldCount() == usedFields.length) { return; } TableSourceTable oldTableSourceTable = scan.getTable().unwrap(TableSourceTable.class); DynamicTableSource newTableSource = oldTableSourceTable.tableSource().copy(); int[][] projectedFields = new int[usedFields.length][]; List<String> fieldNames = new ArrayList<>(); for (int i = 0; i < usedFields.length; ++i) { int usedField = usedFields[i]; projectedFields[i] = new int[] { usedField }; fieldNames.add(scan.getRowType().getFieldNames().get(usedField)); } ((SupportsProjectionPushDown) newTableSource).applyProjection(projectedFields); FlinkTypeFactory flinkTypeFactory = (FlinkTypeFactory) oldTableSourceTable.getRelOptSchema().getTypeFactory(); RelDataType newRowType = flinkTypeFactory.projectStructType(oldTableSourceTable.getRowType(), usedFields); TableSourceTable newTableSourceTable = oldTableSourceTable.copy( newTableSource, newRowType, new String[] { ("project=[" + String.join(", ", fieldNames) + "]") }); LogicalTableScan newScan = new LogicalTableScan( scan.getCluster(), scan.getTraitSet(), scan.getHints(), newTableSourceTable); List<RexNode> newProjects = RexNodeRewriter.rewriteWithNewFieldInput(project.getProjects(), usedFields); LogicalProject newProject = project.copy( project.getTraitSet(), newScan, newProjects, project.getRowType()); if (ProjectRemoveRule.isTrivial(newProject)) { call.transformTo(newScan); } else { call.transformTo(newProject); } }