public static List<Pair<Integer, ValueReader<?>>> buildReadPlan(
      Types.StructType expected,
      Schema record,
      List<ValueReader<?>> fieldReaders,
      Map<Integer, ?> idToConstant,
      BiFunction<Type, Object, Object> convert) {
    Map<Integer, Integer> idToPos = idToPos(expected);

    List<Pair<Integer, ValueReader<?>>> readPlan = Lists.newArrayList();
    List<Schema.Field> fileFields = record.getFields();
    for (int pos = 0; pos < fileFields.size(); pos += 1) {
      Schema.Field field = fileFields.get(pos);
      ValueReader<?> fieldReader = fieldReaders.get(pos);
      Integer fieldId = AvroSchemaUtil.fieldId(field);
      Integer projectionPos = idToPos.remove(fieldId);

      Object constant = idToConstant.get(fieldId);
      if (projectionPos != null && constant != null) {
        readPlan.add(
            Pair.of(projectionPos, ValueReaders.replaceWithConstant(fieldReader, constant)));
      } else {
        readPlan.add(Pair.of(projectionPos, fieldReader));
      }
    }

    // handle any expected columns that are not in the data file
    for (Map.Entry<Integer, Integer> idAndPos : idToPos.entrySet()) {
      int fieldId = idAndPos.getKey();
      int pos = idAndPos.getValue();

      Object constant = idToConstant.get(fieldId);
      Types.NestedField field = expected.field(fieldId);
      if (constant != null) {
        readPlan.add(Pair.of(pos, ValueReaders.constant(constant)));
      } else if (fieldId == MetadataColumns.IS_DELETED.fieldId()) {
        readPlan.add(Pair.of(pos, ValueReaders.constant(false)));
      } else if (fieldId == MetadataColumns.ROW_POSITION.fieldId()) {
        readPlan.add(Pair.of(pos, ValueReaders.positions()));
      } else if (field.isOptional()) {
        readPlan.add(Pair.of(pos, ValueReaders.constant(null)));
      } else {
        throw new IllegalArgumentException(
            String.format("Missing required field: %s", field.name()));
      }
    }

    return readPlan;
  }
Builds a read plan for record classes that use planned reads instead of a ResolvingDecoder.

@param expected expected StructType
@param record Avro record schema
@param fieldReaders list of readers for each field in the Avro record schema
@param idToConstant a map of field ID to constants values
@param convert function to convert from internal classes to the target object model
@return a read plan that is a list of (position, reader) pairs
iceberg/patched-iceberg-core/src/main/java/org/apache/iceberg/avro/ValueReaders.java