16#include <TBufferFile.h>
18#include <arrow/buffer.h>
19#include <arrow/dataset/file_base.h>
20#include <arrow/extension_type.h>
21#include <arrow/memory_pool.h>
22#include <arrow/status.h>
23#include <arrow/type.h>
24#include <arrow/type_fwd.h>
25#include <arrow/util/key_value_metadata.h>
26#include <arrow/array/array_nested.h>
27#include <arrow/array/array_primitive.h>
28#include <arrow/array/builder_nested.h>
29#include <arrow/array/builder_primitive.h>
30#include <arrow/array/util.h>
31#include <arrow/record_batch.h>
74 const std::shared_ptr<arrow::ResizableBuffer>&
buffer);
82 static arrow::Result<std::shared_ptr<TTreeDeferredReadOutputStream>>
Create(
83 std::vector<ReadOps>& ops,
84 int64_t initial_capacity = 4096,
85 arrow::MemoryPool* pool = arrow::default_memory_pool());
95 arrow::Status
Close()
override;
96 [[nodiscard]]
bool closed()
const override;
97 [[nodiscard]] arrow::Result<int64_t>
Tell()
const override;
98 arrow::Status
Write(
const void*
data, int64_t nbytes)
override;
101 using OutputStream::Write;
105 arrow::Result<std::shared_ptr<arrow::Buffer>>
Finish();
112 arrow::Status
Reset(std::vector<ReadOps> ops,
113 int64_t initial_capacity, arrow::MemoryPool* pool);
115 [[nodiscard]] int64_t
capacity()
const {
return capacity_; }
119 std::vector<ReadOps> ops_;
122 arrow::Status Reserve(int64_t nbytes);
124 std::shared_ptr<arrow::ResizableBuffer> buffer_;
128 uint8_t* mutable_data_;
131static constexpr int64_t kBufferMinimumSize = 256;
133TTreeDeferredReadOutputStream::TTreeDeferredReadOutputStream()
134 : is_open_(false), capacity_(0), position_(0), mutable_data_(nullptr) {}
136TTreeDeferredReadOutputStream::TTreeDeferredReadOutputStream(std::vector<ReadOps>& ops,
137 const std::shared_ptr<arrow::ResizableBuffer>&
buffer)
143 mutable_data_(
buffer->mutable_data()) {}
146 std::vector<ReadOps>& ops,
147 int64_t initial_capacity, arrow::MemoryPool* pool)
151 RETURN_NOT_OK(
ptr->Reset(ops, initial_capacity, pool));
156 int64_t initial_capacity, arrow::MemoryPool* pool)
158 ARROW_ASSIGN_OR_RAISE(buffer_, AllocateResizableBuffer(initial_capacity, pool));
161 capacity_ = initial_capacity;
163 mutable_data_ = buffer_->mutable_data();
164 return arrow::Status::OK();
171 if (position_ < capacity_) {
172 RETURN_NOT_OK(buffer_->Resize(position_,
false));
175 return arrow::Status::OK();
182 RETURN_NOT_OK(
Close());
183 buffer_->ZeroPadding();
185 return std::move(buffer_);
193 throw runtime_error_f(
"Error while reading branch %s starting from %d: got %d entries.",
op.branch->GetName(), readEntries, readLast);
195 if (
static_cast<int64_t
>(readEntries) + readLast >
op.rootBranchEntries) {
196 throw runtime_error_f(
"Invalid read range for branch %s: starting from %d, read %d entries, total entries %lld.",
197 op.branch->GetName(), readEntries, readLast,
static_cast<long long>(
op.rootBranchEntries));
203 if (bytesNeeded < 0 || bytesNeeded > available) {
204 throw runtime_error_f(
"Basket of branch %s starting from %d holds %lld bytes, but %lld are needed.",
205 op.branch->GetName(), readEntries,
static_cast<long long>(available),
static_cast<long long>(bytesNeeded));
212 while (readEntries <
op.rootBranchEntries) {
213 auto readLast =
op.branch->GetBulkRead().GetEntriesSerialized(readEntries,
rootBuffer);
215 int size = readLast *
op.listSize;
217 readEntries += readLast;
227 memset(
target, 0,
op.targetBuffer->size());
229 while (readEntries <
op.rootBranchEntries) {
230 auto beginValue = readEntries;
231 readLast =
op.branch->GetBulkRead().GetBulkEntries(readEntries,
rootBuffer);
233 int size = readLast *
op.listSize;
235 readEntries += readLast;
236 for (
int i = beginValue;
i < beginValue +
size; ++
i) {
237 auto value =
static_cast<uint8_t
>(
rootBuffer.GetCurrent()[
i - beginValue] << (
i % 8));
246 if (
op.rootBranchEntries != offsetOp.rootBranchEntries) {
247 throw runtime_error_f(
"Branch %s has %lld entries, but its size branch %s has %lld.",
248 op.branch->GetName(),
static_cast<long long>(
op.rootBranchEntries),
249 offsetOp.branch->GetName(),
static_cast<long long>(offsetOp.rootBranchEntries));
251 auto* tPtrOffset =
reinterpret_cast<const int*
>(offsetOp.targetBuffer->data());
252 std::span<int const>
const offsets{tPtrOffset, tPtrOffset + offsetOp.rootBranchEntries + 1};
255 while (readEntries <
op.rootBranchEntries) {
256 auto readLast =
op.branch->GetBulkRead().GetEntriesSerialized(readEntries,
rootBuffer);
260 throw runtime_error_f(
"Invalid offset range for branch %s: offsets[%d]=%d, offsets[%d]=%d.",
261 op.branch->GetName(), readEntries,
offsets[readEntries], readEntries + readLast,
offsets[readEntries + readLast]);
264 readEntries += readLast;
279 if (ARROW_PREDICT_FALSE(!is_open_)) {
280 return arrow::Status::IOError(
"OutputStream is closed");
282 if (ARROW_PREDICT_TRUE(nbytes == 0)) {
283 return arrow::Status::OK();
285 if (ARROW_PREDICT_FALSE(position_ + nbytes >= capacity_)) {
286 RETURN_NOT_OK(Reserve(nbytes));
290 if (
ref >= ops_.size()) {
291 memcpy(mutable_data_ + position_,
data, nbytes);
293 return arrow::Status::OK();
295 auto&
op = ops_[
ref];
314 op.branch->SetStatus(
false);
315 op.branch->DropBaskets(
"all");
317 op.branch->GetTransientBuffer(0)->Expand(0);
320 return arrow::Status::OK();
323arrow::Status TTreeDeferredReadOutputStream::Reserve(int64_t nbytes)
329 int64_t new_capacity = std::max(kBufferMinimumSize, capacity_);
330 new_capacity = position_ + nbytes;
331 if (new_capacity > capacity_) {
332 RETURN_NOT_OK(buffer_->Resize(new_capacity));
333 capacity_ = new_capacity;
334 mutable_data_ = buffer_->mutable_data();
336 return arrow::Status::OK();
343 : FileWriteOptions(
format)
355 const std::string&
path,
356 const std::shared_ptr<const arrow::KeyValueMetadata>& metadata)
override;
358 virtual std::unique_ptr<TTree>&
GetTree(arrow::dataset::FileSource
source) = 0;
363 size_t& mTotCompressedSize;
364 size_t& mTotUncompressedSize;
369 mTotCompressedSize(totalCompressedSize),
370 mTotUncompressedSize(totalUncompressedSize)
388 auto fs = std::dynamic_pointer_cast<VirtualRootFileSystemBase>(
source.filesystem());
392 return fs->CheckSupport(
source);
395 arrow::Result<std::shared_ptr<arrow::Schema>>
Inspect(
const arrow::dataset::FileSource&
source)
const override;
397 arrow::Result<std::shared_ptr<arrow::dataset::FileFragment>>
MakeFragment(
398 arrow::dataset::FileSource
source, arrow::compute::Expression partition_expression,
399 std::shared_ptr<arrow::Schema> physical_schema)
override;
401 arrow::Result<std::shared_ptr<arrow::dataset::FileWriter>>
MakeWriter(std::shared_ptr<arrow::io::OutputStream> destination, std::shared_ptr<arrow::Schema>
schema, std::shared_ptr<arrow::dataset::FileWriteOptions> options, arrow::fs::FileLocator destination_locator)
const override;
406 const std::shared_ptr<arrow::dataset::ScanOptions>& options,
407 const std::shared_ptr<arrow::dataset::FileFragment>& fragment)
const override;
415 mTotUncompressedSize(totalUncompressedSize),
416 mTotCompressedSize(totalCompressedSize),
421 arrow::Result<arrow::fs::FileInfo>
GetFileInfo(std::string
const&
path)
override;
430 return std::make_shared<RootObjectHandler>((
void*)mTree.get(), std::make_shared<TTreeFileFormat>(mTotCompressedSize, mTotUncompressedSize));
433 std::unique_ptr<TTree>&
GetTree(arrow::dataset::FileSource)
override
443 size_t& mTotUncompressedSize;
444 size_t& mTotCompressedSize;
445 std::unique_ptr<TTree> mTree;
450 arrow::dataset::FileSource
source(
path, shared_from_this());
451 arrow::fs::FileInfo
result;
453 result.set_type(arrow::fs::FileType::File);
462 std::shared_ptr<arrow::dataset::FileFormat>
format,
463 arrow::compute::Expression partition_expression,
464 std::shared_ptr<arrow::Schema> physical_schema)
465 : FileFragment(
source,
format,
std::move(partition_expression), physical_schema)
467 auto rootFS = std::dynamic_pointer_cast<VirtualRootFileSystemBase>(this->
source().filesystem());
468 if (rootFS.get() ==
nullptr) {
470 source.filesystem()->type_name().c_str(),
source.path().c_str());
472 auto objectHandler = rootFS->GetObjectHandler(
source);
473 if (!objectHandler->format->Equals(*
format)) {
474 throw runtime_error_f(
"Cannot read source %s with format %s to pupulate a TTreeFileFragment.",
475 source.path().c_str(), objectHandler->format->type_name().c_str());
477 mTree = objectHandler->GetObjectAsOwner<TTree>();
485 std::vector<ReadOps>&
ops()
494 return std::make_shared<arrow::Buffer>((uint8_t*)(mOps.size() - 1),
size);
498 std::unique_ptr<TTree> mTree;
499 std::vector<ReadOps> mOps;
511 arrow::Status
Close()
override;
513 arrow::Result<int64_t>
Tell()
const override;
515 arrow::Status
Write(
const void*
data, int64_t nbytes)
override;
517 bool closed()
const override;
519 TBranch*
CreateBranch(
char const* branchName,
char const* sizeBranch);
528 std::string mBranchPrefix;
536 mBranchPrefix(
std::move(branchPrefix))
542 if (mTree->GetCurrentFile() ==
nullptr) {
543 return arrow::Status::Invalid(
"Cannot close a tree not attached to a file");
545 mTree->GetCurrentFile()->Close();
546 return arrow::Status::OK();
551 return arrow::Result<int64_t>(arrow::Status::NotImplemented(
"Cannot move"));
556 return arrow::Status::NotImplemented(
"Cannot write raw bytes to a TTree");
562 if (mTree->GetCurrentFile() ==
nullptr) {
565 return mTree->GetCurrentFile()->IsOpen() ==
false;
570 if (mBranchPrefix.empty() ==
true) {
571 return mTree->Branch(branchName, (
char*)
nullptr, sizeBranch);
573 return mTree->Branch((mBranchPrefix +
"/" + branchName).c_str(), (
char*)
nullptr, (mBranchPrefix + sizeBranch).c_str());
579 std::shared_ptr<o2::framework::TTreeFileFormat>
format =
nullptr;
586 context->
format = std::make_shared<o2::framework::TTreeFileFormat>(context->totalCompressedSize, context->totalUncompressedSize);
588 .
options = [context]() {
return context->format->DefaultWriteOptions(); },
589 .format = [context]() {
return context->format; },
590 .deferredOutputStreamer = [](std::shared_ptr<arrow::dataset::FileFragment> fragment,
const std::shared_ptr<arrow::ResizableBuffer>&
buffer) -> std::shared_ptr<arrow::io::OutputStream> {
591 auto treeFragment = std::dynamic_pointer_cast<TTreeFileFragment>(fragment);
592 return std::make_shared<TTreeDeferredReadOutputStream>(treeFragment->ops(),
buffer);
608 auto* tPtrOffset =
reinterpret_cast<int*
>(
op.targetBuffer->mutable_data());
609 offsets = std::span<int>{tPtrOffset, tPtrOffset +
op.rootBranchEntries + 1};
613 while (readEntries <
op.rootBranchEntries) {
614 auto readLast =
op.branch->GetBulkRead().GetEntriesSerialized(readEntries,
rootBuffer);
617 readEntries += readLast;
618 for (
auto i = 0;
i < readLast; ++
i) {
620 uint32_t
raw =
reinterpret_cast<uint32_t*
>(
rootBuffer.GetCurrent())[
i];
621 offset += (std::endian::native == std::endian::little) ? __builtin_bswap32(
raw) :
raw;
624 throw runtime_error_f(
"Invalid sizes for branch %s: offsets overflow at entry %d.",
op.branch->GetName(),
count - 1);
633 const std::shared_ptr<arrow::dataset::ScanOptions>& options,
634 const std::shared_ptr<arrow::dataset::FileFragment>& fragment)
const
636 assert(options->dataset_schema !=
nullptr);
638 auto dataset_schema = options->dataset_schema;
639 auto treeFragment = std::dynamic_pointer_cast<TTreeFileFragment>(fragment);
640 if (treeFragment.get() ==
nullptr) {
641 return {arrow::Status::NotImplemented(
"Not a ttree fragment")};
644 auto generator = [pool = options->pool, treeFragment, dataset_schema, &totalCompressedSize = mTotCompressedSize,
645 &totalUncompressedSize = mTotUncompressedSize]() -> arrow::Future<std::shared_ptr<arrow::RecordBatch>> {
647 O2_SIGNPOST_START(root_arrow_fs, tid,
"Generator",
"Creating batch for tree %{public}s", treeFragment->GetTree()->GetName());
648 std::vector<std::shared_ptr<arrow::Array>> columns;
649 std::vector<std::shared_ptr<arrow::Field>>
fields = dataset_schema->fields();
650 auto physical_schema = *treeFragment->ReadPhysicalSchema();
652 if (dataset_schema->num_fields() > physical_schema->num_fields()) {
653 throw runtime_error_f(
"One TTree must have all the fields requested in a table");
657 std::vector<BranchFieldMapping> mappings;
661 for (
int fi = 0; fi < dataset_schema->num_fields(); ++fi) {
662 auto dataset_field = dataset_schema->field(fi);
665 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid,
"Generator",
"Processing dataset field %{public}s.", dataset_field->name().c_str());
666 int physicalFieldIdx = physical_schema->GetFieldIndex(dataset_field->name());
668 if (physicalFieldIdx < 0) {
669 throw runtime_error_f(
"Cannot find physical field associated to %s. Possible fields: %s",
670 dataset_field->name().c_str(), physical_schema->ToString().c_str());
672 if (physicalFieldIdx > 0 && physical_schema->field(physicalFieldIdx - 1)->name().ends_with(
"_size")) {
673 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid,
"Generator",
"Field %{public}s has sizes in %{public}s.", dataset_field->name().c_str(),
674 physical_schema->field(physicalFieldIdx - 1)->name().c_str());
675 mappings.push_back({physicalFieldIdx, physicalFieldIdx - 1, fi});
678 if (physicalFieldIdx > 0) {
679 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid,
"Generator",
"Field %{public}s previous field is %{public}s.", dataset_field->name().c_str(),
680 physical_schema->field(physicalFieldIdx - 1)->name().c_str());
682 mappings.push_back({physicalFieldIdx, -1, fi});
687 auto*
tree = treeFragment->GetTree();
688 auto branches =
tree->GetListOfBranches();
689 size_t totalTreeSize = 0;
690 std::vector<TBranch*> selectedBranches;
691 for (
auto& mapping : mappings) {
692 selectedBranches.push_back((TBranch*)branches->At(mapping.mainBranchIdx));
693 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid,
"Generator",
"Adding branch %{public}s to stream.", selectedBranches.back()->GetName());
694 totalTreeSize += selectedBranches.back()->GetTotalSize();
695 if (mapping.vlaIdx != -1) {
696 selectedBranches.push_back((TBranch*)branches->At(mapping.vlaIdx));
697 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid,
"Generator",
"Adding branch %{public}s to stream.", selectedBranches.back()->GetName());
698 totalTreeSize += selectedBranches.back()->GetTotalSize();
702 size_t cacheSize = std::max(std::min(totalTreeSize, 25000000UL), 1000000UL);
704 tree->SetCacheSize(cacheSize);
705 for (
auto* branch : selectedBranches) {
706 tree->AddBranchToCache(branch,
false);
708 tree->StopCacheLearningPhase();
711 std::vector<ReadOps>& ops = treeFragment->ops();
713 ops.reserve(opsCount);
714 for (
size_t mi = 0; mi < mappings.size(); ++mi) {
717 auto datasetField = dataset_schema->field(mapping.datasetFieldIdx);
718 auto physicalField = physical_schema->field(mapping.mainBranchIdx);
720 if (mapping.vlaIdx != -1) {
721 auto* branch = (TBranch*)branches->At(mapping.vlaIdx);
724 .rootBranchEntries = branch->GetEntries(),
729 auto&
op = ops.back();
730 ARROW_ASSIGN_OR_RAISE(
op.targetBuffer, arrow::AllocateBuffer((
op.rootBranchEntries + 1) *
op.typeSize, pool));
735 auto& valueOp = ops.back();
736 valueOp.branch = (TBranch*)branches->At(mapping.mainBranchIdx);
737 valueOp.rootBranchEntries = valueOp.branch->GetEntries();
740 auto listType = std::dynamic_pointer_cast<arrow::FixedSizeListType>(datasetField->type());
741 valueOp.typeSize = physicalField->type()->byte_width();
744 if ((datasetField->type() == arrow::boolean())) {
746 valueOp.listSize = 1;
747 valueOp.targetBuffer = treeFragment->GetPlaceholderForOp((valueOp.rootBranchEntries + 7) / 8);
748 }
else if (listType && datasetField->type()->field(0)->type() == arrow::boolean()) {
749 valueOp.typeSize = physicalField->type()->field(0)->type()->byte_width();
750 valueOp.listSize = listType->list_size();
752 valueOp.targetBuffer = treeFragment->GetPlaceholderForOp((valueOp.rootBranchEntries * valueOp.listSize) / 8 + 1);
753 }
else if (mapping.vlaIdx != -1) {
754 valueOp.typeSize = physicalField->type()->field(0)->type()->byte_width();
755 valueOp.listSize = -1;
758 valueOp.targetBuffer = treeFragment->GetPlaceholderForOp(ops[ops.size() - 2].offsetCount * valueOp.typeSize);
759 }
else if (listType) {
761 valueOp.listSize = listType->list_size();
762 valueOp.typeSize = physicalField->type()->field(0)->type()->byte_width();
763 valueOp.targetBuffer = treeFragment->GetPlaceholderForOp(valueOp.rootBranchEntries * valueOp.typeSize * valueOp.listSize);
765 valueOp.typeSize = physicalField->type()->byte_width();
767 valueOp.listSize = 1;
768 valueOp.targetBuffer = treeFragment->GetPlaceholderForOp(valueOp.rootBranchEntries * valueOp.typeSize);
770 arrow::Status status;
771 std::shared_ptr<arrow::Array>
array;
774 auto vdata = std::make_shared<arrow::ArrayData>(datasetField->type()->field(0)->type(), valueOp.rootBranchEntries * valueOp.listSize,
775 std::vector<std::shared_ptr<arrow::Buffer>>{nullptr, valueOp.targetBuffer});
776 array = std::make_shared<arrow::FixedSizeListArray>(datasetField->type(), valueOp.rootBranchEntries, arrow::MakeArray(vdata));
778 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid,
"Op",
"Created op for branch %{public}s with %lli entries, size of the buffer %lli.",
779 valueOp.branch->GetName(),
780 valueOp.rootBranchEntries,
781 valueOp.targetBuffer->size());
782 }
else if (mapping.vlaIdx != -1) {
783 auto& offsetOp = ops[ops.size() - 2];
784 auto vdata = std::make_shared<arrow::ArrayData>(datasetField->type()->field(0)->type(), offsetOp.offsetCount,
785 std::vector<std::shared_ptr<arrow::Buffer>>{nullptr, valueOp.targetBuffer});
787 array = std::make_shared<arrow::ListArray>(datasetField->type(), offsetOp.rootBranchEntries, offsetOp.targetBuffer, arrow::MakeArray(vdata));
788 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid,
"Op",
"Created op for branch %{public}s with %lli entries, size of the buffer %lli.",
789 offsetOp.branch->GetName(), offsetOp.rootBranchEntries, offsetOp.targetBuffer->size());
790 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid,
"Op",
"Created op for branch %{public}s with %lli entries, size of the buffer %lli.",
791 valueOp.branch->GetName(),
792 offsetOp.offsetCount,
793 valueOp.targetBuffer->size());
795 auto data = std::make_shared<arrow::ArrayData>(datasetField->type(), valueOp.rootBranchEntries,
796 std::vector<std::shared_ptr<arrow::Buffer>>{nullptr, valueOp.targetBuffer});
798 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid,
"Op",
"Created op for branch %{public}s with %lli entries, size of the buffer %lli.",
799 valueOp.branch->GetName(),
800 valueOp.rootBranchEntries,
801 valueOp.targetBuffer->size());
804 columns.push_back(
array);
810 for (
size_t i = 0;
i < ops.size(); ++
i) {
813 rows =
op.rootBranchEntries;
816 auto& offsetOp = ops[
i - 1];
817 rows = offsetOp.rootBranchEntries;
820 throw runtime_error_f(
"Unmatching number of rows for branch %s. Expected %lli, found %lli",
op.branch->GetName(),
rows,
op.rootBranchEntries);
823 throw runtime_error_f(
"Unmatching number of rows for branch %s. Expected %lli, found %lli",
op.branch->GetName(),
rows, ops[
i - 1].offsetCount);
827 auto batch = arrow::RecordBatch::Make(dataset_schema,
rows, columns);
828 totalCompressedSize +=
tree->GetZipBytes();
829 totalUncompressedSize +=
tree->GetTotBytes();
830 O2_SIGNPOST_END(root_arrow_fs, tid,
"Generator",
"Done creating batch compressed:%zu uncompressed:%zu", totalCompressedSize, totalUncompressedSize);
839 case arrow::Type::BOOL:
841 case arrow::Type::UINT8:
843 case arrow::Type::UINT16:
845 case arrow::Type::UINT32:
847 case arrow::Type::UINT64:
849 case arrow::Type::INT8:
851 case arrow::Type::INT16:
853 case arrow::Type::INT32:
855 case arrow::Type::INT64:
857 case arrow::Type::FLOAT:
859 case arrow::Type::DOUBLE:
867 const std::string&
path,
868 const std::shared_ptr<const arrow::KeyValueMetadata>& metadata)
870 arrow::dataset::FileSource
source{
path, shared_from_this()};
871 auto prefix = metadata->Get(
"branch_prefix");
873 return std::make_shared<TTreeOutputStream>(
GetTree(
source).get(), *prefix);
875 return std::make_shared<TTreeOutputStream>(
GetTree(
source).get(),
"");
889 auto typeGenerator = [](std::shared_ptr<arrow::DataType>
const&
type,
int size) -> std::shared_ptr<arrow::DataType> {
892 return arrow::list(
type);
894 return std::move(
type);
896 return arrow::fixed_size_list(
type,
size);
901 case EDataType::kBool_t:
902 return typeGenerator(arrow::boolean(),
size);
903 case EDataType::kUChar_t:
904 return typeGenerator(arrow::uint8(),
size);
905 case EDataType::kUShort_t:
906 return typeGenerator(arrow::uint16(),
size);
907 case EDataType::kUInt_t:
908 return typeGenerator(arrow::uint32(),
size);
909 case EDataType::kULong64_t:
910 return typeGenerator(arrow::uint64(),
size);
911 case EDataType::kChar_t:
912 return typeGenerator(arrow::int8(),
size);
913 case EDataType::kShort_t:
914 return typeGenerator(arrow::int16(),
size);
915 case EDataType::kInt_t:
916 return typeGenerator(arrow::int32(),
size);
917 case EDataType::kLong64_t:
918 return typeGenerator(arrow::int64(),
size);
919 case EDataType::kFloat_t:
920 return typeGenerator(arrow::float32(),
size);
921 case EDataType::kDouble_t:
922 return typeGenerator(arrow::float64(),
size);
934 auto fs = std::dynamic_pointer_cast<VirtualRootFileSystemBase>(
source.filesystem());
939 auto objectHandler = fs->GetObjectHandler(
source);
941 if (!objectHandler->format->Equals(*
this)) {
947 auto tree = objectHandler->GetObjectAsOwner<TTree>().release();
948 if (
tree ==
nullptr) {
949 return arrow::Status::IOError(
"Unable to read tree ",
source.path());
952 auto branches =
tree->GetListOfBranches();
953 auto n = branches->GetEntries();
955 std::vector<std::shared_ptr<arrow::Field>>
fields;
957 bool prevIsSize =
false;
958 for (
auto i = 0;
i <
n; ++
i) {
959 auto branch =
static_cast<TBranch*
>(branches->At(
i));
960 if (branch ==
nullptr || branch->GetListOfLeaves()->At(0) ==
nullptr) {
961 return arrow::Status::IOError(
"Invalid branch ",
i,
" in tree ",
source.path());
963 std::string
name = branch->GetName();
964 if (prevIsSize &&
fields.back()->name() !=
name +
"_size") {
965 throw runtime_error_f(
"Unexpected layout for VLA container %s.", branch->GetName());
968 if (
name.ends_with(
"_size")) {
969 fields.emplace_back(std::make_shared<arrow::Field>(
name, arrow::int32()));
974 branch->GetExpectedType(cls,
type);
979 auto listSize =
static_cast<TLeaf*
>(branch->GetListOfLeaves()->At(0))->GetLenStatic();
986 if (!
fields.empty() &&
fields.back()->name().ends_with(
"_size")) {
989 return std::make_shared<arrow::Schema>(
fields);
994 arrow::dataset::FileSource
source, arrow::compute::Expression partition_expression,
995 std::shared_ptr<arrow::Schema> physical_schema)
998 return std::make_shared<TTreeFileFragment>(
source, std::dynamic_pointer_cast<arrow::dataset::FileFormat>(shared_from_this()),
999 std::move(partition_expression),
1005 std::vector<TBranch*> branches;
1006 std::vector<TBranch*> sizesBranches;
1007 std::vector<std::shared_ptr<arrow::Array>> valueArrays;
1008 std::vector<std::shared_ptr<arrow::Array>> sizeArrays;
1009 std::vector<std::shared_ptr<arrow::DataType>> valueTypes;
1011 std::vector<int64_t> valuesIdealBasketSize;
1012 std::vector<int64_t> sizeIdealBasketSize;
1014 std::vector<int64_t> typeSizes;
1015 std::vector<int64_t> listSizes;
1016 bool firstBasket =
true;
1019 void finaliseBasketSize(std::shared_ptr<arrow::RecordBatch> firstBatch)
1022 O2_SIGNPOST_START(root_arrow_fs, sid,
"finaliseBasketSize",
"First batch with %lli rows received and %zu columns",
1023 firstBatch->num_rows(), firstBatch->columns().size());
1024 for (
size_t i = 0;
i < branches.size();
i++) {
1025 auto* branch = branches[
i];
1026 auto* sizeBranch = sizesBranches[
i];
1028 int valueSize = valueTypes[
i]->byte_width();
1029 if (listSizes[
i] == 1) {
1030 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, sid,
"finaliseBasketSize",
"Branch %s exists and uses %d bytes per entry for %lli entries.",
1031 branch->GetName(), valueSize, firstBatch->num_rows());
1032 assert(sizeBranch ==
nullptr);
1033 branch->SetBasketSize(1024 + firstBatch->num_rows() * valueSize);
1034 }
else if (listSizes[
i] == -1) {
1035 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, sid,
"finaliseBasketSize",
"Branch %s exists and uses %d bytes per entry.",
1036 branch->GetName(), valueSize);
1038 auto column = firstBatch->GetColumnByName(schema_->field(
i)->name());
1039 auto list = std::static_pointer_cast<arrow::ListArray>(column);
1040 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, sid,
"finaliseBasketSize",
"Branch %s needed. Associated size branch %s and there are %lli entries of size %d in that list.",
1041 branch->GetName(), sizeBranch->GetName(), list->length(), valueSize);
1042 branch->SetBasketSize(1024 + firstBatch->num_rows() * valueSize * list->length());
1043 sizeBranch->SetBasketSize(1024 + firstBatch->num_rows() * 4);
1045 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, sid,
"finaliseBasketSize",
"Branch %s needed. There are %lli entries per array of size %d in that list.",
1046 branch->GetName(), listSizes[
i], valueSize);
1047 assert(sizeBranch ==
nullptr);
1048 branch->SetBasketSize(1024 + firstBatch->num_rows() * valueSize * listSizes[
i]);
1051 auto field = firstBatch->schema()->field(
i);
1052 if (field->name().starts_with(
"fIndexArray")) {
1054 int idealBasketSize = 4 * firstBatch->num_rows() + 1024 + field->type()->byte_width() * firstBatch->num_rows();
1055 int basketSize = std::max(32000, idealBasketSize);
1056 sizeBranch->SetBasketSize(basketSize);
1057 branch->SetBasketSize(basketSize);
1067 std::shared_ptr<arrow::io::OutputStream> destination,
1068 arrow::fs::FileLocator destination_locator)
1069 : FileWriter(
schema, options, destination, destination_locator)
1072 auto directoryStream = std::dynamic_pointer_cast<TDirectoryFileOutputStream>(destination_);
1073 auto treeStream = std::dynamic_pointer_cast<TTreeOutputStream>(destination_);
1075 if (directoryStream.get()) {
1076 TDirectoryFile* dir = directoryStream->GetDirectory();
1078 auto*
tree =
new TTree(destination_locator_.path.c_str(),
"");
1079 treeStream = std::make_shared<TTreeOutputStream>(
tree,
"");
1080 }
else if (treeStream.get()) {
1084 auto*
tree = treeStream->GetTree();
1085 treeStream = std::make_shared<TTreeOutputStream>(
tree, destination_locator_.path);
1088 throw std::runtime_error(
"Unsupported backend.");
1092 auto& field =
schema->field(
i);
1093 listSizes.push_back(1);
1095 int valuesIdealBasketSize = 0;
1097 switch (field->type()->id()) {
1098 case arrow::Type::FIXED_SIZE_LIST: {
1099 listSizes.back() = std::static_pointer_cast<arrow::FixedSizeListType>(field->type())->list_size();
1100 valuesIdealBasketSize = 1024 + valueTypes.back()->byte_width() * listSizes.back();
1101 valueTypes.push_back(field->type()->field(0)->type());
1102 sizesBranches.push_back(
nullptr);
1103 std::string leafList = fmt::format(
"{}[{}]{}", field->name(), listSizes.back(),
rootSuffixFromArrow(valueTypes.back()->id()));
1104 branches.push_back(treeStream->CreateBranch(field->name().c_str(), leafList.c_str()));
1106 case arrow::Type::LIST: {
1107 valueTypes.push_back(field->type()->field(0)->type());
1108 std::string leafList = fmt::format(
"{}[{}_size]{}", field->name(), field->name(),
rootSuffixFromArrow(valueTypes.back()->id()));
1109 listSizes.back() = -1;
1110 std::string sizeLeafList = field->name() +
"_size/I";
1111 sizesBranches.push_back(treeStream->CreateBranch((field->name() +
"_size").c_str(), sizeLeafList.c_str()));
1112 branches.push_back(treeStream->CreateBranch(field->name().c_str(), leafList.c_str()));
1117 valueTypes.push_back(field->type());
1119 sizesBranches.push_back(
nullptr);
1120 branches.push_back(treeStream->CreateBranch(field->name().c_str(), leafList.c_str()));
1127 arrow::Status
Write(
const std::shared_ptr<arrow::RecordBatch>& batch)
override
1130 firstBasket =
false;
1131 finaliseBasketSize(batch);
1135 if (batch->columns().empty() || batch->num_rows() == 0) {
1136 return arrow::Status::OK();
1140 auto directoryStream = std::dynamic_pointer_cast<TDirectoryFileOutputStream>(destination_);
1141 TTree*
tree =
nullptr;
1142 if (directoryStream.get()) {
1143 TDirectoryFile* dir = directoryStream->GetDirectory();
1144 tree = (TTree*)dir->Get(destination_locator_.path.c_str());
1146 auto treeStream = std::dynamic_pointer_cast<TTreeOutputStream>(destination_);
1150 throw std::runtime_error(
"Unsupported backend.");
1153 for (
auto i = 0u;
i < batch->columns().
size(); ++
i) {
1154 auto column = batch->column(
i);
1155 auto& field = batch->schema()->field(
i);
1157 valueArrays.push_back(
nullptr);
1159 switch (field->type()->id()) {
1160 case arrow::Type::FIXED_SIZE_LIST: {
1161 auto list = std::static_pointer_cast<arrow::FixedSizeListArray>(column);
1162 if (list->list_type()->field(0)->type()->id() == arrow::Type::BOOL) {
1163 int64_t
length = list->length() * list->list_type()->list_size();
1164 arrow::UInt8Builder builder;
1165 auto ok = builder.Reserve(
length);
1168 auto boolArray = std::static_pointer_cast<arrow::BooleanArray>(list->values());
1170 if (boolArray->IsValid(
i)) {
1172 uint8_t
value = boolArray->Value(
i) ? 1 : 0;
1173 auto ok = builder.Append(
value);
1176 auto ok = builder.AppendNull();
1179 valueArrays.back() = *builder.Finish();
1181 valueArrays.back() = list->values();
1184 case arrow::Type::LIST: {
1185 auto list = std::static_pointer_cast<arrow::ListArray>(column);
1186 valueArrays.back() = list->values();
1188 case arrow::Type::BOOL: {
1191 auto boolArray = std::static_pointer_cast<arrow::BooleanArray>(column);
1193 int64_t
length = boolArray->length();
1194 arrow::UInt8Builder builder;
1195 auto ok = builder.Reserve(
length);
1198 if (boolArray->IsValid(
i)) {
1200 uint8_t
value = boolArray->Value(
i) ? 1 : 0;
1201 auto ok = builder.Append(
value);
1204 auto ok = builder.AppendNull();
1207 valueArrays.back() = *builder.Finish();
1210 valueArrays.back() = column;
1215 while (pos < batch->num_rows()) {
1216 for (
size_t bi = 0; bi < branches.size(); ++bi) {
1217 auto* branch = branches[bi];
1218 auto* sizeBranch = sizesBranches[bi];
1219 auto array = batch->column(bi);
1220 auto& field = batch->schema()->field(bi);
1221 auto& listSize = listSizes[bi];
1222 auto valueType = valueTypes[bi];
1223 auto valueArray = valueArrays[bi];
1225 switch (field->type()->id()) {
1226 case arrow::Type::LIST: {
1227 auto list = std::static_pointer_cast<arrow::ListArray>(
array);
1228 listSize = list->value_length(
pos);
1229 uint8_t
const*
buffer = std::static_pointer_cast<arrow::PrimitiveArray>(valueArray)->values()->data() +
array->offset() + list->value_offset(
pos) * valueType->byte_width();
1230 branch->SetAddress((
void*)
buffer);
1231 sizeBranch->SetAddress(&listSize);
1233 case arrow::Type::FIXED_SIZE_LIST:
1236 auto byteWidth = valueType->byte_width() ? valueType->byte_width() : 1;
1237 uint8_t
const*
buffer = std::static_pointer_cast<arrow::PrimitiveArray>(valueArray)->values()->data() +
array->offset() +
pos * listSize * byteWidth;
1238 branch->SetAddress((
void*)
buffer);
1245 return arrow::Status::OK();
1250 auto treeStream = std::dynamic_pointer_cast<TTreeOutputStream>(destination_);
1251 auto*
tree = treeStream->GetTree();
1252 tree->Write(
"", TObject::kOverwrite);
1253 tree->SetDirectory(
nullptr);
1258arrow::Result<std::shared_ptr<arrow::dataset::FileWriter>>
TTreeFileFormat::MakeWriter(std::shared_ptr<arrow::io::OutputStream> destination, std::shared_ptr<arrow::Schema>
schema, std::shared_ptr<arrow::dataset::FileWriteOptions> options, arrow::fs::FileLocator destination_locator)
const
1260 auto writer = std::make_shared<TTreeFileWriter>(
schema, options, destination, destination_locator);
1261 return std::dynamic_pointer_cast<arrow::dataset::FileWriter>(writer);
1266 std::shared_ptr<TTreeFileWriteOptions> options(
std::shared_ptr< arrow::Schema > schema
std::vector< std::shared_ptr< arrow::Field > > fields
o2::raw::RawFileWriter * raw
#define DEFINE_DPL_PLUGIN_INSTANCE(NAME, KIND)
#define DEFINE_DPL_PLUGINS_END
#define DEFINE_DPL_PLUGINS_BEGIN
#define O2_DECLARE_DYNAMIC_LOG(name)
#define O2_SIGNPOST_ID_FROM_POINTER(name, log, pointer)
#define O2_SIGNPOST_END(log, id, name, format,...)
#define O2_SIGNPOST_EVENT_EMIT(log, id, name, format,...)
#define O2_SIGNPOST_START(log, id, name, format,...)
std::string type_name() const override
SingleTreeFileSystem(TTree *tree, size_t &totalCompressedSize, size_t &totalUncompressedSize)
arrow::Result< arrow::fs::FileInfo > GetFileInfo(std::string const &path) override
std::shared_ptr< RootObjectHandler > GetObjectHandler(arrow::dataset::FileSource source) override
std::unique_ptr< TTree > & GetTree(arrow::dataset::FileSource) override
~TTreeDeferredReadOutputStream() override=default
arrow::Status Reset(std::vector< ReadOps > ops, int64_t initial_capacity, arrow::MemoryPool *pool)
Initialize state of OutputStream with newly allocated memory and set position to 0.
arrow::Status Write(const void *data, int64_t nbytes) override
arrow::Result< std::shared_ptr< arrow::Buffer > > Finish()
Close the stream and return the buffer.
arrow::Result< int64_t > Tell() const override
static arrow::Result< std::shared_ptr< TTreeDeferredReadOutputStream > > Create(std::vector< ReadOps > &ops, int64_t initial_capacity=4096, arrow::MemoryPool *pool=arrow::default_memory_pool())
Create in-memory output stream with indicated capacity using a memory pool.
arrow::Status Close() override
Close the stream, preserving the buffer (retrieve it with Finish()).
bool closed() const override
TTreeFileFragment(arrow::dataset::FileSource source, std::shared_ptr< arrow::dataset::FileFormat > format, arrow::compute::Expression partition_expression, std::shared_ptr< arrow::Schema > physical_schema)
std::vector< ReadOps > & ops()
std::shared_ptr< arrow::Buffer > GetPlaceholderForOp(size_t size)
arrow::Result< std::shared_ptr< arrow::io::OutputStream > > OpenOutputStream(const std::string &path, const std::shared_ptr< const arrow::KeyValueMetadata > &metadata) override
~TTreeFileSystem() override
virtual std::unique_ptr< TTree > & GetTree(arrow::dataset::FileSource source)=0
TTreeFileWriteOptions(std::shared_ptr< arrow::dataset::FileFormat > format)
arrow::Status Write(const std::shared_ptr< arrow::RecordBatch > &batch) override
arrow::Future FinishInternal() override
TTreeFileWriter(std::shared_ptr< arrow::Schema > schema, std::shared_ptr< arrow::dataset::FileWriteOptions > options, std::shared_ptr< arrow::io::OutputStream > destination, arrow::fs::FileLocator destination_locator)
arrow::Status Write(const void *data, int64_t nbytes) override
arrow::Result< int64_t > Tell() const override
arrow::Status Close() override
bool closed() const override
TBranch * CreateBranch(char const *branchName, char const *sizeBranch)
TTreeOutputStream(TTree *, std::string branchPrefix)
GLuint GLsizei const GLuint const GLintptr * offsets
GLuint const GLchar * name
GLsizei GLsizei GLchar * source
GLsizei const GLfloat * value
GLint GLint GLsizei GLint GLenum GLenum type
GLuint GLsizei GLsizei * length
GLsizei const GLchar *const * path
GLint GLint GLsizei GLint GLenum format
Defining ITS Vertex explicitly as messageable.
RuntimeErrorRef runtime_error(const char *)
TBufferFile & rootBuffer()
@ RootObjectReadingImplementation
char const * rootSuffixFromArrow(arrow::Type::type id)
auto arrowTypeFromROOT(EDataType type, int size)
void bigEndianCopy(void *dest, const void *src, int count, size_t typeSize)
RuntimeErrorRef runtime_error_f(const char *,...)
int64_t rootBranchEntries
std::shared_ptr< arrow::Buffer > targetBuffer
std::function< std::shared_ptr< arrow::dataset::FileWriteOptions >()> options
RootArrowFactory * create() override
std::shared_ptr< o2::framework::TTreeFileFormat > format
size_t totalCompressedSize
size_t totalUncompressedSize
VectorOfTObjectPtrs other
std::unique_ptr< TTree > tree((TTree *) flIn.Get(std::string(o2::base::NameConf::CTFTREENAME).c_str()))
std::vector< ReadoutWindowData > rows