public static Optional<ReaderColumns> projectColumns(List<IcebergColumnHandle> columns) {
    requireNonNull(columns, "columns is null");

    // No projection is required if all columns are base columns
    if (columns.stream().allMatch(IcebergColumnHandle::isBaseColumn)) {
      return Optional.empty();
    }

    ImmutableList.Builder<ColumnHandle> projectedColumns = ImmutableList.builder();
    ImmutableList.Builder<Integer> outputColumnMapping = ImmutableList.builder();
    Map<Integer, Integer> mappedFieldIds = new HashMap<>();
    int projectedColumnCount = 0;

    for (IcebergColumnHandle column : columns) {
      int baseColumnId = column.getBaseColumnIdentity().getId();
      Integer mapped = mappedFieldIds.get(baseColumnId);

      if (mapped == null) {
        projectedColumns.add(column.getBaseColumn());
        mappedFieldIds.put(baseColumnId, projectedColumnCount);
        outputColumnMapping.add(projectedColumnCount);
        projectedColumnCount++;
      } else {
        outputColumnMapping.add(mapped);
      }
    }

    return Optional.of(new ReaderColumns(projectedColumns.build(), outputColumnMapping.build()));
  }
Creates a mapping between the input {@code columns} and base columns if required.
amoro-format-mixed/amoro-mixed-trino/src/main/java/org/apache/amoro/trino/unkeyed/IcebergPageSourceProvider.java