From 2c56424858d7832ba147f3d7dfd8ab07a51a38d1 Mon Sep 17 00:00:00 2001 From: Costas Zarifis Date: Mon, 28 Sep 2026 07:52:20 +0000 Subject: [PATCH] GH-3829: Preserve column path identity when copying row groups --- .../parquet/hadoop/ParquetFileWriter.java | 15 +++++--- .../hadoop/TestParquetWriterAppendBlocks.java | 37 +++++++++++++++++++ 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/parquet-hadoop/src/main/java/org/apache/parquet/hadoop/ParquetFileWriter.java b/parquet-hadoop/src/main/java/org/apache/parquet/hadoop/ParquetFileWriter.java index 82f4577b83..bb6e0e8668 100644 --- a/parquet-hadoop/src/main/java/org/apache/parquet/hadoop/ParquetFileWriter.java +++ b/parquet-hadoop/src/main/java/org/apache/parquet/hadoop/ParquetFileWriter.java @@ -36,6 +36,7 @@ import java.util.Map.Entry; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import java.util.zip.CRC32; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FSDataInputStream; @@ -1702,21 +1703,21 @@ public void appendRowGroup(SeekableInputStream from, BlockMetaData rowGroup, boo withAbortOnFailure(() -> { startBlock(rowGroup.getRowCount()); - Map columnsToCopy = new HashMap(); + Map columnsToCopy = new HashMap(); for (ColumnChunkMetaData chunk : rowGroup.getColumns()) { - columnsToCopy.put(chunk.getPath().toDotString(), chunk); + columnsToCopy.put(chunk.getPath(), chunk); } List columnsInOrder = new ArrayList(); for (ColumnDescriptor descriptor : schema.getColumns()) { - String path = ColumnPath.get(descriptor.getPath()).toDotString(); + ColumnPath path = ColumnPath.get(descriptor.getPath()); ColumnChunkMetaData chunk = columnsToCopy.remove(path); if (chunk != null) { columnsInOrder.add(chunk); } else { - throw new IllegalArgumentException( - String.format("Missing column '%s', cannot copy row group: %s", path, rowGroup)); + throw new IllegalArgumentException(String.format( + "Missing column '%s', cannot copy row group: %s", path.toDotString(), rowGroup)); } } @@ -1724,7 +1725,9 @@ public void appendRowGroup(SeekableInputStream from, BlockMetaData rowGroup, boo if (!dropColumns && !columnsToCopy.isEmpty()) { throw new IllegalArgumentException(String.format( "Columns cannot be copied (missing from target schema): %s", - String.join(", ", columnsToCopy.keySet()))); + columnsToCopy.keySet().stream() + .map(ColumnPath::toDotString) + .collect(Collectors.joining(", ")))); } // copy the data for all chunks diff --git a/parquet-hadoop/src/test/java/org/apache/parquet/hadoop/TestParquetWriterAppendBlocks.java b/parquet-hadoop/src/test/java/org/apache/parquet/hadoop/TestParquetWriterAppendBlocks.java index 3eb6128209..9a9a8e0d8d 100644 --- a/parquet-hadoop/src/test/java/org/apache/parquet/hadoop/TestParquetWriterAppendBlocks.java +++ b/parquet-hadoop/src/test/java/org/apache/parquet/hadoop/TestParquetWriterAppendBlocks.java @@ -140,6 +140,43 @@ public void testBasicBehavior() throws IOException { assertThat(expected).as("All records should be present").isEmpty(); } + @Test + public void testAppendKeepsCollidingDotStringPathsDistinct() throws IOException { + MessageType schema = Types.buildMessage() + .required(BINARY) + .as(UTF8) + .named("a.b") + .requiredGroup() + .required(BINARY) + .as(UTF8) + .named("b") + .named("a") + .named("AppendCollisionTest"); + SimpleGroupFactory factory = new SimpleGroupFactory(schema); + Group expected = factory.newGroup().append("a.b", "top-level"); + expected.addGroup("a").append("b", "nested"); + + Path source = newTemp(); + try (ParquetWriter sourceWriter = + ExampleParquetWriter.builder(source).withType(schema).build()) { + sourceWriter.write(expected); + } + + Path copied = newTemp(); + ParquetFileWriter copyWriter = new ParquetFileWriter(CONF, schema, copied); + copyWriter.start(); + copyWriter.appendFile(CONF, source); + copyWriter.end(EMPTY_METADATA); + + try (ParquetReader reader = + ParquetReader.builder(new GroupReadSupport(), copied).build()) { + Group actual = reader.read(); + assertThat(actual.getString("a.b", 0)).isEqualTo("top-level"); + assertThat(actual.getGroup("a", 0).getString("b", 0)).isEqualTo("nested"); + assertThat(reader.read()).isNull(); + } + } + /** * This test is similar to {@link #testBasicBehavior()} only that it uses static files generated by a previous release * (1.11.1). This test is to validate the fix of PARQUET-2027.