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 b38601a074 [Fix][Connector-V2] Fix Parquet INT96 mixed-case field
matching (#11067)
b38601a074 is described below
commit b38601a074494c53006ca14cd7b353873e4052a9
Author: Jast <[email protected]>
AuthorDate: Thu Jun 18 08:43:08 2026 +0800
[Fix][Connector-V2] Fix Parquet INT96 mixed-case field matching (#11067)
---
.../file/sink/writer/ParquetWriteStrategy.java | 32 ++++++++++------
.../file/writer/ParquetWriteStrategyTest.java | 44 ++++++++++++++++++++++
2 files changed, 65 insertions(+), 11 deletions(-)
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/ParquetWriteStrategy.java
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/ParquetWriteStrategy.java
index d8ed92d5fb..8537fc688b 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/ParquetWriteStrategy.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/ParquetWriteStrategy.java
@@ -65,9 +65,9 @@ import java.time.temporal.JulianFields;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
-import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
@@ -99,12 +99,15 @@ public class ParquetWriteStrategy extends
AbstractWriteStrategy<ParquetWriter<Ge
public void init(HadoopConf conf, String jobId, String uuidPrefix, int
subTaskIndex) {
super.init(conf, jobId, uuidPrefix, subTaskIndex);
Configuration configuration = getConfiguration(hadoopConf);
- writePathsAsInt96 = new
HashSet<>(fileSinkConfig.getParquetAvroWriteFixedAsInt96());
+ writePathsAsInt96 =
+ fileSinkConfig.getParquetAvroWriteFixedAsInt96().stream()
+ .map(this::normalizeFieldName)
+ .collect(Collectors.toSet());
if (fileSinkConfig.getParquetWriteTimestampAsInt96()) {
List<String> timestampFields = new ArrayList<>();
for (int i = 0; i < seaTunnelRowType.getTotalFields(); i++) {
if
(SqlType.TIMESTAMP.equals(seaTunnelRowType.getFieldType(i).getSqlType())) {
- timestampFields.add(seaTunnelRowType.getFieldName(i));
+
timestampFields.add(normalizeFieldName(seaTunnelRowType.getFieldName(i)));
}
}
writePathsAsInt96.addAll(timestampFields);
@@ -244,7 +247,7 @@ public class ParquetWriteStrategy extends
AbstractWriteStrategy<ParquetWriter<Ge
case DATE:
return data;
case TIMESTAMP:
- if (writePathsAsInt96.contains(name)) {
+ if (writePathsAsInt96.contains(normalizeFieldName(name))) {
LocalDateTime localDateTime = (LocalDateTime) data;
Calendar calendar =
Calendar.getInstance(TimeZone.getTimeZone("UTC"));
calendar.setTime(
@@ -264,7 +267,7 @@ public class ParquetWriteStrategy extends
AbstractWriteStrategy<ParquetWriter<Ge
calendar.get(Calendar.MILLISECOND));
NanoTime nanoTime = new NanoTime(julianDays,
timeOfDayNanos);
return new GenericData.Fixed(
- schema.getField(name.toLowerCase()).schema(),
+ schema.getField(normalizeFieldName(name)).schema(),
nanoTime.toBinary().getBytes());
}
return ((LocalDateTime) data)
@@ -272,9 +275,9 @@ public class ParquetWriteStrategy extends
AbstractWriteStrategy<ParquetWriter<Ge
.toInstant()
.toEpochMilli();
case BYTES:
- if (writePathsAsInt96.contains(name)) {
+ if (writePathsAsInt96.contains(normalizeFieldName(name))) {
return new GenericData.Fixed(
- schema.getField(name.toLowerCase()).schema(),
(byte[]) data);
+
schema.getField(normalizeFieldName(name)).schema(), (byte[]) data);
}
return ByteBuffer.wrap((byte[]) data);
case ROW:
@@ -365,7 +368,7 @@ public class ParquetWriteStrategy extends
AbstractWriteStrategy<ParquetWriter<Ge
PrimitiveType.PrimitiveTypeName.INT64,
Type.Repetition.OPTIONAL)
.named(fieldName);
case TIMESTAMP:
- if (writePathsAsInt96.contains(fieldName)) {
+ if (writePathsAsInt96.contains(normalizeFieldName(fieldName)))
{
return Types.primitive(
PrimitiveType.PrimitiveTypeName.INT96,
Type.Repetition.OPTIONAL)
.named(fieldName);
@@ -392,7 +395,7 @@ public class ParquetWriteStrategy extends
AbstractWriteStrategy<ParquetWriter<Ge
.scale(scale)
.named(fieldName);
case BYTES:
- if (writePathsAsInt96.contains(fieldName)) {
+ if (writePathsAsInt96.contains(normalizeFieldName(fieldName)))
{
return Types.primitive(
PrimitiveType.PrimitiveTypeName.FIXED_LEN_BYTE_ARRAY,
Type.Repetition.OPTIONAL)
@@ -430,10 +433,13 @@ public class ParquetWriteStrategy extends
AbstractWriteStrategy<ParquetWriter<Ge
// are included. Without this, new TIMESTAMP columns get INT64
encoding instead of INT96
// when parquetWriteTimestampAsInt96=true — silent data corruption.
if (fileSinkConfig.getParquetWriteTimestampAsInt96()) {
- writePathsAsInt96 = new
HashSet<>(fileSinkConfig.getParquetAvroWriteFixedAsInt96());
+ writePathsAsInt96 =
+ fileSinkConfig.getParquetAvroWriteFixedAsInt96().stream()
+ .map(this::normalizeFieldName)
+ .collect(Collectors.toSet());
for (int i = 0; i < seaTunnelRowType.getTotalFields(); i++) {
if
(SqlType.TIMESTAMP.equals(seaTunnelRowType.getFieldType(i).getSqlType())) {
- writePathsAsInt96.add(seaTunnelRowType.getFieldName(i));
+
writePathsAsInt96.add(normalizeFieldName(seaTunnelRowType.getFieldName(i)));
}
}
}
@@ -457,4 +463,8 @@ public class ParquetWriteStrategy extends
AbstractWriteStrategy<ParquetWriter<Ge
Types.buildMessage().addFields(types.toArray(new
Type[0])).named("SeaTunnelRecord");
return schemaConverter.convert(seaTunnelRow);
}
+
+ private String normalizeFieldName(String fieldName) {
+ return fieldName.toLowerCase(Locale.ROOT);
+ }
}
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/ParquetWriteStrategyTest.java
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/ParquetWriteStrategyTest.java
index f24def6288..eea84414c9 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/ParquetWriteStrategyTest.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/ParquetWriteStrategyTest.java
@@ -149,4 +149,48 @@ public class ParquetWriteStrategyTest {
Assertions.assertEquals(1, readRows.size());
readStrategy.close();
}
+
+ @DisabledOnOs(OS.WINDOWS)
+ @Test
+ public void testParquetWriteInt96WithMixedCaseTimestampColumn() throws
Exception {
+ Map<String, Object> writeConfig = new HashMap<>();
+ writeConfig.put("tmp_path", TMP_PATH + "-mixed-case");
+ writeConfig.put("path",
"file:///tmp/seatunnel/parquet/int96-mixed-case");
+ writeConfig.put("file_format_type", FileFormat.PARQUET.name());
+ writeConfig.put("parquet_avro_write_timestamp_as_int96", "true");
+
+ SeaTunnelRowType writeRowType =
+ new SeaTunnelRowType(
+ new String[] {"createTime"},
+ new SeaTunnelDataType[]
{LocalTimeType.LOCAL_DATE_TIME_TYPE});
+ FileSinkConfig writeSinkConfig =
+ new FileSinkConfig(ReadonlyConfig.fromMap(writeConfig),
writeRowType);
+ ParquetWriteStrategy writeStrategy = new
ParquetWriteStrategy(writeSinkConfig);
+ LocalFileSystemConf.LocalConf hadoopConf =
+ new LocalFileSystemConf.LocalConf(FS_DEFAULT_NAME_DEFAULT);
+ writeStrategy.setCatalogTable(
+ CatalogTableUtil.getCatalogTable("test", null, null, "test",
writeRowType));
+ writeStrategy.init(hadoopConf, "test1", "test1", 0);
+ writeStrategy.beginTransaction(1L);
+ writeStrategy.write(new SeaTunnelRow(new Object[]
{LocalDateTime.now()}));
+ writeStrategy.finishAndCloseFile();
+ writeStrategy.close();
+
+ ParquetReadStrategy readStrategy = new ParquetReadStrategy();
+ readStrategy.init(hadoopConf);
+ List<String> readFiles = readStrategy.getFileNamesByPath(TMP_PATH +
"-mixed-case");
+ Assertions.assertEquals(1, readFiles.size());
+ try (ParquetFileReader reader =
+ ParquetFileReader.open(
+ HadoopInputFile.fromPath(
+ new
org.apache.hadoop.fs.Path(readFiles.get(0)),
+ new Configuration()))) {
+ FileMetaData metadata = reader.getFooter().getFileMetaData();
+ Type createTimeType = metadata.getSchema().getType("createtime");
+ Assertions.assertEquals(
+ PrimitiveType.PrimitiveTypeName.INT96,
+ createTimeType.asPrimitiveType().getPrimitiveTypeName());
+ }
+ readStrategy.close();
+ }
}