SteNicholas commented on code in PR #257:
URL: https://github.com/apache/paimon-cpp/pull/257#discussion_r3892509452
##########
src/paimon/common/utils/arrow/arrow_utils.cpp:
##########
@@ -484,4 +536,95 @@ Result<arrow::Compression::type>
ArrowUtils::GetCompressionType(const std::strin
return compression_type;
}
+bool ArrowUtils::IsParquetDictionaryValueType(const arrow::DataType& type) {
+ return type.id() == arrow::Type::STRING || type.id() ==
arrow::Type::BINARY;
+}
Review Comment:
You were right that the name over-promised, and chasing it turned up
something worse, so this ended up as two predicates rather than one rename.
`ArrowUtils` is a generic utility, so it should not know about Parquet —
that part is now `IsDictionaryLayoutRecoverableValueType()`, and it answers
only "does `dictionary(int32, T)` survive the C data interface". It accepts
`utf8` and `binary`, which is what the writer can recover and matches Velox's
VARCHAR/VARBINARY. `large_utf8` stays out because a layout reports neither the
index nor the offset width.
I did not go all the way to `IsBinaryType`, and the reason is the second
predicate. While looking at this I found that no value accessor in the
repository can read a `dictionary(int32, binary)`: `ColumnarUtils::GetView()`
asserts on a dictionary whose values are neither `STRING` nor `LARGE_STRING`
and returns an empty `string_view` in a release build, `LiteralConverter`
rejects it, and `ColumnarBatchContext`'s fast path routes dictionaries back to
`GetView()`. Every consumer here has only ever seen `STRING` dictionaries,
because that is all the ORC reader produces under lazy decoding. Since the
option is a *read* option and applies to every read of the table, that was a
silent-empty-value path.
So the reader now forwards `STRING` alone, and it enforces that at the
request site (`ResolveFullyDictionaryEncodedColumns()` requires
`logical_type()->is_string()`, not just `BYTE_ARRAY`) — narrowing only the
reporting side would have left Arrow emitting a `DictionaryArray` for a column
the reader describes as `BINARY`.
`ParquetFileBatchReaderTest.TestDictionaryPassthroughSkipsBinaryColumn` pins it
with `f8`/`STRING` as the control, and
`ParquetFormatWriterTest.TestWriteDictionaryOfBinaryColumn` pins that the
writer keeps the `BINARY` capability so the reader's restriction does not leak
into the format layer.
A name like `IsBinaryType` would have invited someone to add `LARGE_STRING`
later — reasonable for a general type predicate, silently wrong here.
##########
src/paimon/common/utils/arrow/arrow_utils.cpp:
##########
@@ -484,4 +536,95 @@ Result<arrow::Compression::type>
ArrowUtils::GetCompressionType(const std::strin
return compression_type;
}
+bool ArrowUtils::IsParquetDictionaryValueType(const arrow::DataType& type) {
+ return type.id() == arrow::Type::STRING || type.id() ==
arrow::Type::BINARY;
+}
+
+Result<std::shared_ptr<arrow::DataType>>
ArrowUtils::ResolveParquetDictionaryStructType(
+ const std::shared_ptr<arrow::DataType>& logical_type, const ::ArrowArray*
batch) {
+ if (batch == nullptr || logical_type->id() != arrow::Type::STRUCT ||
+ batch->n_children != logical_type->num_fields()) {
+ // Leave the mismatch to the import, which reports it with its own
diagnostics.
+ return logical_type;
+ }
+ arrow::FieldVector fields;
+ bool has_dictionary = false;
+ for (int32_t i = 0; i < logical_type->num_fields(); ++i) {
+ const std::shared_ptr<arrow::Field>& field = logical_type->field(i);
+ const ::ArrowArray* child = batch->children[i];
+ if (child == nullptr || child->dictionary == nullptr) {
+ if (HasUndeclaredDictionaryChild(field->type(), child)) {
+ return Status::NotImplemented(fmt::format(
+ "column '{}' is dictionary-encoded below its top level,
which the Arrow "
+ "import cannot describe without the producer's schema",
+ field->name()));
+ }
+ fields.push_back(field);
+ continue;
+ }
+ if (field->type()->id() == arrow::Type::DICTIONARY) {
+ // The caller already declares the column as a dictionary, so its
type describes the
+ // batch and nothing has to be recovered from the layout.
+ fields.push_back(field);
+ continue;
+ }
+ if (!IsParquetDictionaryValueType(*field->type())) {
+ return Status::NotImplemented(fmt::format(
+ "dictionary-encoded column '{}' of type {} cannot be resolved
from the layout of "
+ "an ArrowArray, which pins down neither the index nor the
offset width",
+ field->name(), field->type()->ToString()));
+ }
+ has_dictionary = true;
+ fields.push_back(field->WithType(arrow::dictionary(arrow::int32(),
field->type())));
+ }
+ if (!has_dictionary) {
+ return logical_type;
+ }
+ return arrow::struct_(fields);
+}
+
+Result<std::shared_ptr<arrow::StructArray>>
ArrowUtils::FlattenUnresolvableDictionaries(
+ const std::shared_ptr<arrow::StructArray>& batch,
+ const std::shared_ptr<arrow::DataType>& logical_type, arrow::MemoryPool*
pool) {
+ const std::shared_ptr<arrow::DataType>& batch_type = batch->type();
+ if (logical_type->id() != arrow::Type::STRUCT ||
!HasDictionary(*batch_type)) {
+ return batch;
+ }
+ const auto& logical_struct_type = checked_cast<const
arrow::StructType&>(*logical_type);
+ arrow::compute::ExecContext exec_context(pool);
+ std::shared_ptr<arrow::ArrayData> data;
+ arrow::FieldVector fields = batch_type->fields();
+ for (int32_t i = 0; i < batch_type->num_fields(); ++i) {
+ std::shared_ptr<arrow::Field> field = fields[i];
+ if (IsResolvableDictionary(*field->type()) ||
!HasDictionary(*field->type())) {
+ continue;
+ }
+ std::shared_ptr<arrow::Field> logical_field =
+ logical_struct_type.GetFieldByName(field->name());
+ if (logical_field == nullptr) {
+ // Nothing says what this column should decode to, so leave it for
the import to
+ // report against its own schema.
+ continue;
+ }
+ if (data == nullptr) {
+ // Copy once, on the first column that has to be decoded: the
parent keeps its offset,
+ // length and validity, and only the child data is swapped
underneath it.
+ data = batch->data()->Copy();
+ }
+ // Decode the whole child rather than the slice the parent exposes, so
the replacement
+ // lines up with the offset and length the parent still carries.
+ PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(
+ arrow::Datum decoded,
+ arrow::compute::Cast(arrow::MakeArray(data->child_data[i]),
logical_field->type(),
+ arrow::compute::CastOptions::Safe(),
&exec_context));
Review Comment:
Done — `CastingUtils::Cast()` here and in
`ParquetFormatWriter::FlattenUnwritableDictionaries()`. It also drops the
hand-built `arrow::compute::ExecContext` and the `arrow/compute/exec.h`
include, since it takes the pool directly.
##########
src/paimon/core/io/data_file_index_writer.cpp:
##########
@@ -100,10 +104,40 @@ Status DataFileIndexWriter::AddBatch(const
std::shared_ptr<arrow::StructArray>&
if (finished_) {
return Status::Invalid("Data file index writer has already finished");
}
+ // Buffers allocated through the adaptor keep a raw pointer to it, so it
has to outlive every
+ // array decoded below. Built on first use, since most batches decode
nothing.
+ std::unique_ptr<arrow::MemoryPool> arrow_pool;
+ // One entry per indexed column, not per index: a column carrying both a
bitmap and a bloom
Review Comment:
Good catch, and yes. `arrow_pool_` is now a member of `DataFileIndexWriter`,
built on first use since most batches decode nothing.
Two details worth naming, since the hazard is easy to reintroduce. It is
declared *before* `writers_`, so it is destroyed last: buffers allocated
through the adaptor keep a raw pointer to it, and an index writer may still
hold a decoded column. And the destructor is now out of line, so the header
only needs a forward declaration of `arrow::MemoryPool`.
`DataFileIndexWriterTest.TestDictionaryEncodedIndexedColumnRoundTrip` now
feeds two batches with different dictionaries and asserts the bitmap over both,
so the pool has to survive across calls and each batch has to be decoded
against its own alphabet.
--
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]