[core] Refactor Variant Parquet reads onto a unified shredding read path#8392
[core] Refactor Variant Parquet reads onto a unified shredding read path#8392lxy-9602 wants to merge 12 commits into
Conversation
48b8d94 to
82dec42
Compare
| } | ||
|
|
||
| @Override | ||
| public ColumnVector[] getChildren() { |
There was a problem hiding this comment.
This vector is still exposed through the vectorized read path: ShreddingFormatReader copies the delegate VectorizedRowIterator, so callers such as ArrowVectorizedBatchConverter can consume the returned batch directly. The Arrow map writer calls mapColumnVector.getChildren()[0/1], so returning null here makes a shared-shredding MAP column fail with an NPE when exported through the vectorized Arrow path. Could we either materialize key/value child vectors for this wrapper or force this reader to fall back to a row iterator for shared-shredding maps?
There was a problem hiding this comment.
Thanks for the suggestion! Updated MapSharedShreddingReadPlan to materialize MAPs columnarly with key/value child vectors. Also added an e2e append read/write test covering shared-shredding MAP values with the supported nested types.
| RowType logicalRowType, GroupType parquetType, boolean caseSensitive) { | ||
| List<DataField> fields = new ArrayList<>(); | ||
| for (DataField field : logicalRowType.getFields()) { | ||
| Type fileType = matchParquetField(parquetType, field.name(), caseSensitive); |
There was a problem hiding this comment.
This read plan is created whenever the requested row type contains any Variant field, but buildPhysicalRowType resolves every top-level field directly from the Parquet file schema. If schema evolution adds a non-Variant column after older files were written, that column is absent from fileSchema; the previous Parquet reader path let clipParquetSchema synthesize a missing column and fill NULLs, while this throws here before requested schema creation. Please preserve the missing-column path, for example by keeping the logical field type when the file field is absent and letting createRequestedSchema mark it missing, and add a test with a Variant column plus a newly added field read from old Parquet files.
There was a problem hiding this comment.
Thanks for pointing this out. I have updated the read plan to be defensive here.
One note: in the normal table read path, schema-evolution missing columns should already be handled before reaching the format reader. FormatReaderMapping builds the actual format read type from the file data schema, so fields absent from an old file are not included in the actualReadRowType passed to Parquet/ORC. Those fields are then represented by indexMapping = -1 and filled as NULL by DataFileRecordReader / VectorMappingUtils.
That said, keeping the missing-field handling in the Variant read plan makes the format-level code more robust and avoids surprises if this plan is used from a more direct format path.
| } | ||
|
|
||
| if (hasSharedShredding) { | ||
| validateMapSharedShreddingFileFormats(options); |
There was a problem hiding this comment.
Schema validation accepts shared-shredding tables with file.compression=snappy or other Parquet/ORC codecs, but MapSharedShreddingWritePlan.fieldMetadata(compression) passes that same codec into normalizeFieldDictCompression, which only allows zstd, lz4, or none. Such a table can be created successfully and then fail only when the writer closes after producing data. Please either validate the relevant data/changelog/per-level compression options here for shared-shredding, or decouple field-dictionary compression from the data-file codec.
There was a problem hiding this comment.
Thanks for the review, fixed now.
| } | ||
|
|
||
| private static void validateNoVariantWithMapSharedShredding(TableSchema schema) { | ||
| if (containsVariantFields(new RowType(schema.fields()))) { |
There was a problem hiding this comment.
The shared-shredding validation only rejects Variant recursively, but BLOB can still be nested in a shredding MAP value because the existing BLOB checks only inspect top-level fields. A schema such as MAP<STRING, BLOB> passes validation and then fails when the read plan creates RowToColumnConverter.createElementConverter(mapType.getValueType()), because BlobType currently throws UnsupportedOperationException. Please reject BLOB recursively for shared-shredding MAP values or implement BLOB materialization, and add a validation test for MAP<STRING, BLOB>.
There was a problem hiding this comment.
Thanks for the review! I’ve updated the validation according to the comments.
| this.keyConverter = | ||
| RowToColumnConverter.createElementConverter(this.mapType.getKeyType()); | ||
| this.valueConverter = | ||
| RowToColumnConverter.createElementConverter(this.mapType.getValueType()); |
There was a problem hiding this comment.
This path allows MAP<STRING, CHAR> values to reach RowToColumnConverter. The shared converter currently handles CharType with row.getByte(...) and WritableByteVector, while ColumnVectorUtils.createWritableColumnVector creates a HeapBytesVector for CHAR (same as VARCHAR). As a result, materializing a shared-shredding MAP whose value type contains CHAR will fail or use the wrong accessor. Please either fix CharType in RowToColumnConverter to use the string/bytes path and add a regression, or reject CHAR in the shared-shredding validation until it is supported.
| if (hasSharedShredding) { | ||
| validateMapSharedShreddingFileFormats(options); | ||
| validateMapSharedShreddingCompressions(options); | ||
| validateNoVariantOrBlobWithMapSharedShredding(schema); |
There was a problem hiding this comment.
This validation still allows shared-shredding maps whose value type contains MULTISET, for example MAP<STRING, MULTISET<INT>>. The write converter can carry that value as an InternalMap, but read materialization later calls RowToColumnConverter.createElementConverter(valueType), and RowToColumnConverter.visit(MultisetType) currently throws UnsupportedOperationException. That means such a table can be created and written but fails when the shared-shredding map is read back. Please either implement multiset conversion in the row-to-column path or reject MultisetType recursively here with the other unsupported types.
There was a problem hiding this comment.
Thanks a lot for your review and time! I learned a lot from the feedback and have made a few more fixes.
|
This PR has become too large, can we split some PRs out. |
Thanks for the detailed review. To make the changes easier to review, I have split the original PR into smaller independent parts:
This should make each part more focused and avoid mixing framework refactoring, validation, and the MAP shared-shredding read/write implementation in one large PR. Would be grateful if you could take another look when you have time. |
Purpose
This is a subtask of MAP shared-shredding support.
Description
This PR introduces a shared shredding read framework to align the read path with the existing shredding write plan abstraction. A
ShreddingReadPlanFactorycan now create a logical-to-physical read plan and a batch assembler, whileShreddingFormatReaderwraps the underlying format reader and converts physical batches back to logical batches before returning rows upstream.This also migrates Parquet Variant reads onto the new read-plan framework. Variant physical schema conversion and batch assembly are moved out of the Parquet column vector internals into
VariantShreddingReadPlanFactory, so Variant follows the same model intended for future MAP shared-shredding support: logical schema -> physical read schema -> physical batch -> logical batch.ShreddingReadPlanFactory,ShreddingReadPlan, andShreddingBatchAssembler.ShreddingFormatReaderto wrap format readers and assemble logical batches from physical batches.SupportsFieldMetadata.Tests
Tests
mvn -pl paimon-format -am -DfailIfNoTests=false clean testmvn -pl paimon-common -am -Pfast-build -DfailIfNoTests=false -Dtest=ShreddingReadPlanFactoriesTest testmvn -pl paimon-arrow -am -Pfast-build -DfailIfNoTests=false -Dtest=ArrowSchemaMetadataCompatibilityTest testmvn -pl paimon-core -am -Pfast-build -DfailIfNoTests=false -Dtest=SchemaEvolutionTest#testAddVariantFieldWithShredding test