minihippo commented on code in PR #5629:
URL: https://github.com/apache/hudi/pull/5629#discussion_r914608155


##########
hudi-common/src/main/java/org/apache/hudi/common/table/log/block/HoodieParquetDataBlock.java:
##########
@@ -93,53 +89,41 @@ protected byte[] serializeRecords(List<HoodieRecord> 
records) throws IOException
     }
 
     Schema writerSchema = new 
Schema.Parser().parse(super.getLogBlockHeader().get(HeaderMetadataType.SCHEMA));
-
-    HoodieAvroWriteSupport writeSupport = new HoodieAvroWriteSupport(
-        new AvroSchemaConverter().convert(writerSchema), writerSchema, 
Option.empty());
-
-    HoodieParquetConfig<HoodieAvroWriteSupport> avroParquetConfig =
-        new HoodieParquetConfig<>(
-            writeSupport,
-            compressionCodecName.get(),
-            ParquetWriter.DEFAULT_BLOCK_SIZE,
-            ParquetWriter.DEFAULT_PAGE_SIZE,
-            1024 * 1024 * 1024,
-            new Configuration(),
-            
Double.parseDouble(String.valueOf(0.1)));//HoodieStorageConfig.PARQUET_COMPRESSION_RATIO.defaultValue()));
-
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
-
     try (FSDataOutputStream outputStream = new FSDataOutputStream(baos)) {
-      try (HoodieParquetStreamWriter parquetWriter = new 
HoodieParquetStreamWriter(outputStream, avroParquetConfig)) {
-        for (HoodieRecord record : records) {
+      HoodieFileWriter parquetWriter = null;
+      HoodieStorageConfig storageConfig =  
HoodieStorageConfig.newBuilder().build();

Review Comment:
   A better way to do it



##########
hudi-client/hudi-spark-client/src/main/java/org/apache/hudi/io/storage/HoodieSparkParquetReader.java:
##########
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hudi.io.storage;
+
+import org.apache.avro.Schema;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.Path;
+import org.apache.hudi.HoodieInternalRowUtils;
+import org.apache.hudi.common.bloom.BloomFilter;
+import org.apache.hudi.common.model.HoodieFileFormat;
+import org.apache.hudi.common.util.BaseFileUtils;
+import org.apache.hudi.common.util.ClosableIterator;
+import org.apache.hudi.common.util.ParquetReaderIterator;
+import org.apache.parquet.hadoop.ParquetReader;
+import org.apache.parquet.hadoop.api.ReadSupport;
+import org.apache.parquet.hadoop.util.HadoopInputFile;
+import org.apache.parquet.io.InputFile;
+import org.apache.spark.sql.catalyst.InternalRow;
+import org.apache.spark.sql.execution.datasources.parquet.ParquetReadSupport;
+import org.apache.spark.sql.internal.SQLConf;
+import org.apache.spark.sql.types.StructType;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class HoodieSparkParquetReader implements HoodieSparkFileReader {
+
+  private final Path path;
+  private final Configuration conf;
+  private final BaseFileUtils parquetUtils;
+  private List<ParquetReaderIterator> readerIterators = new ArrayList<>();
+
+  public HoodieSparkParquetReader(Configuration conf, Path path) {
+    this.path = path;
+    this.conf = conf;
+    this.parquetUtils = BaseFileUtils.getInstance(HoodieFileFormat.PARQUET);
+  }
+
+  @Override
+  public String[] readMinMaxRecordKeys() {
+    return parquetUtils.readMinMaxRecordKeys(conf, path);
+  }
+
+  @Override
+  public BloomFilter readBloomFilter() {
+    return parquetUtils.readBloomFilterFromMetadata(conf, path);
+  }
+
+  @Override
+  public Set<String> filterRowKeys(Set<String> candidateRowKeys) {
+    return parquetUtils.filterRowKeys(conf, path, candidateRowKeys);
+  }
+
+  @Override
+  public ClosableIterator<InternalRow> getInternalRowIterator(Schema schema) 
throws IOException {
+    StructType structType = HoodieInternalRowUtils.getCachedSchema(schema);
+    conf.set(ParquetReadSupport.SPARK_ROW_REQUESTED_SCHEMA(), 
structType.json());
+    // todo: get it from spark context
+    conf.setBoolean(SQLConf.PARQUET_BINARY_AS_STRING().key(),false);
+    conf.setBoolean(SQLConf.PARQUET_INT96_AS_TIMESTAMP().key(), true);
+    InputFile inputFile = HadoopInputFile.fromPath(path, conf);
+    ParquetReader reader = new ParquetReader.Builder<InternalRow>(inputFile) {
+      @Override
+      protected ReadSupport getReadSupport() {
+        return new ParquetReadSupport();
+      }
+    }.withConf(conf).build();
+    ParquetReaderIterator<InternalRow> parquetReaderIterator = new 
ParquetReaderIterator<>(reader, InternalRow::copy);

Review Comment:
   `InternalRow::copy` any better way to solve the problem



##########
hudi-client/hudi-spark-client/src/main/java/org/apache/hudi/commmon/model/HoodieSparkRecord.java:
##########
@@ -0,0 +1,270 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hudi.commmon.model;
+
+import org.apache.hudi.HoodieInternalRowUtils;
+import org.apache.hudi.common.config.TypedProperties;
+import org.apache.hudi.common.model.HoodieKey;
+import org.apache.hudi.common.model.HoodieOperation;
+import org.apache.hudi.common.model.HoodieRecord;
+import org.apache.hudi.common.util.Option;
+import org.apache.hudi.common.util.collection.Pair;
+import org.apache.hudi.exception.HoodieException;
+import org.apache.hudi.keygen.BaseKeyGenerator;
+import org.apache.hudi.keygen.SparkKeyGeneratorInterface;
+import org.apache.hudi.keygen.factory.HoodieSparkKeyGeneratorFactory;
+import org.apache.hudi.util.HoodieSparkRecordUtils;
+
+import org.apache.avro.Schema;
+import org.apache.avro.generic.IndexedRecord;
+import org.apache.spark.sql.catalyst.CatalystTypeConverters;
+import org.apache.spark.sql.catalyst.InternalRow;
+import org.apache.spark.sql.types.DataType;
+import org.apache.spark.sql.types.StructField;
+import org.apache.spark.sql.types.StructType;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import scala.Tuple2;
+
+import static 
org.apache.hudi.common.table.HoodieTableConfig.POPULATE_META_FIELDS;
+import static org.apache.spark.sql.types.DataTypes.BooleanType;
+import static org.apache.spark.sql.types.DataTypes.StringType;
+
+/**
+ * Spark Engine-specific Implementations of `HoodieRecord`.
+ */
+public class HoodieSparkRecord extends HoodieRecord<InternalRow> {
+
+  // IndexedRecord hold its schema, InternalRow should also hold its schema
+  private final StructType structType;
+
+  public HoodieSparkRecord(InternalRow data, StructType schema) {
+    super(null, data);
+    this.structType = schema;
+  }
+
+  public HoodieSparkRecord(InternalRow data, StructType schema, Comparable 
orderingVal) {
+    super(null, data, orderingVal);
+    this.structType = schema;
+  }
+
+  public HoodieSparkRecord(HoodieKey key, InternalRow data, StructType schema) 
{
+    super(key, data);
+    this.structType = schema;
+  }
+
+  public HoodieSparkRecord(HoodieKey key, InternalRow data, StructType schema, 
Comparable orderingVal) {
+    super(key, data, orderingVal);
+    this.structType = schema;
+  }
+
+  public HoodieSparkRecord(HoodieKey key, InternalRow data, StructType schema, 
HoodieOperation operation, Comparable orderingVal) {
+    super(key, data, operation, orderingVal);
+    this.structType = schema;
+  }
+
+  public HoodieSparkRecord(HoodieSparkRecord record) {
+    super(record);
+    this.structType = record.structType;
+  }
+
+  @Override
+  public HoodieRecord<InternalRow> newInstance() {
+    return new HoodieSparkRecord(this);
+  }
+
+  @Override
+  public HoodieRecord<InternalRow> newInstance(HoodieKey key, HoodieOperation 
op) {
+    return new HoodieSparkRecord(key, data, structType, op, 
getOrderingValue());
+  }
+
+  @Override
+  public HoodieRecord<InternalRow> newInstance(HoodieKey key) {
+    return new HoodieSparkRecord(key, data, structType, getOrderingValue());
+  }
+
+  @Override
+  public void deflate() {
+  }
+
+  @Override
+  public String getRecordKey(Option<BaseKeyGenerator> keyGeneratorOpt) {
+    if (key != null) {
+      return getRecordKey();
+    }
+    return keyGeneratorOpt.isPresent() ? ((SparkKeyGeneratorInterface) 
keyGeneratorOpt.get()).getRecordKey(data, structType) : 
data.getString(HoodieMetadataField.RECORD_KEY_METADATA_FIELD.ordinal());
+  }
+
+  @Override
+  public String getRecordKey(String keyFieldName) {
+    if (key != null) {
+      return getRecordKey();
+    }
+    Tuple2<StructField, Object> tuple2 = 
HoodieInternalRowUtils.getCachedSchemaPosMap(structType).get(keyFieldName).get();
+    DataType dataType = tuple2._1.dataType();
+    int pos = (Integer) tuple2._2;
+    return data.get(pos, dataType).toString();
+  }
+
+  @Override
+  public HoodieRecordType getRecordType() {
+    return HoodieRecordType.SPARK;
+  }
+
+  @Override
+  public Object getRecordColumnValues(String[] columns, Schema schema, boolean 
consistentLogicalTimestampEnabled) {
+    return HoodieSparkRecordUtils.getRecordColumnValues(this, columns, 
structType, consistentLogicalTimestampEnabled);
+  }
+
+  @Override
+  public HoodieRecord mergeWith(Schema schema, HoodieRecord other, Schema 
otherSchema, Schema writerSchema) throws IOException {
+    StructType otherStructType = 
HoodieInternalRowUtils.getCachedSchema(otherSchema);
+    StructType writerStructType = 
HoodieInternalRowUtils.getCachedSchema(writerSchema);
+    InternalRow mergeRow = HoodieInternalRowUtils.stitchRecords(data, 
structType, (InternalRow) other.getData(), otherStructType, writerStructType);
+    return new HoodieSparkRecord(getKey(), mergeRow, writerStructType, 
getOperation());
+  }
+
+  @Override
+  public HoodieRecord rewriteRecord(Schema recordSchema, Schema targetSchema, 
TypedProperties props) throws IOException {
+    StructType targetStructType = 
HoodieInternalRowUtils.getCachedSchema(targetSchema);
+    InternalRow rewriteRow = HoodieInternalRowUtils.rewriteRecord(data, 
structType, targetStructType);
+    return new HoodieSparkRecord(getKey(), rewriteRow, targetStructType, 
getOperation());
+  }
+
+  @Override
+  public HoodieRecord rewriteRecord(Schema recordSchema, Properties prop, 
boolean schemaOnReadEnabled, Schema writeSchemaWithMetaFields) throws 
IOException {
+    StructType writeSchemaWithMetaFieldsStructType = 
HoodieInternalRowUtils.getCachedSchema(writeSchemaWithMetaFields);
+    InternalRow rewriteRow = schemaOnReadEnabled ? 
HoodieInternalRowUtils.rewriteRecordWithNewSchema(data, structType, 
writeSchemaWithMetaFieldsStructType, new HashMap<>())
+        : HoodieInternalRowUtils.rewriteRecord(data, structType, 
writeSchemaWithMetaFieldsStructType);
+    return new HoodieSparkRecord(getKey(), rewriteRow, 
writeSchemaWithMetaFieldsStructType, getOperation());
+  }
+
+  @Override
+  public HoodieRecord rewriteRecordWithMetadata(Schema recordSchema, 
Properties prop, boolean schemaOnReadEnabled, Schema writeSchemaWithMetaFields, 
String fileName) throws IOException {
+    StructType writeSchemaWithMetaFieldsStructType = 
HoodieInternalRowUtils.getCachedSchema(writeSchemaWithMetaFields);
+    InternalRow rewriteRow = schemaOnReadEnabled ? 
HoodieInternalRowUtils.rewriteEvolutionRecordWithMetadata(data, structType, 
writeSchemaWithMetaFieldsStructType, fileName)
+        : HoodieInternalRowUtils.rewriteRecordWithMetadata(data, structType, 
writeSchemaWithMetaFieldsStructType, fileName);
+    return new HoodieSparkRecord(getKey(), rewriteRow, 
writeSchemaWithMetaFieldsStructType, getOperation());
+  }
+
+  @Override
+  public HoodieRecord rewriteRecordWithNewSchema(Schema recordSchema, 
Properties prop, Schema newSchema, Map<String, String> renameCols) throws 
IOException {
+    StructType newStructType = 
HoodieInternalRowUtils.getCachedSchema(newSchema);
+    InternalRow rewriteRow = 
HoodieInternalRowUtils.rewriteRecordWithNewSchema(data, structType, 
newStructType, renameCols);
+    return new HoodieSparkRecord(getKey(), rewriteRow, newStructType, 
getOperation());
+  }
+
+  @Override
+  public HoodieRecord rewriteRecordWithNewSchema(Schema recordSchema, 
Properties prop, Schema newSchema) throws IOException {
+    StructType newStructType = 
HoodieInternalRowUtils.getCachedSchema(newSchema);
+    InternalRow rewriteRow = HoodieInternalRowUtils.rewriteRecord(data, 
structType, newStructType);
+    return new HoodieSparkRecord(getKey(), rewriteRow, structType, 
getOperation());
+  }
+
+  @Override
+  public HoodieRecord overrideMetadataFieldValue(Schema recordSchema, 
Properties prop, int pos, String newValue) throws IOException {
+    data.update(pos, CatalystTypeConverters.convertToCatalyst(newValue));
+    return this;
+  }
+
+  @Override
+  public HoodieRecord addMetadataValues(Schema recordSchema, Properties prop, 
Map<HoodieMetadataField, String> metadataValues) throws IOException {
+    Arrays.stream(HoodieMetadataField.values()).forEach(metadataField -> {
+      String value = metadataValues.get(metadataField);
+      if (value != null) {
+        data.update(recordSchema.getField(metadataField.getFieldName()).pos(), 
CatalystTypeConverters.convertToCatalyst(value));
+      }
+    });
+    return this;
+  }
+
+  @Override
+  public HoodieRecord expansion(Schema schema, Properties prop, String 
payloadClass,
+      String preCombineField,
+      Option<Pair<String, String>> simpleKeyGenFieldsOpt,
+      Boolean withOperation,
+      Option<String> partitionNameOp,
+      Option<Boolean> populateMetaFieldsOp) {
+    boolean populateMetaFields = populateMetaFieldsOp.orElse(false);
+    if (populateMetaFields) {
+      return HoodieSparkRecordUtils.convertToHoodieSparkRecord(structType, 
data, preCombineField, withOperation);
+    } else if (simpleKeyGenFieldsOpt.isPresent()) {
+      return HoodieSparkRecordUtils.convertToHoodieSparkRecord(structType, 
data, preCombineField, simpleKeyGenFieldsOpt.get(), withOperation, 
Option.empty());
+    } else {
+      return HoodieSparkRecordUtils.convertToHoodieSparkRecord(structType, 
data, preCombineField, withOperation, partitionNameOp);
+    }
+  }
+
+  @Override
+  public HoodieRecord transform(Schema schema, Properties prop, boolean 
useKeygen) {
+    StructType structType = HoodieInternalRowUtils.getCachedSchema(schema);
+    Option<SparkKeyGeneratorInterface> keyGeneratorOpt = Option.empty();
+    if (useKeygen && 
!Boolean.parseBoolean(prop.getOrDefault(POPULATE_META_FIELDS.key(), 
POPULATE_META_FIELDS.defaultValue().toString()).toString())) {
+      try {
+        keyGeneratorOpt = Option.of((SparkKeyGeneratorInterface) 
HoodieSparkKeyGeneratorFactory.createKeyGenerator(new TypedProperties(prop)));
+      } catch (IOException e) {
+        throw new HoodieException("Only SparkKeyGeneratorInterface are 
supported when meta columns are disabled ", e);
+      }
+    }
+    String key = keyGeneratorOpt.isPresent() ? 
keyGeneratorOpt.get().getRecordKey(data, structType)
+        : data.get(HoodieMetadataField.RECORD_KEY_METADATA_FIELD.ordinal(), 
StringType).toString();
+    String partition = keyGeneratorOpt.isPresent() ? 
keyGeneratorOpt.get().getPartitionPath(data, structType)
+        : 
data.get(HoodieMetadataField.PARTITION_PATH_METADATA_FIELD.ordinal(), 
StringType).toString();
+    this.key = new HoodieKey(key, partition);
+
+    return this;
+  }
+
+  @Override
+  public Option<Map<String, String>> getMetadata() {
+    return Option.empty();
+  }
+
+  @Override
+  public boolean isPresent(Schema schema, Properties prop) throws IOException {

Review Comment:
   will fix



##########
hudi-client/hudi-spark-client/src/main/java/org/apache/hudi/commmon/model/HoodieSparkRecord.java:
##########
@@ -0,0 +1,270 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hudi.commmon.model;
+
+import org.apache.hudi.HoodieInternalRowUtils;
+import org.apache.hudi.common.config.TypedProperties;
+import org.apache.hudi.common.model.HoodieKey;
+import org.apache.hudi.common.model.HoodieOperation;
+import org.apache.hudi.common.model.HoodieRecord;
+import org.apache.hudi.common.util.Option;
+import org.apache.hudi.common.util.collection.Pair;
+import org.apache.hudi.exception.HoodieException;
+import org.apache.hudi.keygen.BaseKeyGenerator;
+import org.apache.hudi.keygen.SparkKeyGeneratorInterface;
+import org.apache.hudi.keygen.factory.HoodieSparkKeyGeneratorFactory;
+import org.apache.hudi.util.HoodieSparkRecordUtils;
+
+import org.apache.avro.Schema;
+import org.apache.avro.generic.IndexedRecord;
+import org.apache.spark.sql.catalyst.CatalystTypeConverters;
+import org.apache.spark.sql.catalyst.InternalRow;
+import org.apache.spark.sql.types.DataType;
+import org.apache.spark.sql.types.StructField;
+import org.apache.spark.sql.types.StructType;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import scala.Tuple2;
+
+import static 
org.apache.hudi.common.table.HoodieTableConfig.POPULATE_META_FIELDS;
+import static org.apache.spark.sql.types.DataTypes.BooleanType;
+import static org.apache.spark.sql.types.DataTypes.StringType;
+
+/**
+ * Spark Engine-specific Implementations of `HoodieRecord`.
+ */
+public class HoodieSparkRecord extends HoodieRecord<InternalRow> {
+
+  // IndexedRecord hold its schema, InternalRow should also hold its schema
+  private final StructType structType;
+
+  public HoodieSparkRecord(InternalRow data, StructType schema) {
+    super(null, data);
+    this.structType = schema;
+  }
+
+  public HoodieSparkRecord(InternalRow data, StructType schema, Comparable 
orderingVal) {
+    super(null, data, orderingVal);
+    this.structType = schema;
+  }
+
+  public HoodieSparkRecord(HoodieKey key, InternalRow data, StructType schema) 
{
+    super(key, data);
+    this.structType = schema;
+  }
+
+  public HoodieSparkRecord(HoodieKey key, InternalRow data, StructType schema, 
Comparable orderingVal) {
+    super(key, data, orderingVal);
+    this.structType = schema;
+  }
+
+  public HoodieSparkRecord(HoodieKey key, InternalRow data, StructType schema, 
HoodieOperation operation, Comparable orderingVal) {
+    super(key, data, operation, orderingVal);
+    this.structType = schema;
+  }
+
+  public HoodieSparkRecord(HoodieSparkRecord record) {
+    super(record);
+    this.structType = record.structType;
+  }
+
+  @Override
+  public HoodieRecord<InternalRow> newInstance() {
+    return new HoodieSparkRecord(this);
+  }
+
+  @Override
+  public HoodieRecord<InternalRow> newInstance(HoodieKey key, HoodieOperation 
op) {
+    return new HoodieSparkRecord(key, data, structType, op, 
getOrderingValue());
+  }
+
+  @Override
+  public HoodieRecord<InternalRow> newInstance(HoodieKey key) {
+    return new HoodieSparkRecord(key, data, structType, getOrderingValue());
+  }
+
+  @Override
+  public void deflate() {
+  }
+
+  @Override
+  public String getRecordKey(Option<BaseKeyGenerator> keyGeneratorOpt) {
+    if (key != null) {
+      return getRecordKey();
+    }
+    return keyGeneratorOpt.isPresent() ? ((SparkKeyGeneratorInterface) 
keyGeneratorOpt.get()).getRecordKey(data, structType) : 
data.getString(HoodieMetadataField.RECORD_KEY_METADATA_FIELD.ordinal());
+  }
+
+  @Override
+  public String getRecordKey(String keyFieldName) {
+    if (key != null) {
+      return getRecordKey();
+    }
+    Tuple2<StructField, Object> tuple2 = 
HoodieInternalRowUtils.getCachedSchemaPosMap(structType).get(keyFieldName).get();
+    DataType dataType = tuple2._1.dataType();
+    int pos = (Integer) tuple2._2;
+    return data.get(pos, dataType).toString();
+  }
+
+  @Override
+  public HoodieRecordType getRecordType() {
+    return HoodieRecordType.SPARK;
+  }
+
+  @Override
+  public Object getRecordColumnValues(String[] columns, Schema schema, boolean 
consistentLogicalTimestampEnabled) {
+    return HoodieSparkRecordUtils.getRecordColumnValues(this, columns, 
structType, consistentLogicalTimestampEnabled);
+  }
+
+  @Override
+  public HoodieRecord mergeWith(Schema schema, HoodieRecord other, Schema 
otherSchema, Schema writerSchema) throws IOException {
+    StructType otherStructType = 
HoodieInternalRowUtils.getCachedSchema(otherSchema);
+    StructType writerStructType = 
HoodieInternalRowUtils.getCachedSchema(writerSchema);
+    InternalRow mergeRow = HoodieInternalRowUtils.stitchRecords(data, 
structType, (InternalRow) other.getData(), otherStructType, writerStructType);
+    return new HoodieSparkRecord(getKey(), mergeRow, writerStructType, 
getOperation());
+  }
+
+  @Override
+  public HoodieRecord rewriteRecord(Schema recordSchema, Schema targetSchema, 
TypedProperties props) throws IOException {
+    StructType targetStructType = 
HoodieInternalRowUtils.getCachedSchema(targetSchema);
+    InternalRow rewriteRow = HoodieInternalRowUtils.rewriteRecord(data, 
structType, targetStructType);
+    return new HoodieSparkRecord(getKey(), rewriteRow, targetStructType, 
getOperation());
+  }
+
+  @Override
+  public HoodieRecord rewriteRecord(Schema recordSchema, Properties prop, 
boolean schemaOnReadEnabled, Schema writeSchemaWithMetaFields) throws 
IOException {
+    StructType writeSchemaWithMetaFieldsStructType = 
HoodieInternalRowUtils.getCachedSchema(writeSchemaWithMetaFields);
+    InternalRow rewriteRow = schemaOnReadEnabled ? 
HoodieInternalRowUtils.rewriteRecordWithNewSchema(data, structType, 
writeSchemaWithMetaFieldsStructType, new HashMap<>())
+        : HoodieInternalRowUtils.rewriteRecord(data, structType, 
writeSchemaWithMetaFieldsStructType);
+    return new HoodieSparkRecord(getKey(), rewriteRow, 
writeSchemaWithMetaFieldsStructType, getOperation());
+  }
+
+  @Override
+  public HoodieRecord rewriteRecordWithMetadata(Schema recordSchema, 
Properties prop, boolean schemaOnReadEnabled, Schema writeSchemaWithMetaFields, 
String fileName) throws IOException {
+    StructType writeSchemaWithMetaFieldsStructType = 
HoodieInternalRowUtils.getCachedSchema(writeSchemaWithMetaFields);
+    InternalRow rewriteRow = schemaOnReadEnabled ? 
HoodieInternalRowUtils.rewriteEvolutionRecordWithMetadata(data, structType, 
writeSchemaWithMetaFieldsStructType, fileName)
+        : HoodieInternalRowUtils.rewriteRecordWithMetadata(data, structType, 
writeSchemaWithMetaFieldsStructType, fileName);
+    return new HoodieSparkRecord(getKey(), rewriteRow, 
writeSchemaWithMetaFieldsStructType, getOperation());
+  }
+
+  @Override
+  public HoodieRecord rewriteRecordWithNewSchema(Schema recordSchema, 
Properties prop, Schema newSchema, Map<String, String> renameCols) throws 
IOException {
+    StructType newStructType = 
HoodieInternalRowUtils.getCachedSchema(newSchema);
+    InternalRow rewriteRow = 
HoodieInternalRowUtils.rewriteRecordWithNewSchema(data, structType, 
newStructType, renameCols);
+    return new HoodieSparkRecord(getKey(), rewriteRow, newStructType, 
getOperation());
+  }
+
+  @Override
+  public HoodieRecord rewriteRecordWithNewSchema(Schema recordSchema, 
Properties prop, Schema newSchema) throws IOException {
+    StructType newStructType = 
HoodieInternalRowUtils.getCachedSchema(newSchema);
+    InternalRow rewriteRow = HoodieInternalRowUtils.rewriteRecord(data, 
structType, newStructType);
+    return new HoodieSparkRecord(getKey(), rewriteRow, structType, 
getOperation());
+  }
+
+  @Override
+  public HoodieRecord overrideMetadataFieldValue(Schema recordSchema, 
Properties prop, int pos, String newValue) throws IOException {
+    data.update(pos, CatalystTypeConverters.convertToCatalyst(newValue));
+    return this;
+  }
+
+  @Override
+  public HoodieRecord addMetadataValues(Schema recordSchema, Properties prop, 
Map<HoodieMetadataField, String> metadataValues) throws IOException {
+    Arrays.stream(HoodieMetadataField.values()).forEach(metadataField -> {
+      String value = metadataValues.get(metadataField);
+      if (value != null) {
+        data.update(recordSchema.getField(metadataField.getFieldName()).pos(), 
CatalystTypeConverters.convertToCatalyst(value));
+      }
+    });
+    return this;
+  }
+
+  @Override
+  public HoodieRecord expansion(Schema schema, Properties prop, String 
payloadClass,
+      String preCombineField,
+      Option<Pair<String, String>> simpleKeyGenFieldsOpt,
+      Boolean withOperation,
+      Option<String> partitionNameOp,
+      Option<Boolean> populateMetaFieldsOp) {
+    boolean populateMetaFields = populateMetaFieldsOp.orElse(false);
+    if (populateMetaFields) {
+      return HoodieSparkRecordUtils.convertToHoodieSparkRecord(structType, 
data, preCombineField, withOperation);
+    } else if (simpleKeyGenFieldsOpt.isPresent()) {
+      return HoodieSparkRecordUtils.convertToHoodieSparkRecord(structType, 
data, preCombineField, simpleKeyGenFieldsOpt.get(), withOperation, 
Option.empty());
+    } else {
+      return HoodieSparkRecordUtils.convertToHoodieSparkRecord(structType, 
data, preCombineField, withOperation, partitionNameOp);
+    }
+  }
+
+  @Override
+  public HoodieRecord transform(Schema schema, Properties prop, boolean 
useKeygen) {
+    StructType structType = HoodieInternalRowUtils.getCachedSchema(schema);
+    Option<SparkKeyGeneratorInterface> keyGeneratorOpt = Option.empty();
+    if (useKeygen && 
!Boolean.parseBoolean(prop.getOrDefault(POPULATE_META_FIELDS.key(), 
POPULATE_META_FIELDS.defaultValue().toString()).toString())) {
+      try {
+        keyGeneratorOpt = Option.of((SparkKeyGeneratorInterface) 
HoodieSparkKeyGeneratorFactory.createKeyGenerator(new TypedProperties(prop)));
+      } catch (IOException e) {
+        throw new HoodieException("Only SparkKeyGeneratorInterface are 
supported when meta columns are disabled ", e);
+      }
+    }
+    String key = keyGeneratorOpt.isPresent() ? 
keyGeneratorOpt.get().getRecordKey(data, structType)
+        : data.get(HoodieMetadataField.RECORD_KEY_METADATA_FIELD.ordinal(), 
StringType).toString();
+    String partition = keyGeneratorOpt.isPresent() ? 
keyGeneratorOpt.get().getPartitionPath(data, structType)
+        : 
data.get(HoodieMetadataField.PARTITION_PATH_METADATA_FIELD.ordinal(), 
StringType).toString();
+    this.key = new HoodieKey(key, partition);
+
+    return this;
+  }
+
+  @Override
+  public Option<Map<String, String>> getMetadata() {
+    return Option.empty();
+  }
+
+  @Override
+  public boolean isPresent(Schema schema, Properties prop) throws IOException {
+    if (null == data) {
+      return false;
+    }
+    if (schema.getField(HoodieRecord.HOODIE_IS_DELETED_FIELD) == null) {
+      return true;
+    }
+    Object deleteMarker = 
data.get(schema.getField(HoodieRecord.HOODIE_IS_DELETED_FIELD).pos(), 
BooleanType);
+    return !(deleteMarker instanceof Boolean && (boolean) deleteMarker);
+  }
+
+  @Override
+  public boolean shouldIgnore(Schema schema, Properties prop) throws 
IOException {
+    // TODO SENTINEL should refactor SENTINEL without Avro(GenericRecord)
+    if (null != data && data.equals(SENTINEL)) {

Review Comment:
   will fix



##########
hudi-common/src/main/java/org/apache/hudi/common/table/log/AbstractHoodieLogRecordReader.java:
##########
@@ -380,27 +379,22 @@ private boolean isNewInstantBlock(HoodieLogBlock 
logBlock) {
    * handle it.
    */
   private void processDataBlock(HoodieDataBlock dataBlock, Option<KeySpec> 
keySpecOpt) throws Exception {
-    HoodieRecord.Mapper mapper = (rec) -> createHoodieRecord(rec, 
this.hoodieTableMetaClient.getTableConfig(),
-        this.payloadClassFQN, this.preCombineField, this.withOperationField, 
this.simpleKeyGenFields, this.partitionName);
-
-    try (ClosableIterator<HoodieRecord> recordIterator = 
getRecordsIterator(dataBlock, keySpecOpt, mapper)) {
+    try (ClosableIterator<HoodieRecord> recordIterator = 
getRecordsIterator(dataBlock, keySpecOpt, recordType)) {
       Option<Schema> schemaOption = getMergedSchema(dataBlock);
-      Schema finalReadSchema;
-      if (recordIterator instanceof RecordIterator) {
-        finalReadSchema = ((RecordIterator) 
recordIterator).getFinalReadSchema();
-      } else {
-        finalReadSchema = dataBlock.getSchema();
-      }
       while (recordIterator.hasNext()) {
         HoodieRecord currentRecord = recordIterator.next();
-        HoodieRecord record = schemaOption.isPresent()
-            ? currentRecord.rewriteRecordWithNewSchema(finalReadSchema, new 
Properties(), schemaOption.get(), new HashMap<>(), mapper) : currentRecord;
-        processNextRecord(record);
+        HoodieRecord record = schemaOption.isPresent() ? 
currentRecord.rewriteRecordWithNewSchema(dataBlock.getSchema(), new 
Properties(), schemaOption.get(), new HashMap<>()) : currentRecord;

Review Comment:
   unused schema here. Introduced by step1&2, should be deleted



##########
hudi-common/src/main/java/org/apache/hudi/common/table/log/HoodieMergedLogRecordScanner.java:
##########
@@ -317,16 +325,36 @@ public Builder withPartition(String partitionName) {
       return this;
     }
 
+    @Override
+    public Builder withRecordType(HoodieRecordType type) {
+      this.recordType = type;
+      return this;
+    }
+
+    @Override
+    public Builder withMergeClass(String mergeClass) {
+      this.mergeClass = mergeClass;
+      return this;
+    }
+
     @Override
     public HoodieMergedLogRecordScanner build() {
       if (this.partitionName == null && 
CollectionUtils.nonEmpty(this.logFilePaths)) {
         this.partitionName = getRelativePartitionPath(new Path(basePath), new 
Path(this.logFilePaths.get(0)).getParent());
       }
+      assert recordType != null;
+      assert mergeClass != null;
+
+      if (HoodieTableMetadata.isMetadataTable(basePath)) {
+        recordType = HoodieRecordType.AVRO;
+        mergeClass = HoodieAvroRecordMerge.class.getName();
+      }

Review Comment:
   Duplicate code with `HoodieUnMergedLogRecordScanner`



##########
hudi-client/hudi-client-common/src/main/java/org/apache/hudi/table/action/commit/HoodieMergeHelper.java:
##########
@@ -135,21 +131,22 @@ public void runMerge(HoodieTable<T, 
HoodieData<HoodieRecord<T>>, HoodieData<Hood
         readerIterator = getMergingIterator(table, mergeHandle, baseFile, 
reader, readSchema, externalSchemaTransformation);
       } else {
         if (needToReWriteRecord) {
-          readerIterator = new 
RewriteIterator(reader.getRecordIterator(HoodieAvroIndexedRecord::new), 
readSchema, readSchema, table.getConfig().getProps(), renameCols);
+          readerIterator = new RewriteIterator(reader.getRecordIterator(), 
readSchema, readSchema, table.getConfig().getProps(), renameCols);
         } else {
-          readerIterator = reader.getRecordIterator(readSchema, 
HoodieAvroIndexedRecord::new);
+          readerIterator = reader.getRecordIterator(readSchema);
         }
       }
 
-      ThreadLocal<BinaryEncoder> encoderCache = new ThreadLocal<>();
-      ThreadLocal<BinaryDecoder> decoderCache = new ThreadLocal<>();
       wrapper = new 
BoundedInMemoryExecutor(table.getConfig().getWriteBufferLimitBytes(), 
readerIterator,
           new UpdateHandler(mergeHandle), record -> {
         if (!externalSchemaTransformation) {
           return record;
         }
-        // TODO Other type of record need to change
-        return transformRecordBasedOnNewSchema(gReader, gWriter, encoderCache, 
decoderCache, (GenericRecord) ((HoodieRecord)record).getData());
+        try {
+          return ((HoodieRecord) record).rewriteRecord(writerSchema, 
readerSchema, new TypedProperties());
+        } catch (IOException e) {
+          throw new HoodieException(e);

Review Comment:
   will fix



##########
hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/HoodieMergeHandle.java:
##########
@@ -329,19 +329,16 @@ protected boolean writeRecord(HoodieRecord<T> 
hoodieRecord, Option<HoodieRecord>
    * Go through an old record. Here if we detect a newer version shows up, we 
write the new one to the file.
    */
   public void write(HoodieRecord<T> oldRecord) {
-    String key = oldRecord.getRecordKey(keyGeneratorOpt);
-    boolean copyOldRecord = true;
     Schema schema = useWriterSchemaForCompaction ? tableSchemaWithMetaFields : 
tableSchema;
+    boolean copyOldRecord = true;
+    String key = oldRecord.getRecordKey(keyGeneratorOpt);

Review Comment:
   Revert the changes



##########
hudi-client/hudi-spark-client/src/main/java/org/apache/hudi/io/storage/HoodieSparkFileWriter.java:
##########
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hudi.io.storage;
+
+import org.apache.avro.Schema;
+import org.apache.hudi.common.model.HoodieKey;
+import org.apache.hudi.common.model.HoodieRecord;
+import org.apache.spark.sql.catalyst.CatalystTypeConverters;
+import org.apache.spark.sql.catalyst.InternalRow;
+
+import java.io.IOException;
+import java.util.Properties;
+
+public interface HoodieSparkFileWriter extends HoodieFileWriter {
+  boolean canWrite();
+
+  void close() throws IOException;
+
+  void writeRowWithMetadata(HoodieKey recordKey, InternalRow row) throws 
IOException;
+
+  void writeRow(String recordKey, InternalRow row) throws IOException;
+
+  @Override
+  default void write(String recordKey, HoodieRecord record, Schema schema, 
Properties props) throws IOException {
+    writeRow(recordKey, (InternalRow) record.getData());
+  }
+
+  @Override
+  default void writeWithMetadata(HoodieKey key, HoodieRecord record, Schema 
schema, Properties props) throws IOException {
+    writeRowWithMetadata(key, (InternalRow) record.getData());
+  }
+
+  default InternalRow prepRecordWithMetadata(HoodieKey key, InternalRow row, 
String instantTime, Integer partitionId, long recordIndex, String fileName)  {
+    String seqId = HoodieRecord.generateSequenceId(instantTime, partitionId, 
recordIndex);
+    
row.update(HoodieRecord.HoodieMetadataField.COMMIT_TIME_METADATA_FIELD.ordinal(),
 CatalystTypeConverters.convertToCatalyst(instantTime));
+    
row.update(HoodieRecord.HoodieMetadataField.COMMIT_SEQNO_METADATA_FIELD.ordinal(),
 CatalystTypeConverters.convertToCatalyst(seqId));
+    
row.update(HoodieRecord.HoodieMetadataField.RECORD_KEY_METADATA_FIELD.ordinal(),
 CatalystTypeConverters.convertToCatalyst(key.getRecordKey()));
+    
row.update(HoodieRecord.HoodieMetadataField.PARTITION_PATH_METADATA_FIELD.ordinal(),
 CatalystTypeConverters.convertToCatalyst(key.getPartitionPath()));
+    
row.update(HoodieRecord.HoodieMetadataField.FILENAME_METADATA_FIELD.ordinal(), 
CatalystTypeConverters.convertToCatalyst(fileName));
+    return row;
+    // Object[] metadata = {instantTime, seqId, key.getRecordKey(), 
key.getPartitionPath(), fileName};
+    // InternalRow metadataRow = new GenericInternalRow(Arrays.stream(metadata)
+    //    .map(o -> CatalystTypeConverters.convertToCatalyst(o)).toArray());
+    // return new JoinedRow(metadataRow, row);

Review Comment:
   will fix



##########
hudi-common/src/main/java/org/apache/hudi/common/table/log/HoodieMergedLogRecordScanner.java:
##########
@@ -189,8 +188,13 @@ protected void processNextDeletedRecord(DeleteRecord 
deleteRecord) {
       }
     }
     // Put the DELETE record
-    records.put(key, SpillableMapUtils.generateEmptyPayload(key,
-        deleteRecord.getPartitionPath(), deleteRecord.getOrderingValue(), 
getPayloadClassFQN()));
+    if (recordType == HoodieRecordType.AVRO) {
+      records.put(key, SpillableMapUtils.generateEmptyPayload(key,
+          deleteRecord.getPartitionPath(), deleteRecord.getOrderingValue(), 
getPayloadClassFQN()));
+    } else {
+      HoodieEmptyRecord record = new HoodieEmptyRecord<>(new HoodieKey(key, 
deleteRecord.getPartitionPath()), deleteRecord.getOrderingValue(), recordType);

Review Comment:
   moving into `SpillableMapUtils.generateEmptyPayload` is restraint



##########
hudi-spark-datasource/hudi-spark-common/src/main/java/org/apache/hudi/HoodieSparkDefaultRecordMerge.java:
##########
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hudi;
+
+import org.apache.hudi.common.model.HoodieEmptyRecord;
+import org.apache.hudi.common.model.HoodieRecord;
+import org.apache.hudi.common.model.HoodieRecord.HoodieRecordType;
+import org.apache.hudi.common.util.Option;
+
+import org.apache.avro.Schema;
+
+import java.io.IOException;
+import java.util.Properties;
+
+public class HoodieSparkDefaultRecordMerge extends HoodieSparkRecordMerge {
+
+  @Override
+  public Option<HoodieRecord> combineAndGetUpdateValue(HoodieRecord older, 
HoodieRecord newer, Schema schema, Properties props) throws IOException {
+    assert older.getRecordType() == HoodieRecordType.SPARK;
+    assert newer.getRecordType() == HoodieRecordType.SPARK;
+
+    // Null check is needed here to support schema evolution. The record in 
storage may be from old schema where
+    // the new ordering column might not be present and hence returns null.
+    if (!needUpdatingPersistedRecord(older, newer, props)) {

Review Comment:
   Cloud precombine field be changed?



##########
hudi-client/hudi-spark-client/src/main/java/org/apache/hudi/execution/bulkinsert/RDDSpatialCurveSortPartitioner.java:
##########
@@ -54,42 +63,64 @@
   private final SerializableSchema schema;
   private final HoodieClusteringConfig.LayoutOptimizationStrategy 
layoutOptStrategy;
   private final HoodieClusteringConfig.SpatialCurveCompositionStrategyType 
curveCompositionStrategyType;
+  private final HoodieRecordType recordType;
 
   public RDDSpatialCurveSortPartitioner(HoodieSparkEngineContext 
sparkEngineContext,
-                                        String[] orderByColumns,
-                                        
HoodieClusteringConfig.LayoutOptimizationStrategy layoutOptStrategy,
-                                        
HoodieClusteringConfig.SpatialCurveCompositionStrategyType 
curveCompositionStrategyType,
-                                        Schema schema) {
+      String[] orderByColumns,
+      LayoutOptimizationStrategy layoutOptStrategy,
+      SpatialCurveCompositionStrategyType curveCompositionStrategyType,
+      Schema schema, HoodieRecordType recordType) {
     this.sparkEngineContext = sparkEngineContext;
     this.orderByColumns = orderByColumns;
     this.layoutOptStrategy = layoutOptStrategy;
     this.curveCompositionStrategyType = curveCompositionStrategyType;
     this.schema = new SerializableSchema(schema);
+    this.recordType = recordType;
   }
 
   @Override
   public JavaRDD<HoodieRecord<T>> repartitionRecords(JavaRDD<HoodieRecord<T>> 
records, int outputSparkPartitions) {
-    JavaRDD<GenericRecord> genericRecordsRDD =
-        records.map(f -> (GenericRecord) f.toIndexedRecord(schema.get(), new 
Properties()).get());
-
-    Dataset<Row> sourceDataset =
-        AvroConversionUtils.createDataFrame(
-            genericRecordsRDD.rdd(),
-            schema.toString(),
-            sparkEngineContext.getSqlContext().sparkSession()
-        );
-
-    Dataset<Row> sortedDataset = reorder(sourceDataset, outputSparkPartitions);
-
-    return HoodieSparkUtils.createRdd(sortedDataset, schema.get().getName(), 
schema.get().getNamespace(), false, Option.empty())
-        .toJavaRDD()
-        .map(record -> {
-          String key = 
record.get(HoodieRecord.RECORD_KEY_METADATA_FIELD).toString();
-          String partition = 
record.get(HoodieRecord.PARTITION_PATH_METADATA_FIELD).toString();
-          HoodieKey hoodieKey = new HoodieKey(key, partition);
-          HoodieRecord hoodieRecord = new HoodieAvroRecord(hoodieKey, new 
RewriteAvroPayload(record));
-          return hoodieRecord;
-        });
+    if (recordType == HoodieRecordType.AVRO) {
+      JavaRDD<GenericRecord> genericRecordsRDD =
+          records.map(f -> (GenericRecord) f.toIndexedRecord(schema.get(), new 
Properties()).get());
+
+      Dataset<Row> sourceDataset =
+          AvroConversionUtils.createDataFrame(
+              genericRecordsRDD.rdd(),
+              schema.toString(),
+              sparkEngineContext.getSqlContext().sparkSession()
+          );
+
+      Dataset<Row> sortedDataset = reorder(sourceDataset, 
outputSparkPartitions);
+
+      return HoodieSparkUtils.createRdd(sortedDataset, schema.get().getName(), 
schema.get().getNamespace(), false, Option.empty())
+          .toJavaRDD()
+          .map(record -> {
+            String key = 
record.get(HoodieRecord.RECORD_KEY_METADATA_FIELD).toString();
+            String partition = 
record.get(HoodieRecord.PARTITION_PATH_METADATA_FIELD).toString();
+            HoodieKey hoodieKey = new HoodieKey(key, partition);
+            HoodieRecord hoodieRecord = new HoodieAvroRecord(hoodieKey, new 
RewriteAvroPayload(record));
+            return hoodieRecord;
+          });
+    } else if (recordType == HoodieRecordType.SPARK) {
+      StructType structType = 
HoodieInternalRowUtils.getCachedSchema(schema.get());
+      Dataset<Row> sourceDataset = 
SparkConversionUtils.createDataFrame(records.rdd(), 
sparkEngineContext.getSqlContext().sparkSession(), structType);
+
+      Dataset<Row> sortedDataset = reorder(sourceDataset, 
outputSparkPartitions);
+
+      return sortedDataset.queryExecution().toRdd()
+          .toJavaRDD()
+          .map(row -> {
+            InternalRow internalRow = row.copy();

Review Comment:
   The root cause is in the `ParquetReaderIterator`.
   ```java
    public T next() {
       try {
         // To handle case when next() is called before hasNext()
         if (this.next == null) {
           if (!hasNext()) {
             throw new HoodieException("No more records left to read from 
parquet file");
           }
         }
         T retVal = this.next;
         this.next = read();
         return retVal;
       } catch (Exception e) {
         FileIOUtils.closeQuietly(parquetReader);
         throw new HoodieException("unable to read next record from parquet 
file ", e);
       }
     }
   
     private T read() throws IOException {
       T record = parquetReader.read();
       if (mapper == null || record == null) {
         return record;
       } else {
         return mapper.apply(record);
       }
     }
   
   ```
   Caused by inner implementations of the internalRow, when go next and read 
without copying, the value of `retVal` will be changed meanwhile.



##########
hudi-client/hudi-client-common/src/main/java/org/apache/hudi/table/action/commit/HoodieMergeHelper.java:
##########
@@ -84,21 +80,21 @@ public void runMerge(HoodieTable<T, 
HoodieData<HoodieRecord<T>>, HoodieData<Hood
     Configuration cfgForHoodieFile = new Configuration(table.getHadoopConf());
     HoodieBaseFile baseFile = mergeHandle.baseFileForMerge();
 
-    final GenericDatumWriter<GenericRecord> gWriter;
-    final GenericDatumReader<GenericRecord> gReader;
     Schema readSchema;
+    Schema readerSchema;
+    Schema writerSchema;
     if (externalSchemaTransformation || 
baseFile.getBootstrapBaseFile().isPresent()) {
-      readSchema = 
HoodieFileReaderFactory.getFileReader(table.getHadoopConf(), 
mergeHandle.getOldFilePath()).getSchema();
-      gWriter = new GenericDatumWriter<>(readSchema);
-      gReader = new GenericDatumReader<>(readSchema, 
mergeHandle.getWriterSchemaWithMetaFields());
+      readSchema = 
HoodieFileReaderFactory.getReaderFactory(table.getConfig().getRecordType()).getFileReader(table.getHadoopConf(),
 mergeHandle.getOldFilePath()).getSchema();
+      writerSchema = readSchema;
+      readerSchema = mergeHandle.getWriterSchemaWithMetaFields();
     } else {
-      gReader = null;
-      gWriter = null;
+      readerSchema = null;
+      writerSchema = null;
       readSchema = mergeHandle.getWriterSchemaWithMetaFields();
     }
 
     BoundedInMemoryExecutor<GenericRecord, GenericRecord, Void> wrapper = null;
-    HoodieFileReader reader = 
HoodieFileReaderFactory.getFileReader(cfgForHoodieFile, 
mergeHandle.getOldFilePath());
+    HoodieFileReader reader = 
HoodieFileReaderFactory.getReaderFactory(table.getConfig().getRecordType()).getFileReader(cfgForHoodieFile,
 mergeHandle.getOldFilePath());

Review Comment:
   will fix



##########
hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/HoodieMergeOnReadRDD.scala:
##########
@@ -302,14 +306,29 @@ class HoodieMergeOnReadRDD(@transient sc: SparkContext,
     private def serialize(curRowRecord: InternalRow): GenericRecord =
       serializer.serialize(curRowRecord).asInstanceOf[GenericRecord]
 
-    private def merge(curAvroRecord: GenericRecord, newRecord: HoodieRecord[_ 
<: HoodieRecordPayload[_]]): Option[IndexedRecord] = {
+    private def merge(curRow: InternalRow, newRecord: HoodieRecord[_]): 
Option[InternalRow] = {
       // NOTE: We have to pass in Avro Schema used to read from Delta Log file 
since we invoke combining API
       //       on the record from the Delta Log
-      val combinedRecord = merge.combineAndGetUpdateValue(new 
HoodieAvroIndexedRecord(curAvroRecord), newRecord, logFileReaderAvroSchema, 
payloadProps)
-      if (combinedRecord.isPresent) {
-        
toScalaOption(combinedRecord.get.asInstanceOf[HoodieAvroIndexedRecord].toIndexedRecord)
-      } else {
-        Option.empty
+      newRecord.getRecordType match {
+        case HoodieRecordType.SPARK =>
+          // Get ordering value in curAvroRecord
+          var curRecord = new HoodieSparkRecord(curRow, 
baseFileReaderSchema.structTypeSchema)
+          val orderField = 
payloadProps.getProperty(HoodiePayloadProps.PAYLOAD_ORDERING_FIELD_PROP_KEY)

Review Comment:
   move to sparkMerger



##########
hudi-client/hudi-spark-client/src/main/java/org/apache/hudi/io/storage/HoodieSparkFileReader.java:
##########
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hudi.io.storage;
+
+import org.apache.hudi.HoodieInternalRowUtils;
+import org.apache.hudi.commmon.model.HoodieSparkRecord;
+import org.apache.hudi.common.model.HoodieRecord;
+import org.apache.hudi.common.util.ClosableIterator;
+import org.apache.hudi.common.util.MappingIterator;
+
+import org.apache.avro.Schema;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.spark.sql.catalyst.InternalRow;
+import org.apache.spark.sql.types.StructType;
+
+import java.io.IOException;
+
+import static org.apache.hudi.TypeUtils.unsafeCast;
+
+public interface HoodieSparkFileReader extends HoodieFileReader<InternalRow> {
+
+  Logger LOG = LogManager.getLogger(HoodieSparkFileReader.class);

Review Comment:
   will fix



##########
hudi-client/hudi-spark-client/src/main/java/org/apache/hudi/io/storage/HoodieSparkFileWriter.java:
##########
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hudi.io.storage;
+
+import org.apache.avro.Schema;
+import org.apache.hudi.common.model.HoodieKey;
+import org.apache.hudi.common.model.HoodieRecord;
+import org.apache.spark.sql.catalyst.CatalystTypeConverters;
+import org.apache.spark.sql.catalyst.InternalRow;
+
+import java.io.IOException;
+import java.util.Properties;
+
+public interface HoodieSparkFileWriter extends HoodieFileWriter {
+  boolean canWrite();
+
+  void close() throws IOException;
+
+  void writeRowWithMetadata(HoodieKey recordKey, InternalRow row) throws 
IOException;
+
+  void writeRow(String recordKey, InternalRow row) throws IOException;
+
+  @Override
+  default void write(String recordKey, HoodieRecord record, Schema schema, 
Properties props) throws IOException {
+    writeRow(recordKey, (InternalRow) record.getData());
+  }
+
+  @Override
+  default void writeWithMetadata(HoodieKey key, HoodieRecord record, Schema 
schema, Properties props) throws IOException {
+    writeRowWithMetadata(key, (InternalRow) record.getData());
+  }
+
+  default InternalRow prepRecordWithMetadata(HoodieKey key, InternalRow row, 
String instantTime, Integer partitionId, long recordIndex, String fileName)  {
+    String seqId = HoodieRecord.generateSequenceId(instantTime, partitionId, 
recordIndex);
+    
row.update(HoodieRecord.HoodieMetadataField.COMMIT_TIME_METADATA_FIELD.ordinal(),
 CatalystTypeConverters.convertToCatalyst(instantTime));
+    
row.update(HoodieRecord.HoodieMetadataField.COMMIT_SEQNO_METADATA_FIELD.ordinal(),
 CatalystTypeConverters.convertToCatalyst(seqId));
+    
row.update(HoodieRecord.HoodieMetadataField.RECORD_KEY_METADATA_FIELD.ordinal(),
 CatalystTypeConverters.convertToCatalyst(key.getRecordKey()));
+    
row.update(HoodieRecord.HoodieMetadataField.PARTITION_PATH_METADATA_FIELD.ordinal(),
 CatalystTypeConverters.convertToCatalyst(key.getPartitionPath()));
+    
row.update(HoodieRecord.HoodieMetadataField.FILENAME_METADATA_FIELD.ordinal(), 
CatalystTypeConverters.convertToCatalyst(fileName));

Review Comment:
   will fix



##########
hudi-client/hudi-spark-client/src/main/java/org/apache/hudi/commmon/model/HoodieSparkRecord.java:
##########
@@ -0,0 +1,270 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hudi.commmon.model;
+
+import org.apache.hudi.HoodieInternalRowUtils;
+import org.apache.hudi.common.config.TypedProperties;
+import org.apache.hudi.common.model.HoodieKey;
+import org.apache.hudi.common.model.HoodieOperation;
+import org.apache.hudi.common.model.HoodieRecord;
+import org.apache.hudi.common.util.Option;
+import org.apache.hudi.common.util.collection.Pair;
+import org.apache.hudi.exception.HoodieException;
+import org.apache.hudi.keygen.BaseKeyGenerator;
+import org.apache.hudi.keygen.SparkKeyGeneratorInterface;
+import org.apache.hudi.keygen.factory.HoodieSparkKeyGeneratorFactory;
+import org.apache.hudi.util.HoodieSparkRecordUtils;
+
+import org.apache.avro.Schema;
+import org.apache.avro.generic.IndexedRecord;
+import org.apache.spark.sql.catalyst.CatalystTypeConverters;
+import org.apache.spark.sql.catalyst.InternalRow;
+import org.apache.spark.sql.types.DataType;
+import org.apache.spark.sql.types.StructField;
+import org.apache.spark.sql.types.StructType;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import scala.Tuple2;
+
+import static 
org.apache.hudi.common.table.HoodieTableConfig.POPULATE_META_FIELDS;
+import static org.apache.spark.sql.types.DataTypes.BooleanType;
+import static org.apache.spark.sql.types.DataTypes.StringType;
+
+/**
+ * Spark Engine-specific Implementations of `HoodieRecord`.
+ */
+public class HoodieSparkRecord extends HoodieRecord<InternalRow> {
+
+  // IndexedRecord hold its schema, InternalRow should also hold its schema
+  private final StructType structType;
+
+  public HoodieSparkRecord(InternalRow data, StructType schema) {
+    super(null, data);
+    this.structType = schema;
+  }
+
+  public HoodieSparkRecord(InternalRow data, StructType schema, Comparable 
orderingVal) {
+    super(null, data, orderingVal);
+    this.structType = schema;
+  }
+
+  public HoodieSparkRecord(HoodieKey key, InternalRow data, StructType schema) 
{
+    super(key, data);
+    this.structType = schema;
+  }
+
+  public HoodieSparkRecord(HoodieKey key, InternalRow data, StructType schema, 
Comparable orderingVal) {
+    super(key, data, orderingVal);
+    this.structType = schema;
+  }
+
+  public HoodieSparkRecord(HoodieKey key, InternalRow data, StructType schema, 
HoodieOperation operation, Comparable orderingVal) {
+    super(key, data, operation, orderingVal);
+    this.structType = schema;
+  }
+
+  public HoodieSparkRecord(HoodieSparkRecord record) {
+    super(record);
+    this.structType = record.structType;
+  }
+
+  @Override
+  public HoodieRecord<InternalRow> newInstance() {
+    return new HoodieSparkRecord(this);
+  }
+
+  @Override
+  public HoodieRecord<InternalRow> newInstance(HoodieKey key, HoodieOperation 
op) {
+    return new HoodieSparkRecord(key, data, structType, op, 
getOrderingValue());
+  }
+
+  @Override
+  public HoodieRecord<InternalRow> newInstance(HoodieKey key) {
+    return new HoodieSparkRecord(key, data, structType, getOrderingValue());
+  }
+
+  @Override
+  public void deflate() {
+  }
+
+  @Override
+  public String getRecordKey(Option<BaseKeyGenerator> keyGeneratorOpt) {
+    if (key != null) {
+      return getRecordKey();
+    }
+    return keyGeneratorOpt.isPresent() ? ((SparkKeyGeneratorInterface) 
keyGeneratorOpt.get()).getRecordKey(data, structType) : 
data.getString(HoodieMetadataField.RECORD_KEY_METADATA_FIELD.ordinal());
+  }
+
+  @Override
+  public String getRecordKey(String keyFieldName) {
+    if (key != null) {
+      return getRecordKey();
+    }
+    Tuple2<StructField, Object> tuple2 = 
HoodieInternalRowUtils.getCachedSchemaPosMap(structType).get(keyFieldName).get();
+    DataType dataType = tuple2._1.dataType();
+    int pos = (Integer) tuple2._2;
+    return data.get(pos, dataType).toString();
+  }
+
+  @Override
+  public HoodieRecordType getRecordType() {
+    return HoodieRecordType.SPARK;
+  }
+
+  @Override
+  public Object getRecordColumnValues(String[] columns, Schema schema, boolean 
consistentLogicalTimestampEnabled) {
+    return HoodieSparkRecordUtils.getRecordColumnValues(this, columns, 
structType, consistentLogicalTimestampEnabled);
+  }
+
+  @Override
+  public HoodieRecord mergeWith(Schema schema, HoodieRecord other, Schema 
otherSchema, Schema writerSchema) throws IOException {
+    StructType otherStructType = 
HoodieInternalRowUtils.getCachedSchema(otherSchema);
+    StructType writerStructType = 
HoodieInternalRowUtils.getCachedSchema(writerSchema);
+    InternalRow mergeRow = HoodieInternalRowUtils.stitchRecords(data, 
structType, (InternalRow) other.getData(), otherStructType, writerStructType);
+    return new HoodieSparkRecord(getKey(), mergeRow, writerStructType, 
getOperation());
+  }
+
+  @Override
+  public HoodieRecord rewriteRecord(Schema recordSchema, Schema targetSchema, 
TypedProperties props) throws IOException {
+    StructType targetStructType = 
HoodieInternalRowUtils.getCachedSchema(targetSchema);
+    InternalRow rewriteRow = HoodieInternalRowUtils.rewriteRecord(data, 
structType, targetStructType);
+    return new HoodieSparkRecord(getKey(), rewriteRow, targetStructType, 
getOperation());
+  }
+
+  @Override
+  public HoodieRecord rewriteRecord(Schema recordSchema, Properties prop, 
boolean schemaOnReadEnabled, Schema writeSchemaWithMetaFields) throws 
IOException {
+    StructType writeSchemaWithMetaFieldsStructType = 
HoodieInternalRowUtils.getCachedSchema(writeSchemaWithMetaFields);
+    InternalRow rewriteRow = schemaOnReadEnabled ? 
HoodieInternalRowUtils.rewriteRecordWithNewSchema(data, structType, 
writeSchemaWithMetaFieldsStructType, new HashMap<>())
+        : HoodieInternalRowUtils.rewriteRecord(data, structType, 
writeSchemaWithMetaFieldsStructType);
+    return new HoodieSparkRecord(getKey(), rewriteRow, 
writeSchemaWithMetaFieldsStructType, getOperation());
+  }
+
+  @Override
+  public HoodieRecord rewriteRecordWithMetadata(Schema recordSchema, 
Properties prop, boolean schemaOnReadEnabled, Schema writeSchemaWithMetaFields, 
String fileName) throws IOException {
+    StructType writeSchemaWithMetaFieldsStructType = 
HoodieInternalRowUtils.getCachedSchema(writeSchemaWithMetaFields);
+    InternalRow rewriteRow = schemaOnReadEnabled ? 
HoodieInternalRowUtils.rewriteEvolutionRecordWithMetadata(data, structType, 
writeSchemaWithMetaFieldsStructType, fileName)
+        : HoodieInternalRowUtils.rewriteRecordWithMetadata(data, structType, 
writeSchemaWithMetaFieldsStructType, fileName);
+    return new HoodieSparkRecord(getKey(), rewriteRow, 
writeSchemaWithMetaFieldsStructType, getOperation());
+  }
+
+  @Override
+  public HoodieRecord rewriteRecordWithNewSchema(Schema recordSchema, 
Properties prop, Schema newSchema, Map<String, String> renameCols) throws 
IOException {
+    StructType newStructType = 
HoodieInternalRowUtils.getCachedSchema(newSchema);
+    InternalRow rewriteRow = 
HoodieInternalRowUtils.rewriteRecordWithNewSchema(data, structType, 
newStructType, renameCols);
+    return new HoodieSparkRecord(getKey(), rewriteRow, newStructType, 
getOperation());
+  }
+
+  @Override
+  public HoodieRecord rewriteRecordWithNewSchema(Schema recordSchema, 
Properties prop, Schema newSchema) throws IOException {
+    StructType newStructType = 
HoodieInternalRowUtils.getCachedSchema(newSchema);
+    InternalRow rewriteRow = HoodieInternalRowUtils.rewriteRecord(data, 
structType, newStructType);
+    return new HoodieSparkRecord(getKey(), rewriteRow, structType, 
getOperation());
+  }
+
+  @Override
+  public HoodieRecord overrideMetadataFieldValue(Schema recordSchema, 
Properties prop, int pos, String newValue) throws IOException {
+    data.update(pos, CatalystTypeConverters.convertToCatalyst(newValue));
+    return this;
+  }
+
+  @Override
+  public HoodieRecord addMetadataValues(Schema recordSchema, Properties prop, 
Map<HoodieMetadataField, String> metadataValues) throws IOException {
+    Arrays.stream(HoodieMetadataField.values()).forEach(metadataField -> {
+      String value = metadataValues.get(metadataField);
+      if (value != null) {
+        data.update(recordSchema.getField(metadataField.getFieldName()).pos(), 
CatalystTypeConverters.convertToCatalyst(value));
+      }
+    });
+    return this;
+  }
+
+  @Override
+  public HoodieRecord expansion(Schema schema, Properties prop, String 
payloadClass,
+      String preCombineField,
+      Option<Pair<String, String>> simpleKeyGenFieldsOpt,
+      Boolean withOperation,
+      Option<String> partitionNameOp,
+      Option<Boolean> populateMetaFieldsOp) {
+    boolean populateMetaFields = populateMetaFieldsOp.orElse(false);
+    if (populateMetaFields) {
+      return HoodieSparkRecordUtils.convertToHoodieSparkRecord(structType, 
data, preCombineField, withOperation);
+    } else if (simpleKeyGenFieldsOpt.isPresent()) {
+      return HoodieSparkRecordUtils.convertToHoodieSparkRecord(structType, 
data, preCombineField, simpleKeyGenFieldsOpt.get(), withOperation, 
Option.empty());
+    } else {
+      return HoodieSparkRecordUtils.convertToHoodieSparkRecord(structType, 
data, preCombineField, withOperation, partitionNameOp);
+    }
+  }
+
+  @Override
+  public HoodieRecord transform(Schema schema, Properties prop, boolean 
useKeygen) {
+    StructType structType = HoodieInternalRowUtils.getCachedSchema(schema);
+    Option<SparkKeyGeneratorInterface> keyGeneratorOpt = Option.empty();

Review Comment:
   will fix
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to