This is an automated email from the ASF dual-hosted git repository.

davidzollo pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git


The following commit(s) were added to refs/heads/dev by this push:
     new 8ef362f848 [Fix][Connector-V2] Fix parquet read failure when column 
name contains Avro-illegal characters (#10960)
8ef362f848 is described below

commit 8ef362f84882ebc1c195d9b0ca279d5267710eb4
Author: shown <[email protected]>
AuthorDate: Mon Jun 1 10:36:00 2026 +0800

    [Fix][Connector-V2] Fix parquet read failure when column name contains 
Avro-illegal characters (#10960)
    
    Signed-off-by: yuluo-yx <[email protected]>
---
 .../file/source/reader/ParquetReadStrategy.java    | 427 +++++++++++++++++++++
 .../file/writer/ParquetReadStrategyTest.java       | 158 ++++++++
 2 files changed, 585 insertions(+)

diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/ParquetReadStrategy.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/ParquetReadStrategy.java
index 0b7698b04b..d444dc84f7 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/ParquetReadStrategy.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/ParquetReadStrategy.java
@@ -36,6 +36,7 @@ import 
org.apache.seatunnel.connectors.seatunnel.file.exception.FileConnectorExc
 import 
org.apache.seatunnel.connectors.seatunnel.file.source.split.FileSourceSplit;
 
 import org.apache.avro.Conversions;
+import org.apache.avro.SchemaParseException;
 import org.apache.avro.data.TimeConversions;
 import org.apache.avro.generic.GenericData;
 import org.apache.avro.generic.GenericRecord;
@@ -43,9 +44,11 @@ import org.apache.avro.util.Utf8;
 import org.apache.hadoop.fs.FSDataInputStream;
 import org.apache.hadoop.fs.Path;
 import org.apache.parquet.avro.AvroParquetReader;
+import org.apache.parquet.example.data.Group;
 import org.apache.parquet.example.data.simple.NanoTime;
 import org.apache.parquet.hadoop.ParquetFileReader;
 import org.apache.parquet.hadoop.ParquetReader;
+import org.apache.parquet.hadoop.example.GroupReadSupport;
 import org.apache.parquet.hadoop.metadata.FileMetaData;
 import org.apache.parquet.hadoop.metadata.ParquetMetadata;
 import org.apache.parquet.hadoop.util.HadoopInputFile;
@@ -54,16 +57,19 @@ import org.apache.parquet.schema.GroupType;
 import org.apache.parquet.schema.LogicalTypeAnnotation;
 import org.apache.parquet.schema.MessageType;
 import org.apache.parquet.schema.OriginalType;
+import org.apache.parquet.schema.PrimitiveType;
 import org.apache.parquet.schema.Type;
 
 import lombok.extern.slf4j.Slf4j;
 
 import java.io.IOException;
 import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.math.RoundingMode;
 import java.nio.ByteBuffer;
 import java.sql.Timestamp;
 import java.time.Instant;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.ArrayList;
@@ -96,6 +102,23 @@ public class ParquetReadStrategy extends 
AbstractReadStrategy {
     @Override
     public void read(FileSourceSplit split, Collector<SeaTunnelRow> output)
             throws IOException, FileConnectorException {
+        try {
+            readWithAvro(split, output);
+        } catch (RuntimeException e) {
+            if (!isIllegalAvroFieldNameException(e)) {
+                throw e;
+            }
+            log.warn(
+                    "Failed to read parquet file [{}] with Avro reader due to 
illegal Avro field"
+                            + " name, fallback to native parquet reader",
+                    split.getFilePath(),
+                    e);
+            readWithNativeParquet(split, output);
+        }
+    }
+
+    private void readWithAvro(FileSourceSplit split, Collector<SeaTunnelRow> 
output)
+            throws IOException, FileConnectorException {
         String tableId = split.getTableId();
         String path = split.getFilePath();
         if (Boolean.FALSE.equals(checkFileType(path))) {
@@ -150,6 +173,410 @@ public class ParquetReadStrategy extends 
AbstractReadStrategy {
         }
     }
 
+    private void readWithNativeParquet(FileSourceSplit split, 
Collector<SeaTunnelRow> output)
+            throws IOException {
+        String tableId = split.getTableId();
+        String path = split.getFilePath();
+        Path filePath = new Path(path);
+        Map<String, String> partitionsMap = parsePartitionsByPath(path);
+        int fieldsCount = seaTunnelRowType.getTotalFields();
+        final boolean useSplitRange =
+                enableSplitFile && split.getStart() >= 0 && split.getLength() 
> 0;
+        ParquetReader<Group> reader =
+                hadoopFileSystemProxy.doWithHadoopAuth(
+                        (configuration, userGroupInformation) -> {
+                            ParquetReader.Builder<Group> builder =
+                                    ParquetReader.builder(new 
GroupReadSupport(), filePath)
+                                            .withConf(configuration);
+                            if (useSplitRange) {
+                                long start = split.getStart();
+                                long end = start + split.getLength();
+                                builder.withFileRange(start, end);
+                            }
+                            return builder.build();
+                        });
+        try (ParquetReader<Group> closeableReader = reader) {
+            Group record;
+            while ((record = closeableReader.read()) != null) {
+                Object[] fields;
+                if (isMergePartition) {
+                    int index = fieldsCount;
+                    fields = new Object[fieldsCount + partitionsMap.size()];
+                    for (String value : partitionsMap.values()) {
+                        fields[index++] = value;
+                    }
+                } else {
+                    fields = new Object[fieldsCount];
+                }
+                for (int i = 0; i < fieldsCount; i++) {
+                    fields[i] =
+                            resolveGroupObject(
+                                    record,
+                                    record.getType().getType(indexes[i]),
+                                    indexes[i],
+                                    seaTunnelRowType.getFieldType(i));
+                }
+                SeaTunnelRow seaTunnelRow = new SeaTunnelRow(fields);
+                seaTunnelRow.setTableId(tableId);
+                output.collect(seaTunnelRow);
+            }
+        }
+    }
+
+    private boolean isIllegalAvroFieldNameException(Throwable throwable) {
+        while (throwable != null) {
+            String message = throwable.getMessage();
+            if ((throwable instanceof SchemaParseException
+                            || 
throwable.getClass().getName().endsWith(".SchemaParseException"))
+                    && message != null
+                    && message.contains("Illegal character in")) {
+                return true;
+            }
+            throwable = throwable.getCause();
+        }
+        return false;
+    }
+
+    private Object resolveGroupObject(
+            Group group, Type parquetType, int fieldIndex, 
SeaTunnelDataType<?> fieldType) {
+        if (group.getFieldRepetitionCount(fieldIndex) == 0) {
+            return null;
+        }
+        if (!parquetType.isPrimitive()) {
+            return resolveGroupType(group, parquetType, fieldIndex, fieldType);
+        }
+        Object field = readPrimitiveGroupObject(group, 
parquetType.asPrimitiveType(), fieldIndex);
+        if (field instanceof LocalDateTime || field instanceof LocalDate) {
+            return field;
+        }
+        return resolveObject(field, fieldType);
+    }
+
+    private Object resolveGroupType(
+            Group group, Type parquetType, int fieldIndex, 
SeaTunnelDataType<?> fieldType) {
+        GroupType groupType = parquetType.asGroupType();
+        LogicalTypeAnnotation logicalTypeAnnotation = 
groupType.getLogicalTypeAnnotation();
+        if (logicalTypeAnnotation == null) {
+            SeaTunnelRowType rowType = (SeaTunnelRowType) fieldType;
+            Group childGroup = group.getGroup(fieldIndex, 0);
+            Object[] objects = new Object[rowType.getTotalFields()];
+            for (int i = 0; i < rowType.getTotalFields(); i++) {
+                objects[i] =
+                        resolveGroupObject(
+                                childGroup, groupType.getType(i), i, 
rowType.getFieldType(i));
+            }
+            return new SeaTunnelRow(objects);
+        }
+        OriginalType originalType = logicalTypeAnnotation.toOriginalType();
+        if (originalType == OriginalType.LIST) {
+            return readList(group, groupType, fieldIndex, fieldType);
+        }
+        if (originalType == OriginalType.MAP) {
+            return readMap(group, groupType, fieldIndex, fieldType);
+        }
+        throw CommonError.convertToSeaTunnelTypeError(
+                PARQUET, parquetType.toString(), parquetType.getName());
+    }
+
+    /**
+     * Reads a LIST field from a Parquet Group using the native Group API. 
Handles both the standard
+     * 3-level LIST encoding (group → repeated list → element) and the legacy 
2-level encoding
+     * (group → repeated element directly).
+     */
+    private Object readList(
+            Group group,
+            GroupType parentGroupType,
+            int listFieldIndex,
+            SeaTunnelDataType<?> fieldType) {
+        // Check if the LIST field is present
+        if (group.getFieldRepetitionCount(listFieldIndex) == 0) {
+            return new Object[0];
+        }
+
+        ArrayType<?, ?> arrayType = (ArrayType<?, ?>) fieldType;
+        SeaTunnelDataType<?> elementType = arrayType.getElementType();
+        Group listGroup = group.getGroup(listFieldIndex, 0);
+        GroupType listGroupType = parentGroupType;
+
+        Type repeatedType = listGroupType.getType(0);
+        // Number of repeated elements within the LIST group
+        int numElements = listGroup.getFieldRepetitionCount(0);
+
+        // Determine 3-level vs 2-level LIST encoding.
+        // 3-level: LIST group → REPEATED group (1 field) → element
+        // 2-level: LIST group → REPEATED element directly (legacy)
+        boolean isThreeLevel = false;
+        if (!repeatedType.isPrimitive()) {
+            GroupType repeatedGroupType = repeatedType.asGroupType();
+            LogicalTypeAnnotation repeatedAnnotation = 
repeatedGroupType.getLogicalTypeAnnotation();
+            if (repeatedAnnotation == null
+                    || (repeatedAnnotation.toOriginalType() != 
OriginalType.LIST
+                            && repeatedAnnotation.toOriginalType() != 
OriginalType.MAP)) {
+                if (repeatedGroupType.getFieldCount() == 1) {
+                    isThreeLevel = true;
+                }
+            }
+        }
+
+        List<Object> result = new ArrayList<>(numElements);
+        if (isThreeLevel) {
+            read3LevelList(
+                    listGroup, listGroupType, repeatedType, numElements, 
elementType, result);
+        } else {
+            read2LevelList(
+                    listGroup, repeatedType, numElements, elementType, 
listGroupType, result);
+        }
+        return convertListResult(result, elementType);
+    }
+
+    private Object convertListResult(List<Object> result, SeaTunnelDataType<?> 
elementType) {
+        switch (elementType.getSqlType()) {
+            case STRING:
+                return result.toArray(TYPE_ARRAY_STRING);
+            case BOOLEAN:
+                return result.toArray(TYPE_ARRAY_BOOLEAN);
+            case TINYINT:
+                return result.toArray(TYPE_ARRAY_BYTE);
+            case SMALLINT:
+                return result.toArray(TYPE_ARRAY_SHORT);
+            case INT:
+                return result.toArray(TYPE_ARRAY_INTEGER);
+            case BIGINT:
+                return result.toArray(TYPE_ARRAY_LONG);
+            case FLOAT:
+                return result.toArray(TYPE_ARRAY_FLOAT);
+            case DOUBLE:
+                return result.toArray(TYPE_ARRAY_DOUBLE);
+            case DECIMAL:
+                return result.toArray(TYPE_ARRAY_BIG_DECIMAL);
+            case DATE:
+                return result.toArray(TYPE_ARRAY_LOCAL_DATE);
+            case TIMESTAMP:
+                return result.toArray(TYPE_ARRAY_LOCAL_DATETIME);
+            case BYTES:
+                byte[][] bytesArray = new byte[result.size()][];
+                for (int i = 0; i < result.size(); i++) {
+                    Object element = result.get(i);
+                    if (element instanceof ByteBuffer) {
+                        ByteBuffer buffer = (ByteBuffer) element;
+                        byte[] bytes = new byte[buffer.remaining()];
+                        buffer.get(bytes, 0, bytes.length);
+                        bytesArray[i] = bytes;
+                    } else if (element instanceof byte[]) {
+                        bytesArray[i] = (byte[]) element;
+                    }
+                }
+                return bytesArray;
+            default:
+                return result.toArray(new Object[0]);
+        }
+    }
+
+    /**
+     * Reads elements from a 3-level encoded Parquet LIST. Structure: 
listGroup → REPEATED wrapper
+     * (field 0) → actual element (field 0 within wrapper).
+     */
+    private void read3LevelList(
+            Group listGroup,
+            GroupType listGroupType,
+            Type repeatedType,
+            int numElements,
+            SeaTunnelDataType<?> elementType,
+            List<Object> result) {
+        Type elementTypeInParquet = repeatedType.asGroupType().getType(0);
+        for (int i = 0; i < numElements; i++) {
+            Group repeatedInstance = listGroup.getGroup(0, i);
+            if (repeatedInstance.getFieldRepetitionCount(0) == 0) {
+                result.add(null);
+            } else {
+                result.add(
+                        resolveGroupObject(repeatedInstance, 
elementTypeInParquet, 0, elementType));
+            }
+        }
+    }
+
+    /**
+     * Reads elements from a 2-level encoded Parquet LIST (legacy). Structure: 
listGroup → REPEATED
+     * element directly at field index 0, with each instance accessed by 
repetition index.
+     */
+    private void read2LevelList(
+            Group listGroup,
+            Type repeatedType,
+            int numElements,
+            SeaTunnelDataType<?> elementType,
+            GroupType listGroupType,
+            List<Object> result) {
+        if (repeatedType.isPrimitive()) {
+            // 2-level LIST with primitive repeated elements: read each 
instance by index
+            PrimitiveType primitiveElementType = 
repeatedType.asPrimitiveType();
+            for (int i = 0; i < numElements; i++) {
+                result.add(readPrimitiveGroupObjectByIndex(listGroup, 
primitiveElementType, 0, i));
+            }
+        } else {
+            // 2-level LIST with group repeated elements (e.g., LIST of ROW)
+            GroupType elementGroupType = repeatedType.asGroupType();
+            LogicalTypeAnnotation elemAnnotation = 
elementGroupType.getLogicalTypeAnnotation();
+            if (elemAnnotation != null) {
+                throw CommonError.convertToSeaTunnelTypeError(
+                        PARQUET,
+                        "2-level LIST with annotated group element",
+                        listGroupType.getName());
+            }
+            SeaTunnelRowType rowType = (SeaTunnelRowType) elementType;
+            for (int i = 0; i < numElements; i++) {
+                Group elementGroup = listGroup.getGroup(0, i);
+                Object[] objects = new Object[rowType.getTotalFields()];
+                for (int j = 0; j < rowType.getTotalFields(); j++) {
+                    objects[j] =
+                            resolveGroupObject(
+                                    elementGroup,
+                                    elementGroupType.getType(j),
+                                    j,
+                                    rowType.getFieldType(j));
+                }
+                result.add(new SeaTunnelRow(objects));
+            }
+        }
+    }
+
+    /**
+     * Reads a single primitive value from a Parquet Group at the given field 
index and repetition
+     * index. This is needed for 2-level LIST encoding where repeated 
primitive elements are
+     * accessed by repetition index.
+     */
+    private Object readPrimitiveGroupObjectByIndex(
+            Group group, PrimitiveType parquetType, int fieldIndex, int 
repetitionIndex) {
+        switch (parquetType.getPrimitiveTypeName()) {
+            case BOOLEAN:
+                return group.getBoolean(fieldIndex, repetitionIndex);
+            case INT32:
+                if (parquetType.getOriginalType() == OriginalType.DATE) {
+                    return LocalDate.ofEpochDay(group.getInteger(fieldIndex, 
repetitionIndex));
+                }
+                return group.getInteger(fieldIndex, repetitionIndex);
+            case INT64:
+                return group.getLong(fieldIndex, repetitionIndex);
+            case INT96:
+                return int96ToLocalDateTime(group.getInt96(fieldIndex, 
repetitionIndex));
+            case FLOAT:
+                return group.getFloat(fieldIndex, repetitionIndex);
+            case DOUBLE:
+                return group.getDouble(fieldIndex, repetitionIndex);
+            case BINARY:
+                return readBinaryGroupObject(
+                        group.getBinary(fieldIndex, repetitionIndex), 
parquetType);
+            case FIXED_LEN_BYTE_ARRAY:
+                return readFixedLenByteArrayGroupObject(
+                        group.getBinary(fieldIndex, repetitionIndex), 
parquetType);
+            default:
+                throw CommonError.convertToSeaTunnelTypeError(
+                        PARQUET, parquetType.toString(), 
parquetType.getName());
+        }
+    }
+
+    /**
+     * Reads a MAP field from a Parquet Group using the native Group API. 
Parquet MAP structure:
+     * mapGroup → repeated key_value (field 0) → {key (field 0), value (field 
1)}
+     */
+    private Object readMap(
+            Group group,
+            GroupType parentGroupType,
+            int mapFieldIndex,
+            SeaTunnelDataType<?> fieldType) {
+        MapType<?, ?> mapType = (MapType<?, ?>) fieldType;
+        SeaTunnelDataType<?> keyType = mapType.getKeyType();
+        SeaTunnelDataType<?> valueType = mapType.getValueType();
+
+        if (group.getFieldRepetitionCount(mapFieldIndex) == 0) {
+            return new HashMap<>();
+        }
+
+        Group mapGroup = group.getGroup(mapFieldIndex, 0);
+        GroupType mapGroupType = 
parentGroupType.getType(mapFieldIndex).asGroupType();
+        GroupType keyValueGroupType = mapGroupType.getType(0).asGroupType();
+
+        int numEntries = mapGroup.getFieldRepetitionCount(0);
+        Map<Object, Object> result = new HashMap<>();
+        for (int i = 0; i < numEntries; i++) {
+            Group keyValue = mapGroup.getGroup(0, i);
+            Object key = resolveGroupObject(keyValue, 
keyValueGroupType.getType(0), 0, keyType);
+            Object value = null;
+            if (keyValue.getFieldRepetitionCount(1) > 0) {
+                value = resolveGroupObject(keyValue, 
keyValueGroupType.getType(1), 1, valueType);
+            }
+            result.put(key, value);
+        }
+        return result;
+    }
+
+    private Object readPrimitiveGroupObject(
+            Group group, PrimitiveType parquetType, int fieldIndex) {
+        switch (parquetType.getPrimitiveTypeName()) {
+            case BOOLEAN:
+                return group.getBoolean(fieldIndex, 0);
+            case INT32:
+                if (parquetType.getOriginalType() == OriginalType.DATE) {
+                    return LocalDate.ofEpochDay(group.getInteger(fieldIndex, 
0));
+                }
+                return group.getInteger(fieldIndex, 0);
+            case INT64:
+                return group.getLong(fieldIndex, 0);
+            case INT96:
+                return int96ToLocalDateTime(group.getInt96(fieldIndex, 0));
+            case FLOAT:
+                return group.getFloat(fieldIndex, 0);
+            case DOUBLE:
+                return group.getDouble(fieldIndex, 0);
+            case BINARY:
+                return readBinaryGroupObject(group.getBinary(fieldIndex, 0), 
parquetType);
+            case FIXED_LEN_BYTE_ARRAY:
+                return readFixedLenByteArrayGroupObject(
+                        group.getBinary(fieldIndex, 0), parquetType);
+            default:
+                throw CommonError.convertToSeaTunnelTypeError(
+                        PARQUET, parquetType.toString(), 
parquetType.getName());
+        }
+    }
+
+    private Object readBinaryGroupObject(Binary binary, PrimitiveType 
parquetType) {
+        if (parquetType.getOriginalType() == OriginalType.DECIMAL) {
+            return binaryToDecimal(binary, parquetType);
+        }
+        if (parquetType.getOriginalType() == null) {
+            return binary.toByteBuffer();
+        }
+        return binary.toStringUsingUTF8();
+    }
+
+    private Object readFixedLenByteArrayGroupObject(Binary binary, 
PrimitiveType parquetType) {
+        if (parquetType.getLogicalTypeAnnotation()
+                instanceof LogicalTypeAnnotation.DecimalLogicalTypeAnnotation) 
{
+            return binaryToDecimal(binary, parquetType);
+        }
+        return binary.toByteBuffer();
+    }
+
+    private BigDecimal binaryToDecimal(Binary binary, PrimitiveType 
parquetType) {
+        int scale =
+                parquetType.getLogicalTypeAnnotation()
+                                instanceof 
LogicalTypeAnnotation.DecimalLogicalTypeAnnotation
+                        ? ((LogicalTypeAnnotation.DecimalLogicalTypeAnnotation)
+                                        parquetType.getLogicalTypeAnnotation())
+                                .getScale()
+                        : parquetType.getDecimalMetadata().getScale();
+        return new BigDecimal(new BigInteger(binary.getBytes()), scale);
+    }
+
+    private LocalDateTime int96ToLocalDateTime(Binary binary) {
+        NanoTime nanoTime = NanoTime.fromBinary(binary);
+        int julianDay = nanoTime.getJulianDay();
+        long nanosOfDay = nanoTime.getTimeOfDayNanos();
+        long timestamp =
+                (julianDay - JULIAN_DAY_NUMBER_FOR_UNIX_EPOCH) * MILLIS_PER_DAY
+                        + nanosOfDay / NANOS_PER_MILLISECOND;
+        return new Timestamp(timestamp).toLocalDateTime();
+    }
+
     private Object resolveObject(Object field, SeaTunnelDataType<?> fieldType) 
{
         if (field == null) {
             return null;
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/ParquetReadStrategyTest.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/ParquetReadStrategyTest.java
index 1806d8bfa1..fb9d416bbb 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/ParquetReadStrategyTest.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/ParquetReadStrategyTest.java
@@ -77,6 +77,7 @@ import java.util.Map;
 import java.util.TimeZone;
 
 import static 
org.apache.hadoop.fs.CommonConfigurationKeysPublic.FS_DEFAULT_NAME_DEFAULT;
+import static org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName.BINARY;
 import static org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName.INT32;
 
 @Slf4j
@@ -240,6 +241,65 @@ public class ParquetReadStrategyTest {
         parquetReadStrategy.read(NativeParquetWriter.DATA_FILE_PATH, "", 
testCollector);
     }
 
+    @DisabledOnOs(OS.WINDOWS)
+    @Test
+    public void testParquetReadColumnNameNotCompatibleWithAvro() throws 
Exception {
+        NativeParquetWriterWithAvroIncompatibleColumn.generateTestData();
+        try {
+            ParquetReadStrategy parquetReadStrategy = new 
ParquetReadStrategy();
+            LocalConf localConf = new LocalConf(FS_DEFAULT_NAME_DEFAULT);
+            parquetReadStrategy.init(localConf);
+            SeaTunnelRowType rowType =
+                    parquetReadStrategy.getSeaTunnelRowTypeInfo(
+                            
NativeParquetWriterWithAvroIncompatibleColumn.DATA_FILE_PATH);
+            Assertions.assertEquals("job_blue-collar", 
rowType.getFieldName(1));
+
+            TestCollector testCollector = new TestCollector();
+            parquetReadStrategy.read(
+                    
NativeParquetWriterWithAvroIncompatibleColumn.DATA_FILE_PATH,
+                    "",
+                    testCollector);
+            Assertions.assertEquals(1, testCollector.getRows().size());
+            SeaTunnelRow row = testCollector.getRows().get(0);
+            Assertions.assertEquals(1, row.getField(0));
+            Assertions.assertEquals("engineer", row.getField(1));
+        } finally {
+            NativeParquetWriterWithAvroIncompatibleColumn.deleteFile();
+        }
+    }
+
+    @DisabledOnOs(OS.WINDOWS)
+    @Test
+    public void testParquetReadColumnNameNotCompatibleWithAvroWithList() 
throws Exception {
+        
NativeParquetWriterWithAvroIncompatibleColumnAndList.generateTestData();
+        try {
+            ParquetReadStrategy parquetReadStrategy = new 
ParquetReadStrategy();
+            LocalConf localConf = new LocalConf(FS_DEFAULT_NAME_DEFAULT);
+            parquetReadStrategy.init(localConf);
+            SeaTunnelRowType rowType =
+                    parquetReadStrategy.getSeaTunnelRowTypeInfo(
+                            
NativeParquetWriterWithAvroIncompatibleColumnAndList.DATA_FILE_PATH);
+            Assertions.assertEquals("job_blue-collar", 
rowType.getFieldName(1));
+            Assertions.assertEquals("skill-tags", rowType.getFieldName(2));
+
+            TestCollector testCollector = new TestCollector();
+            parquetReadStrategy.read(
+                    
NativeParquetWriterWithAvroIncompatibleColumnAndList.DATA_FILE_PATH,
+                    "",
+                    testCollector);
+            Assertions.assertEquals(1, testCollector.getRows().size());
+            SeaTunnelRow row = testCollector.getRows().get(0);
+            Assertions.assertEquals(1, row.getField(0));
+            Assertions.assertEquals("engineer", row.getField(1));
+            String[] tags = (String[]) row.getField(2);
+            Assertions.assertEquals(2, tags.length);
+            Assertions.assertEquals("java", tags[0]);
+            Assertions.assertEquals("python", tags[1]);
+        } finally {
+            NativeParquetWriterWithAvroIncompatibleColumnAndList.deleteFile();
+        }
+    }
+
     @DisabledOnOs(OS.WINDOWS)
     @Test
     public void testParquetWithUserConfigRowType() throws Exception {
@@ -932,4 +992,102 @@ public class ParquetReadStrategyTest {
             }
         }
     }
+
+    public static class NativeParquetWriterWithAvroIncompatibleColumn {
+
+        public static final String DATA_FILE_PATH = 
"/tmp/data_invalid_avro_column.parquet";
+
+        public static MessageType createSchema() {
+            return Types.buildMessage()
+                    .required(INT32)
+                    .named("id")
+                    .required(BINARY)
+                    .as(LogicalTypeAnnotation.stringType())
+                    .named("job_blue-collar")
+                    .named("User");
+        }
+
+        public static void generateTestData() throws IOException {
+            deleteFile();
+            MessageType schema = createSchema();
+            Configuration conf = new Configuration();
+            GroupWriteSupport.setSchema(schema, conf);
+
+            Path file = new Path(DATA_FILE_PATH);
+            try (ParquetWriter<Group> writer =
+                    ExampleParquetWriter.builder(file)
+                            .withConf(conf)
+                            .withCompressionCodec(CompressionCodecName.SNAPPY)
+                            .build()) {
+                Group record = new SimpleGroup(schema);
+                record.add("id", 1);
+                record.add("job_blue-collar", "engineer");
+                writer.write(record);
+            }
+        }
+
+        private static void deleteFile() {
+            File parquetFile = new File(DATA_FILE_PATH);
+            if (parquetFile.exists()) {
+                parquetFile.delete();
+            }
+            File checksumFile = new File(DATA_FILE_PATH + ".crc");
+            if (checksumFile.exists()) {
+                checksumFile.delete();
+            }
+        }
+    }
+
+    public static class NativeParquetWriterWithAvroIncompatibleColumnAndList {
+
+        public static final String DATA_FILE_PATH =
+                "/tmp/data_invalid_avro_column_with_list.parquet";
+
+        public static MessageType createSchema() {
+            return Types.buildMessage()
+                    .required(INT32)
+                    .named("id")
+                    .required(BINARY)
+                    .as(LogicalTypeAnnotation.stringType())
+                    .named("job_blue-collar")
+                    .optionalList()
+                    .optionalElement(BINARY)
+                    .as(LogicalTypeAnnotation.stringType())
+                    .named("skill-tags")
+                    .named("User");
+        }
+
+        public static void generateTestData() throws IOException {
+            deleteFile();
+            MessageType schema = createSchema();
+            Configuration conf = new Configuration();
+            GroupWriteSupport.setSchema(schema, conf);
+
+            Path file = new Path(DATA_FILE_PATH);
+            try (ParquetWriter<Group> writer =
+                    ExampleParquetWriter.builder(file)
+                            .withConf(conf)
+                            .withCompressionCodec(CompressionCodecName.SNAPPY)
+                            .build()) {
+                Group record = new SimpleGroup(schema);
+                record.add("id", 1);
+                record.add("job_blue-collar", "engineer");
+                Group listGroup = record.addGroup("skill-tags");
+                listGroup.addGroup(0).append("element", "java");
+                listGroup.addGroup(0).append("element", "python");
+                writer.write(record);
+            }
+        }
+
+        private static void deleteFile() {
+            File parquetFile = new File(DATA_FILE_PATH);
+            if (parquetFile.exists()) {
+                parquetFile.delete();
+            }
+            File checksumFile = new File(DATA_FILE_PATH + ".crc");
+            if (checksumFile.exists()) {
+                checksumFile.delete();
+            }
+        }
+    }
 }

Reply via email to