danny0405 commented on code in PR #19114:
URL: https://github.com/apache/hudi/pull/19114#discussion_r3497382397
##########
hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/table/format/cdc/CdcIterators.java:
##########
@@ -417,24 +494,31 @@ private static int[] computeRequiredPos(HoodieSchema
tableSchema, HoodieSchema r
.toArray();
}
+ private static boolean isNativeCdcFileSplit(HoodieCDCFileSplit fileSplit) {
+ boolean nativeCdc =
FSUtils.matchNativeLogFile(fileSplit.getCdcFiles().get(0)).isPresent();
Review Comment:
Fixed in 3d16791c1d49: Flink AS_IS CDC iterator creation now handles
null/empty CDC file lists by returning HoodieCDCLogRecordIterator.empty()
before checking the first path.
##########
hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/table/format/cdc/CdcIterators.java:
##########
@@ -390,20 +421,66 @@ protected BaseImageIterator(
this.requiredPos = computeRequiredPos(tableSchema, requiredSchema);
this.recordBuilder = new
GenericRecordBuilder(requiredSchema.getAvroSchema());
this.avroToRowDataConverter =
AvroToRowDataConverters.createRowConverter(requiredSchema, requiredRowType,
true);
+ this.nativeCdcImageProjection =
RowDataProjection.instance(requiredRowType, requiredPos);
+ this.nativeCdcImageArity =
HoodieSchemaUtils.removeMetadataFields(tableSchema).getFields().size();
+ this.cdcItr = createCdcRecordIterator(
+ conf, hadoopConf, tablePath, cdcSchema, fileSplit);
+ }
- StoragePath hadoopTablePath = new StoragePath(tablePath);
- HoodieStorage storage = HoodieStorageUtils.getStorage(
- tablePath, HadoopFSUtils.getStorageConf(hadoopConf));
- HoodieLogFile[] cdcLogFiles = fileSplit.getCdcFiles().stream()
- .map(cdcFile -> {
- try {
- return new HoodieLogFile(storage.getPathInfo(new
StoragePath(hadoopTablePath, cdcFile)));
- } catch (IOException e) {
- throw new HoodieIOException("Failed to get file status for CDC
log: " + cdcFile, e);
- }
- })
- .toArray(HoodieLogFile[]::new);
- this.cdcItr = new HoodieCDCLogRecordIterator(storage, cdcLogFiles,
cdcSchema);
+ private static HoodieCDCLogRecordIterator<?> createCdcRecordIterator(
+ org.apache.flink.configuration.Configuration conf,
+ org.apache.hadoop.conf.Configuration hadoopConf,
+ String tablePath,
+ HoodieSchema cdcSchema,
+ HoodieCDCFileSplit fileSplit) {
+ if (isNativeCdcFileSplit(fileSplit)) {
+ return new HoodieCDCNativeLogRecordIterator<>(
+ fileSplit.getCdcFiles().iterator(),
+ cdcFile -> getNativeCdcFileIterator(conf, hadoopConf, tablePath,
cdcFile, cdcSchema),
+ ROW_DATA_CDC_RECORD_ACCESSOR);
+ } else {
+ StoragePath hadoopTablePath = new StoragePath(tablePath);
+ HoodieStorage storage = HoodieStorageUtils.getStorage(
+ tablePath, HadoopFSUtils.getStorageConf(hadoopConf));
+ HoodieLogFile[] cdcLogFiles = fileSplit.getCdcFiles().stream()
+ .map(cdcFile -> {
+ try {
+ return new HoodieLogFile(storage.getPathInfo(new
StoragePath(hadoopTablePath, cdcFile)));
+ } catch (IOException e) {
+ throw new HoodieIOException("Failed to get file status for CDC
log: " + cdcFile, e);
+ }
+ })
+ .toArray(HoodieLogFile[]::new);
+ return new HoodieCDCInlineLogRecordIterator(storage, cdcLogFiles,
cdcSchema);
+ }
+ }
+
+ private static ClosableIterator<RowData> getNativeCdcFileIterator(
+ org.apache.flink.configuration.Configuration conf,
+ org.apache.hadoop.conf.Configuration hadoopConf,
+ String tablePath,
+ String cdcFile,
+ HoodieSchema cdcSchema) {
+ try {
+ DataType cdcDataType =
HoodieSchemaConverter.convertToDataType(cdcSchema);
+ RowType cdcRowType = (RowType) cdcDataType.getLogicalType();
+ return RecordIterators.getParquetRecordIterator(
Review Comment:
Fixed in 3d16791c1d49: native CDC reads in Flink now route through
HoodieIOFactory/HoodieRowDataFileReader using the native log file suffix to
select the format, instead of calling the parquet iterator directly. The
returned iterator also closes both the row iterator and the file reader.
##########
hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/cdc/CDCFileGroupIterator.scala:
##########
@@ -233,10 +225,21 @@ class CDCFileGroupIterator(split: HoodieCDCFileGroupSplit,
// images. Keyed by the record's schema id so schema evolution is handled
correctly.
private val cdcImageConverterMap: mutable.Map[Integer,
(UnaryOperator[InternalRow], InternalRowToJsonStringConverter)] =
mutable.Map.empty
+ private lazy val cdcDataSparkSchema: StructType =
HoodieSchemaConversionUtils.convertHoodieSchemaToStructType(
+ HoodieSchemaUtils.removeMetadataFields(schema))
+
+ private lazy val nativeCdcTableSchemaOpt = {
Review Comment:
Fixed in 3d16791c1d49: renamed nativeCdcTableSchemaOpt to
nativeCdcParquetSchemaOpt to make it clear this is the CDC Parquet MessageType,
not the table schema.
##########
hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/cdc/CDCFileGroupIterator.scala:
##########
@@ -558,6 +517,88 @@ class CDCFileGroupIterator(split: HoodieCDCFileGroupSplit,
CloseableIteratorListener.addListener(keyBasedFileGroupRecordBuffer.get().getLogRecordIterator).asScala
}
+ private def readNativeCdcFile(cdcFile: String): Iterator[InternalRow] = {
+ val absCDCPath = new StoragePath(basePath, cdcFile)
+ val fileStatus = storage.getPathInfo(absCDCPath)
+ val pf = sparkPartitionedFileUtils.createPartitionedFile(
+ InternalRow.empty, absCDCPath, 0, fileStatus.getLength)
+ baseFileReader.read(pf, cdcSparkSchema, new StructType(),
+ org.apache.hudi.common.util.Option.empty(), Seq.empty, conf,
nativeCdcTableSchemaOpt)
+ }
+
+ private val nativeCdcRecordAccessor = new
HoodieCDCEngineRecordAccessor[InternalRow] {
+ override def getOperation(record: InternalRow): String =
record.getString(0)
+ override def getRecordKey(record: InternalRow): String =
record.getString(1)
+ override def getImage(record: InternalRow, ordinal: Int, imageArity: Int):
InternalRow = {
+ if (record.isNullAt(ordinal)) null else record.getStruct(ordinal,
imageArity)
+ }
+ }
+
+ private def createCdcRecordIterator(fileSplit: HoodieCDCFileSplit):
HoodieCDCLogRecordIterator[_] = {
+ if (isNativeCdcFileSplit(fileSplit)) {
+ new HoodieCDCNativeLogRecordIterator[InternalRow](
+ fileSplit.getCdcFiles.iterator(),
+ cdcFile => ClosableIterator.wrap(readNativeCdcFile(cdcFile).asJava),
+ nativeCdcRecordAccessor)
+ } else {
+ val cdcLogFiles = fileSplit.getCdcFiles.asScala.map { cdcFile =>
+ new HoodieLogFile(storage.getPathInfo(new StoragePath(basePath,
cdcFile)))
+ }.toArray
+ new HoodieCDCInlineLogRecordIterator(storage, cdcLogFiles,
cdcHoodieSchema)
+ }
+ }
+
+ private def isNativeCdcFileSplit(fileSplit: HoodieCDCFileSplit): Boolean = {
+ val nativeFlags = fileSplit.getCdcFiles.asScala.map(path =>
FSUtils.matchNativeLogFile(path).isPresent)
+ assert(nativeFlags.forall(_ == nativeFlags.head), "CDC file split cannot
mix inline and native CDC log files")
+ nativeFlags.head
Review Comment:
Fixed in 3d16791c1d49: empty CDC file lists now return
HoodieCDCLogRecordIterator.empty(), and the mixed inline/native guard now uses
ValidationUtils.checkState so it is enforced in production.
##########
hudi-common/src/main/java/org/apache/hudi/common/table/log/HoodieCDCInlineLogRecordIterator.java:
##########
@@ -0,0 +1,169 @@
+/*
+ * 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.common.table.log;
+
+import org.apache.hudi.common.model.HoodieAvroIndexedRecord;
+import org.apache.hudi.common.model.HoodieLogFile;
+import org.apache.hudi.common.model.HoodieRecord.HoodieRecordType;
+import org.apache.hudi.common.schema.HoodieSchema;
+import org.apache.hudi.common.table.log.block.HoodieDataBlock;
+import org.apache.hudi.common.util.collection.ClosableIterator;
+import org.apache.hudi.common.util.collection.CloseableMappingIterator;
+import org.apache.hudi.exception.HoodieIOException;
+import org.apache.hudi.storage.HoodieStorage;
+
+import org.apache.avro.generic.IndexedRecord;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * CDC log record iterator for inline CDC log blocks.
+ */
+public class HoodieCDCInlineLogRecordIterator implements
HoodieCDCLogRecordIterator<IndexedRecord> {
+
+ private final HoodieStorage storage;
+
+ private final HoodieSchema cdcSchema;
+
+ private final Iterator<HoodieLogFile> cdcLogFileIter;
+
+ private HoodieLogFormat.Reader reader;
+
+ private ClosableIterator<HoodieCDCLogRecord<IndexedRecord>> itr;
+
+ private HoodieCDCLogRecord<IndexedRecord> record;
+
+ public HoodieCDCInlineLogRecordIterator(HoodieStorage storage,
HoodieLogFile[] cdcLogFiles, HoodieSchema cdcSchema) {
+ this.storage = storage;
+ this.cdcSchema = cdcSchema;
+ this.cdcLogFileIter = Arrays.stream(cdcLogFiles).iterator();
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (record != null) {
+ return true;
+ }
+ if (itr == null || !itr.hasNext()) {
+ if (reader == null || !reader.hasNext()) {
+ if (!loadReader()) {
+ return false;
+ }
+ }
+ if (!loadItr()) {
+ return false;
+ }
+ }
+ record = itr.next();
+ return true;
+ }
+
+ private boolean loadReader() {
+ try {
+ closeReader();
+ if (cdcLogFileIter.hasNext()) {
+ reader = new HoodieLogFileReader(
+ storage, cdcLogFileIter.next(), cdcSchema,
HoodieLogFileReader.DEFAULT_BUFFER_SIZE);
+ return reader.hasNext();
+ }
+ return false;
+ } catch (IOException e) {
+ throw new HoodieIOException(e.getMessage(), e);
+ }
+ }
+
+ private boolean loadItr() {
+ HoodieDataBlock dataBlock = (HoodieDataBlock) reader.next();
+ closeItr();
+ itr = new CloseableMappingIterator<>(
+ dataBlock.getRecordIterator(HoodieRecordType.AVRO),
+ record -> new InlineCDCLogRecord(((HoodieAvroIndexedRecord) (Object)
record).getData()));
+ return itr.hasNext();
Review Comment:
Fixed in 3d16791c1d49: added a short comment explaining why the cast goes
through Object for the AVRO record iterator.
--
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]