Project
Loading...
Searching...
No Matches
TTreePlugin.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
13#include "Framework/Plugins.h"
14#include "Framework/Signpost.h"
15#include "Framework/BigEndian.h"
16#include <TBufferFile.h>
17#include <TBufferIO.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>
32#include <TTree.h>
33#include <TBranch.h>
34#include <TFile.h>
35#include <TLeaf.h>
36#include <unistd.h>
37#include <cstdint>
38#include <memory>
39#include <stdexcept>
40
42
43namespace o2::framework
44{
45
46enum struct ReadOpKind {
47 Unknown,
48 Offsets,
49 Values,
51 VLA
52};
53
54struct ReadOps {
55 TBranch* branch = nullptr;
56 std::shared_ptr<arrow::Buffer> targetBuffer = nullptr;
57 int64_t rootBranchEntries = 0;
58 size_t typeSize = 0;
59 size_t listSize = 0;
60 // If this is an offset reading op, keep track of the actual
61 // range for the offsets, not only how many VLAs are there.
62 int64_t offsetCount = 0;
64};
65
71{
72 public:
73 explicit TTreeDeferredReadOutputStream(std::vector<ReadOps>& ops,
74 const std::shared_ptr<arrow::ResizableBuffer>& buffer);
75
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());
86
87 // By the time we call the destructor, the contents
88 // of the buffer are already moved to fairmq
89 // for being sent.
90 ~TTreeDeferredReadOutputStream() override = default;
91
92 // Implement the OutputStream interface
93
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;
99
101 using OutputStream::Write;
103
105 arrow::Result<std::shared_ptr<arrow::Buffer>> Finish();
106
112 arrow::Status Reset(std::vector<ReadOps> ops,
113 int64_t initial_capacity, arrow::MemoryPool* pool);
114
115 [[nodiscard]] int64_t capacity() const { return capacity_; }
116
117 private:
119 std::vector<ReadOps> ops_;
120
121 // Ensures there is sufficient space available to write nbytes
122 arrow::Status Reserve(int64_t nbytes);
123
124 std::shared_ptr<arrow::ResizableBuffer> buffer_;
125 bool is_open_;
126 int64_t capacity_;
127 int64_t position_;
128 uint8_t* mutable_data_;
129};
130
131static constexpr int64_t kBufferMinimumSize = 256;
132
133TTreeDeferredReadOutputStream::TTreeDeferredReadOutputStream()
134 : is_open_(false), capacity_(0), position_(0), mutable_data_(nullptr) {}
135
136TTreeDeferredReadOutputStream::TTreeDeferredReadOutputStream(std::vector<ReadOps>& ops,
137 const std::shared_ptr<arrow::ResizableBuffer>& buffer)
138 : ops_(ops),
139 buffer_(buffer),
140 is_open_(true),
141 capacity_(buffer->size()),
142 position_(0),
143 mutable_data_(buffer->mutable_data()) {}
144
145arrow::Result<std::shared_ptr<TTreeDeferredReadOutputStream>> TTreeDeferredReadOutputStream::Create(
146 std::vector<ReadOps>& ops,
147 int64_t initial_capacity, arrow::MemoryPool* pool)
148{
149 // ctor is private, so cannot use make_shared
150 auto ptr = std::shared_ptr<TTreeDeferredReadOutputStream>(new TTreeDeferredReadOutputStream);
151 RETURN_NOT_OK(ptr->Reset(ops, initial_capacity, pool));
152 return ptr;
153}
154
155arrow::Status TTreeDeferredReadOutputStream::Reset(std::vector<ReadOps> ops,
156 int64_t initial_capacity, arrow::MemoryPool* pool)
157{
158 ARROW_ASSIGN_OR_RAISE(buffer_, AllocateResizableBuffer(initial_capacity, pool));
159 ops_ = ops;
160 is_open_ = true;
161 capacity_ = initial_capacity;
162 position_ = 0;
163 mutable_data_ = buffer_->mutable_data();
164 return arrow::Status::OK();
165}
166
168{
169 if (is_open_) {
170 is_open_ = false;
171 if (position_ < capacity_) {
172 RETURN_NOT_OK(buffer_->Resize(position_, false));
173 }
174 }
175 return arrow::Status::OK();
176}
177
178bool TTreeDeferredReadOutputStream::closed() const { return !is_open_; }
179
180arrow::Result<std::shared_ptr<arrow::Buffer>> TTreeDeferredReadOutputStream::Finish()
181{
182 RETURN_NOT_OK(Close());
183 buffer_->ZeroPadding();
184 is_open_ = false;
185 return std::move(buffer_);
186}
187
188arrow::Result<int64_t> TTreeDeferredReadOutputStream::Tell() const { return position_; }
189
191 int readEntries = 0;
192 rootBuffer.Reset();
193 while (readEntries < op.rootBranchEntries) {
194 auto readLast = op.branch->GetBulkRead().GetEntriesSerialized(readEntries, rootBuffer);
195 if (readLast < 0) {
196 throw runtime_error_f("Error while reading branch %s starting from %zu.", op.branch->GetName(), readEntries);
197 }
198 int size = readLast * op.listSize;
199 readEntries += readLast;
200 bigEndianCopy(target, rootBuffer.GetCurrent(), size, op.typeSize);
201 target += (ptrdiff_t)(size * op.typeSize);
202 }
203};
204
206 int readEntries = 0;
207 rootBuffer.Reset();
208 // Set to 0
209 memset(target, 0, op.targetBuffer->size());
210 int readLast = 0;
211 while (readEntries < op.rootBranchEntries) {
212 auto beginValue = readEntries;
213 readLast = op.branch->GetBulkRead().GetBulkEntries(readEntries, rootBuffer);
214 if (readLast < 0) {
215 throw runtime_error_f("Error while reading branch %s starting from %d.", op.branch->GetName(), readEntries);
216 }
217 int size = readLast * op.listSize;
218 readEntries += readLast;
219 for (int i = beginValue; i < beginValue + size; ++i) {
220 auto value = static_cast<uint8_t>(rootBuffer.GetCurrent()[i - beginValue] << (i % 8));
221 target[i / 8] |= value;
222 }
223 }
224};
225
226auto readVLAValues = [](uint8_t* target, ReadOps& op, ReadOps const& offsetOp, TBufferFile& rootBuffer) {
227 int readEntries = 0;
228 auto* tPtrOffset = reinterpret_cast<const int*>(offsetOp.targetBuffer->data());
229 std::span<int const> const offsets{tPtrOffset, tPtrOffset + offsetOp.rootBranchEntries + 1};
230
231 rootBuffer.Reset();
232 while (readEntries < op.rootBranchEntries) {
233 auto readLast = op.branch->GetBulkRead().GetEntriesSerialized(readEntries, rootBuffer);
234 if (readLast < 0) {
235 throw runtime_error_f("Error while reading branch %s starting from %d.", op.branch->GetName(), readEntries);
236 }
237 if (readEntries + readLast > op.rootBranchEntries) {
238 throw runtime_error_f("Invalid read range for branch %s: starting from %d, read %d entries, total entries %lld.",
239 op.branch->GetName(), readEntries, readLast, static_cast<long long>(op.rootBranchEntries));
240 }
241 int size = offsets[readEntries + readLast] - offsets[readEntries];
242 if (size < 0) {
243 throw runtime_error_f("Invalid offset range for branch %s: offsets[%d]=%d, offsets[%d]=%d.",
244 op.branch->GetName(), readEntries, offsets[readEntries], readEntries + readLast, offsets[readEntries + readLast]);
245 }
246 readEntries += readLast;
247 bigEndianCopy(target, rootBuffer.GetCurrent(), size, op.typeSize);
248 target += (ptrdiff_t)(size * op.typeSize);
249 }
250};
251
253{
254 // FIXME: we will need more than one once we have multithreaded reading.
255 static TBufferFile rootBuffer{TBuffer::EMode::kWrite, 4 * 1024 * 1024};
256 return rootBuffer;
257}
258
259arrow::Status TTreeDeferredReadOutputStream::Write(const void* data, int64_t nbytes)
260{
261 if (ARROW_PREDICT_FALSE(!is_open_)) {
262 return arrow::Status::IOError("OutputStream is closed");
263 }
264 if (ARROW_PREDICT_TRUE(nbytes == 0)) {
265 return arrow::Status::OK();
266 }
267 if (ARROW_PREDICT_FALSE(position_ + nbytes >= capacity_)) {
268 RETURN_NOT_OK(Reserve(nbytes));
269 }
270 // This is a real address which needs to be copied. Do it!
271 auto ref = (int64_t)data;
272 if (ref >= ops_.size()) {
273 memcpy(mutable_data_ + position_, data, nbytes);
274 position_ += nbytes;
275 return arrow::Status::OK();
276 }
277 auto& op = ops_[ref];
278
279 switch (op.kind) {
280 // Offsets need to be read in advance because we need to know
281 // how many elements are there in total (since TTree does not allow discovering such informantion)
283 break;
285 readValues(mutable_data_ + position_, op, rootBuffer());
286 break;
287 case ReadOpKind::VLA:
288 readVLAValues(mutable_data_ + position_, op, ops_[ref - 1], rootBuffer());
289 break;
291 readBoolValues(mutable_data_ + position_, op, rootBuffer());
292 break;
294 throw runtime_error("Unknown Op");
295 }
296 op.branch->SetStatus(false);
297 op.branch->DropBaskets("all");
298 op.branch->Reset();
299 op.branch->GetTransientBuffer(0)->Expand(0);
300
301 position_ += nbytes;
302 return arrow::Status::OK();
303}
304
305arrow::Status TTreeDeferredReadOutputStream::Reserve(int64_t nbytes)
306{
307 // Always overallocate by doubling. It seems that it is a better growth
308 // strategy, at least for memory_benchmark.cc.
309 // This may be because it helps match the allocator's allocation buckets
310 // more exactly. Or perhaps it hits a sweet spot in jemalloc.
311 int64_t new_capacity = std::max(kBufferMinimumSize, capacity_);
312 new_capacity = position_ + nbytes;
313 if (new_capacity > capacity_) {
314 RETURN_NOT_OK(buffer_->Resize(new_capacity));
315 capacity_ = new_capacity;
316 mutable_data_ = buffer_->mutable_data();
317 }
318 return arrow::Status::OK();
319}
320
322{
323 public:
324 TTreeFileWriteOptions(std::shared_ptr<arrow::dataset::FileFormat> format)
325 : FileWriteOptions(format)
326 {
327 }
328};
329
330// A filesystem which allows me to get a TTree
332{
333 public:
335
336 arrow::Result<std::shared_ptr<arrow::io::OutputStream>> OpenOutputStream(
337 const std::string& path,
338 const std::shared_ptr<const arrow::KeyValueMetadata>& metadata) override;
339
340 virtual std::unique_ptr<TTree>& GetTree(arrow::dataset::FileSource source) = 0;
341};
342
344{
345 size_t& mTotCompressedSize;
346 size_t& mTotUncompressedSize;
347
348 public:
349 TTreeFileFormat(size_t& totalCompressedSize, size_t& totalUncompressedSize)
350 : FileFormat({}),
351 mTotCompressedSize(totalCompressedSize),
352 mTotUncompressedSize(totalUncompressedSize)
353 {
354 }
355
356 ~TTreeFileFormat() override = default;
357
358 std::string type_name() const override
359 {
360 return "ttree";
361 }
362
363 bool Equals(const FileFormat& other) const override
364 {
365 return other.type_name() == this->type_name();
366 }
367
368 arrow::Result<bool> IsSupported(const arrow::dataset::FileSource& source) const override
369 {
370 auto fs = std::dynamic_pointer_cast<VirtualRootFileSystemBase>(source.filesystem());
371 if (!fs) {
372 return false;
373 }
374 return fs->CheckSupport(source);
375 }
376
377 arrow::Result<std::shared_ptr<arrow::Schema>> Inspect(const arrow::dataset::FileSource& source) const override;
379 arrow::Result<std::shared_ptr<arrow::dataset::FileFragment>> MakeFragment(
380 arrow::dataset::FileSource source, arrow::compute::Expression partition_expression,
381 std::shared_ptr<arrow::Schema> physical_schema) override;
382
383 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;
384
385 std::shared_ptr<arrow::dataset::FileWriteOptions> DefaultWriteOptions() override;
386
387 arrow::Result<arrow::RecordBatchGenerator> ScanBatchesAsync(
388 const std::shared_ptr<arrow::dataset::ScanOptions>& options,
389 const std::shared_ptr<arrow::dataset::FileFragment>& fragment) const override;
390};
391
393{
394 public:
395 SingleTreeFileSystem(TTree* tree, size_t& totalCompressedSize, size_t& totalUncompressedSize)
396 : TTreeFileSystem(),
397 mTotUncompressedSize(totalUncompressedSize),
398 mTotCompressedSize(totalCompressedSize),
399 mTree(tree)
400 {
401 }
402
403 arrow::Result<arrow::fs::FileInfo> GetFileInfo(std::string const& path) override;
404
405 std::string type_name() const override
406 {
407 return "ttree";
408 }
409
410 std::shared_ptr<RootObjectHandler> GetObjectHandler(arrow::dataset::FileSource source) override
411 {
412 return std::make_shared<RootObjectHandler>((void*)mTree.get(), std::make_shared<TTreeFileFormat>(mTotCompressedSize, mTotUncompressedSize));
413 }
414
415 std::unique_ptr<TTree>& GetTree(arrow::dataset::FileSource) override
416 {
417 // Simply return the only TTree we have
418 return mTree;
419 }
420
421 private:
422 // References, not values: a TTreeFileFormat built in GetObjectHandler binds to these,
423 // so by-value members would have it accumulate into copies that are thrown away (and,
424 // being uninitialised here, read as indeterminate).
425 size_t& mTotUncompressedSize;
426 size_t& mTotCompressedSize;
427 std::unique_ptr<TTree> mTree;
428};
429
430arrow::Result<arrow::fs::FileInfo> SingleTreeFileSystem::GetFileInfo(std::string const& path)
431{
432 arrow::dataset::FileSource source(path, shared_from_this());
433 arrow::fs::FileInfo result;
434 result.set_path(path);
435 result.set_type(arrow::fs::FileType::File);
436 return result;
437}
438
439// A fragment which holds a tree
441{
442 public:
443 TTreeFileFragment(arrow::dataset::FileSource source,
444 std::shared_ptr<arrow::dataset::FileFormat> format,
445 arrow::compute::Expression partition_expression,
446 std::shared_ptr<arrow::Schema> physical_schema)
447 : FileFragment(source, format, std::move(partition_expression), physical_schema)
448 {
449 auto rootFS = std::dynamic_pointer_cast<VirtualRootFileSystemBase>(this->source().filesystem());
450 if (rootFS.get() == nullptr) {
451 throw runtime_error_f("Unknown filesystem %s when reading %s.",
452 source.filesystem()->type_name().c_str(), source.path().c_str());
453 }
454 auto objectHandler = rootFS->GetObjectHandler(source);
455 if (!objectHandler->format->Equals(*format)) {
456 throw runtime_error_f("Cannot read source %s with format %s to pupulate a TTreeFileFragment.",
457 source.path().c_str(), objectHandler->format->type_name().c_str());
458 };
459 mTree = objectHandler->GetObjectAsOwner<TTree>();
460 }
461
462 TTree* GetTree()
463 {
464 return mTree.get();
465 }
466
467 std::vector<ReadOps>& ops()
468 {
469 return mOps;
470 }
471
474 std::shared_ptr<arrow::Buffer> GetPlaceholderForOp(size_t size)
475 {
476 return std::make_shared<arrow::Buffer>((uint8_t*)(mOps.size() - 1), size);
477 }
478
479 private:
480 std::unique_ptr<TTree> mTree;
481 std::vector<ReadOps> mOps;
482};
483
484// An arrow outputstream which allows to write to a TTree. Eventually
485// with a prefix for the branches.
487{
488 public:
489 // Using a pointer means that the tree itself is owned by another
490 // class
491 TTreeOutputStream(TTree*, std::string branchPrefix);
492
493 arrow::Status Close() override;
494
495 arrow::Result<int64_t> Tell() const override;
496
497 arrow::Status Write(const void* data, int64_t nbytes) override;
498
499 bool closed() const override;
500
501 TBranch* CreateBranch(char const* branchName, char const* sizeBranch);
502
503 TTree* GetTree()
504 {
505 return mTree;
506 }
507
508 private:
509 TTree* mTree;
510 std::string mBranchPrefix;
511};
512
513// An arrow outputstream which allows to write to a ttree
514// @a branch prefix is to be used to identify a set of branches which all belong to
515// the same table.
516TTreeOutputStream::TTreeOutputStream(TTree* f, std::string branchPrefix)
517 : mTree(f),
518 mBranchPrefix(std::move(branchPrefix))
519{
520}
521
523{
524 if (mTree->GetCurrentFile() == nullptr) {
525 return arrow::Status::Invalid("Cannot close a tree not attached to a file");
526 }
527 mTree->GetCurrentFile()->Close();
528 return arrow::Status::OK();
529}
530
531arrow::Result<int64_t> TTreeOutputStream::Tell() const
532{
533 return arrow::Result<int64_t>(arrow::Status::NotImplemented("Cannot move"));
534}
535
536arrow::Status TTreeOutputStream::Write(const void* data, int64_t nbytes)
537{
538 return arrow::Status::NotImplemented("Cannot write raw bytes to a TTree");
539}
540
542{
543 // A standalone tree is never closed.
544 if (mTree->GetCurrentFile() == nullptr) {
545 return false;
546 }
547 return mTree->GetCurrentFile()->IsOpen() == false;
548}
549
550TBranch* TTreeOutputStream::CreateBranch(char const* branchName, char const* sizeBranch)
551{
552 if (mBranchPrefix.empty() == true) {
553 return mTree->Branch(branchName, (char*)nullptr, sizeBranch);
554 }
555 return mTree->Branch((mBranchPrefix + "/" + branchName).c_str(), (char*)nullptr, (mBranchPrefix + sizeBranch).c_str());
556}
557
561 std::shared_ptr<o2::framework::TTreeFileFormat> format = nullptr;
562};
563
566 {
567 auto context = new TTreePluginContext;
568 context->format = std::make_shared<o2::framework::TTreeFileFormat>(context->totalCompressedSize, context->totalUncompressedSize);
569 return new RootArrowFactory{
570 .options = [context]() { return context->format->DefaultWriteOptions(); },
571 .format = [context]() { return context->format; },
572 .deferredOutputStreamer = [](std::shared_ptr<arrow::dataset::FileFragment> fragment, const std::shared_ptr<arrow::ResizableBuffer>& buffer) -> std::shared_ptr<arrow::io::OutputStream> {
573 auto treeFragment = std::dynamic_pointer_cast<TTreeFileFragment>(fragment);
574 return std::make_shared<TTreeDeferredReadOutputStream>(treeFragment->ops(), buffer);
575 }};
576 }
577};
578
584
586 uint32_t offset = 0;
587 std::span<int> offsets;
588 int readEntries = 0;
589 int count = 0;
590 auto* tPtrOffset = reinterpret_cast<int*>(op.targetBuffer->mutable_data());
591 offsets = std::span<int>{tPtrOffset, tPtrOffset + op.rootBranchEntries + 1};
592
593 // read sizes first
594 rootBuffer.Reset();
595 while (readEntries < op.rootBranchEntries) {
596 auto readLast = op.branch->GetBulkRead().GetEntriesSerialized(readEntries, rootBuffer);
597 if (readLast == -1) {
598 throw runtime_error_f("Unable to read from branch %s.", op.branch->GetName());
599 }
600 readEntries += readLast;
601 for (auto i = 0; i < readLast; ++i) {
602 offsets[count++] = (int)offset;
603 uint32_t raw = reinterpret_cast<uint32_t*>(rootBuffer.GetCurrent())[i];
604 offset += (std::endian::native == std::endian::little) ? __builtin_bswap32(raw) : raw;
605 }
606 }
608 op.offsetCount = offset;
609};
610
611arrow::Result<arrow::RecordBatchGenerator> TTreeFileFormat::ScanBatchesAsync(
612 const std::shared_ptr<arrow::dataset::ScanOptions>& options,
613 const std::shared_ptr<arrow::dataset::FileFragment>& fragment) const
614{
615 assert(options->dataset_schema != nullptr);
616 // This is the schema we want to read
617 auto dataset_schema = options->dataset_schema;
618 auto treeFragment = std::dynamic_pointer_cast<TTreeFileFragment>(fragment);
619 if (treeFragment.get() == nullptr) {
620 return {arrow::Status::NotImplemented("Not a ttree fragment")};
621 }
622
623 auto generator = [pool = options->pool, treeFragment, dataset_schema, &totalCompressedSize = mTotCompressedSize,
624 &totalUncompressedSize = mTotUncompressedSize]() -> arrow::Future<std::shared_ptr<arrow::RecordBatch>> {
625 O2_SIGNPOST_ID_FROM_POINTER(tid, root_arrow_fs, treeFragment->GetTree());
626 O2_SIGNPOST_START(root_arrow_fs, tid, "Generator", "Creating batch for tree %{public}s", treeFragment->GetTree()->GetName());
627 std::vector<std::shared_ptr<arrow::Array>> columns;
628 std::vector<std::shared_ptr<arrow::Field>> fields = dataset_schema->fields();
629 auto physical_schema = *treeFragment->ReadPhysicalSchema();
630
631 if (dataset_schema->num_fields() > physical_schema->num_fields()) {
632 throw runtime_error_f("One TTree must have all the fields requested in a table");
633 }
634
635 // Register physical fields into the cache
636 std::vector<BranchFieldMapping> mappings;
637
638 // We need to count the number of readops to avoid moving the vector.
639 int opsCount = 0;
640 for (int fi = 0; fi < dataset_schema->num_fields(); ++fi) {
641 auto dataset_field = dataset_schema->field(fi);
642 // This is needed because for now the dataset_field
643 // is actually the schema of the ttree
644 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid, "Generator", "Processing dataset field %{public}s.", dataset_field->name().c_str());
645 int physicalFieldIdx = physical_schema->GetFieldIndex(dataset_field->name());
646
647 if (physicalFieldIdx < 0) {
648 throw runtime_error_f("Cannot find physical field associated to %s. Possible fields: %s",
649 dataset_field->name().c_str(), physical_schema->ToString().c_str());
650 }
651 if (physicalFieldIdx > 0 && physical_schema->field(physicalFieldIdx - 1)->name().ends_with("_size")) {
652 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid, "Generator", "Field %{public}s has sizes in %{public}s.", dataset_field->name().c_str(),
653 physical_schema->field(physicalFieldIdx - 1)->name().c_str());
654 mappings.push_back({physicalFieldIdx, physicalFieldIdx - 1, fi});
655 opsCount += 2;
656 } else {
657 if (physicalFieldIdx > 0) {
658 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid, "Generator", "Field %{public}s previous field is %{public}s.", dataset_field->name().c_str(),
659 physical_schema->field(physicalFieldIdx - 1)->name().c_str());
660 }
661 mappings.push_back({physicalFieldIdx, -1, fi});
662 opsCount++;
663 }
664 }
665
666 auto* tree = treeFragment->GetTree();
667 auto branches = tree->GetListOfBranches();
668 size_t totalTreeSize = 0;
669 std::vector<TBranch*> selectedBranches;
670 for (auto& mapping : mappings) {
671 selectedBranches.push_back((TBranch*)branches->At(mapping.mainBranchIdx));
672 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid, "Generator", "Adding branch %{public}s to stream.", selectedBranches.back()->GetName());
673 totalTreeSize += selectedBranches.back()->GetTotalSize();
674 if (mapping.vlaIdx != -1) {
675 selectedBranches.push_back((TBranch*)branches->At(mapping.vlaIdx));
676 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid, "Generator", "Adding branch %{public}s to stream.", selectedBranches.back()->GetName());
677 totalTreeSize += selectedBranches.back()->GetTotalSize();
678 }
679 }
680
681 size_t cacheSize = std::max(std::min(totalTreeSize, 25000000UL), 1000000UL);
682 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid, "Generator", "Resizing cache to %zu.", cacheSize);
683 tree->SetCacheSize(cacheSize);
684 for (auto* branch : selectedBranches) {
685 tree->AddBranchToCache(branch, false);
686 }
687 tree->StopCacheLearningPhase();
688
689 // Intermediate buffer to bulk read. Two for now
690 std::vector<ReadOps>& ops = treeFragment->ops();
691 ops.clear();
692 ops.reserve(opsCount);
693 for (size_t mi = 0; mi < mappings.size(); ++mi) {
694 BranchFieldMapping mapping = mappings[mi];
695 // The field actually on disk
696 auto datasetField = dataset_schema->field(mapping.datasetFieldIdx);
697 auto physicalField = physical_schema->field(mapping.mainBranchIdx);
698
699 if (mapping.vlaIdx != -1) {
700 auto* branch = (TBranch*)branches->At(mapping.vlaIdx);
701 ops.emplace_back(ReadOps{
702 .branch = branch,
703 .rootBranchEntries = branch->GetEntries(),
704 .typeSize = 4,
705 .listSize = 1,
706 .kind = ReadOpKind::Offsets,
707 });
708 auto& op = ops.back();
709 ARROW_ASSIGN_OR_RAISE(op.targetBuffer, arrow::AllocateBuffer((op.rootBranchEntries + 1) * op.typeSize, pool));
710 // Offsets need to be read immediately to know how many values are there
712 }
713 ops.push_back({});
714 auto& valueOp = ops.back();
715 valueOp.branch = (TBranch*)branches->At(mapping.mainBranchIdx);
716 valueOp.rootBranchEntries = valueOp.branch->GetEntries();
717 // In case this is a vla, we set the offsetCount as totalEntries
718 // In case we read booleans we need a special coversion from bytes to bits.
719 auto listType = std::dynamic_pointer_cast<arrow::FixedSizeListType>(datasetField->type());
720 valueOp.typeSize = physicalField->type()->byte_width();
721 // Notice how we are not (yet) allocating buffers at this point. We merely
722 // create placeholders to subsequently fill.
723 if ((datasetField->type() == arrow::boolean())) {
724 valueOp.kind = ReadOpKind::Booleans;
725 valueOp.listSize = 1;
726 valueOp.targetBuffer = treeFragment->GetPlaceholderForOp((valueOp.rootBranchEntries + 7) / 8);
727 } else if (listType && datasetField->type()->field(0)->type() == arrow::boolean()) {
728 valueOp.typeSize = physicalField->type()->field(0)->type()->byte_width();
729 valueOp.listSize = listType->list_size();
730 valueOp.kind = ReadOpKind::Booleans;
731 valueOp.targetBuffer = treeFragment->GetPlaceholderForOp((valueOp.rootBranchEntries * valueOp.listSize) / 8 + 1);
732 } else if (mapping.vlaIdx != -1) {
733 valueOp.typeSize = physicalField->type()->field(0)->type()->byte_width();
734 valueOp.listSize = -1;
735 // -1 is the current one, -2 is the one with for the offsets
736 valueOp.kind = ReadOpKind::VLA;
737 valueOp.targetBuffer = treeFragment->GetPlaceholderForOp(ops[ops.size() - 2].offsetCount * valueOp.typeSize);
738 } else if (listType) {
739 valueOp.kind = ReadOpKind::Values;
740 valueOp.listSize = listType->list_size();
741 valueOp.typeSize = physicalField->type()->field(0)->type()->byte_width();
742 valueOp.targetBuffer = treeFragment->GetPlaceholderForOp(valueOp.rootBranchEntries * valueOp.typeSize * valueOp.listSize);
743 } else {
744 valueOp.typeSize = physicalField->type()->byte_width();
745 valueOp.kind = ReadOpKind::Values;
746 valueOp.listSize = 1;
747 valueOp.targetBuffer = treeFragment->GetPlaceholderForOp(valueOp.rootBranchEntries * valueOp.typeSize);
748 }
749 arrow::Status status;
750 std::shared_ptr<arrow::Array> array;
751
752 if (listType) {
753 auto vdata = std::make_shared<arrow::ArrayData>(datasetField->type()->field(0)->type(), valueOp.rootBranchEntries * valueOp.listSize,
754 std::vector<std::shared_ptr<arrow::Buffer>>{nullptr, valueOp.targetBuffer});
755 array = std::make_shared<arrow::FixedSizeListArray>(datasetField->type(), valueOp.rootBranchEntries, arrow::MakeArray(vdata));
756 // This is a vla, there is also an offset op
757 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid, "Op", "Created op for branch %{public}s with %lli entries, size of the buffer %lli.",
758 valueOp.branch->GetName(),
759 valueOp.rootBranchEntries,
760 valueOp.targetBuffer->size());
761 } else if (mapping.vlaIdx != -1) {
762 auto& offsetOp = ops[ops.size() - 2];
763 auto vdata = std::make_shared<arrow::ArrayData>(datasetField->type()->field(0)->type(), offsetOp.offsetCount,
764 std::vector<std::shared_ptr<arrow::Buffer>>{nullptr, valueOp.targetBuffer});
765 // We have pushed an offset op if this was the case.
766 array = std::make_shared<arrow::ListArray>(datasetField->type(), offsetOp.rootBranchEntries, offsetOp.targetBuffer, arrow::MakeArray(vdata));
767 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid, "Op", "Created op for branch %{public}s with %lli entries, size of the buffer %lli.",
768 offsetOp.branch->GetName(), offsetOp.rootBranchEntries, offsetOp.targetBuffer->size());
769 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid, "Op", "Created op for branch %{public}s with %lli entries, size of the buffer %lli.",
770 valueOp.branch->GetName(),
771 offsetOp.offsetCount,
772 valueOp.targetBuffer->size());
773 } else {
774 auto data = std::make_shared<arrow::ArrayData>(datasetField->type(), valueOp.rootBranchEntries,
775 std::vector<std::shared_ptr<arrow::Buffer>>{nullptr, valueOp.targetBuffer});
776 array = arrow::MakeArray(data);
777 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid, "Op", "Created op for branch %{public}s with %lli entries, size of the buffer %lli.",
778 valueOp.branch->GetName(),
779 valueOp.rootBranchEntries,
780 valueOp.targetBuffer->size());
781 }
782
783 columns.push_back(array);
784 }
785
786 // Do the actual filling of the buffers. This happens after we have created the whole structure
787 // so that we can read directly in shared memory.
788 int64_t rows = -1;
789 for (size_t i = 0; i < ops.size(); ++i) {
790 auto& op = ops[i];
791 if (rows == -1 && op.kind != ReadOpKind::VLA) {
792 rows = op.rootBranchEntries;
793 }
794 if (rows == -1 && op.kind == ReadOpKind::VLA) {
795 auto& offsetOp = ops[i - 1];
796 rows = offsetOp.rootBranchEntries;
797 }
798 if (op.kind != ReadOpKind::VLA && rows != op.rootBranchEntries) {
799 throw runtime_error_f("Unmatching number of rows for branch %s. Expected %lli, found %lli", op.branch->GetName(), rows, op.rootBranchEntries);
800 }
801 if (op.kind == ReadOpKind::VLA && rows != ops[i - 1].rootBranchEntries) {
802 throw runtime_error_f("Unmatching number of rows for branch %s. Expected %lli, found %lli", op.branch->GetName(), rows, ops[i - 1].offsetCount);
803 }
804 }
805
806 auto batch = arrow::RecordBatch::Make(dataset_schema, rows, columns);
807 totalCompressedSize += tree->GetZipBytes();
808 totalUncompressedSize += tree->GetTotBytes();
809 O2_SIGNPOST_END(root_arrow_fs, tid, "Generator", "Done creating batch compressed:%zu uncompressed:%zu", totalCompressedSize, totalUncompressedSize);
810 return batch;
811 };
812 return generator;
813}
814
815char const* rootSuffixFromArrow(arrow::Type::type id)
816{
817 switch (id) {
818 case arrow::Type::BOOL:
819 return "/O";
820 case arrow::Type::UINT8:
821 return "/b";
822 case arrow::Type::UINT16:
823 return "/s";
824 case arrow::Type::UINT32:
825 return "/i";
826 case arrow::Type::UINT64:
827 return "/l";
828 case arrow::Type::INT8:
829 return "/B";
830 case arrow::Type::INT16:
831 return "/S";
832 case arrow::Type::INT32:
833 return "/I";
834 case arrow::Type::INT64:
835 return "/L";
836 case arrow::Type::FLOAT:
837 return "/F";
838 case arrow::Type::DOUBLE:
839 return "/D";
840 default:
841 throw runtime_error("Unsupported arrow column type");
842 }
843}
844
845arrow::Result<std::shared_ptr<arrow::io::OutputStream>> TTreeFileSystem::OpenOutputStream(
846 const std::string& path,
847 const std::shared_ptr<const arrow::KeyValueMetadata>& metadata)
848{
849 arrow::dataset::FileSource source{path, shared_from_this()};
850 auto prefix = metadata->Get("branch_prefix");
851 if (prefix.ok()) {
852 return std::make_shared<TTreeOutputStream>(GetTree(source).get(), *prefix);
853 }
854 return std::make_shared<TTreeOutputStream>(GetTree(source).get(), "");
855}
856
857namespace
858{
859struct BranchInfo {
860 std::string name;
861 TBranch* ptr;
862 bool mVLA;
863};
864} // namespace
865
866auto arrowTypeFromROOT(EDataType type, int size)
867{
868 auto typeGenerator = [](std::shared_ptr<arrow::DataType> const& type, int size) -> std::shared_ptr<arrow::DataType> {
869 switch (size) {
870 case -1:
871 return arrow::list(type);
872 case 1:
873 return std::move(type);
874 default:
875 return arrow::fixed_size_list(type, size);
876 }
877 };
878
879 switch (type) {
880 case EDataType::kBool_t:
881 return typeGenerator(arrow::boolean(), size);
882 case EDataType::kUChar_t:
883 return typeGenerator(arrow::uint8(), size);
884 case EDataType::kUShort_t:
885 return typeGenerator(arrow::uint16(), size);
886 case EDataType::kUInt_t:
887 return typeGenerator(arrow::uint32(), size);
888 case EDataType::kULong64_t:
889 return typeGenerator(arrow::uint64(), size);
890 case EDataType::kChar_t:
891 return typeGenerator(arrow::int8(), size);
892 case EDataType::kShort_t:
893 return typeGenerator(arrow::int16(), size);
894 case EDataType::kInt_t:
895 return typeGenerator(arrow::int32(), size);
896 case EDataType::kLong64_t:
897 return typeGenerator(arrow::int64(), size);
898 case EDataType::kFloat_t:
899 return typeGenerator(arrow::float32(), size);
900 case EDataType::kDouble_t:
901 return typeGenerator(arrow::float64(), size);
902 default:
903 throw o2::framework::runtime_error_f("Unsupported branch type: %d", static_cast<int>(type));
904 }
905}
906
907// This is a datatype for branches which implies
908struct RootTransientIndexType : arrow::ExtensionType {
909};
910
911arrow::Result<std::shared_ptr<arrow::Schema>> TTreeFileFormat::Inspect(const arrow::dataset::FileSource& source) const
912{
913 auto fs = std::dynamic_pointer_cast<VirtualRootFileSystemBase>(source.filesystem());
914
915 if (!fs.get()) {
916 throw runtime_error_f("Unknown filesystem %s\n", source.filesystem()->type_name().c_str());
917 }
918 auto objectHandler = fs->GetObjectHandler(source);
919
920 if (!objectHandler->format->Equals(*this)) {
921 throw runtime_error_f("Unknown filesystem %s\n", source.filesystem()->type_name().c_str());
922 }
923
924 // Notice that we abuse of the API here and do not release the TTree,
925 // so that it's still managed by ROOT.
926 auto tree = objectHandler->GetObjectAsOwner<TTree>().release();
927
928 auto branches = tree->GetListOfBranches();
929 auto n = branches->GetEntries();
930
931 std::vector<std::shared_ptr<arrow::Field>> fields;
932
933 bool prevIsSize = false;
934 for (auto i = 0; i < n; ++i) {
935 auto branch = static_cast<TBranch*>(branches->At(i));
936 std::string name = branch->GetName();
937 if (prevIsSize && fields.back()->name() != name + "_size") {
938 throw runtime_error_f("Unexpected layout for VLA container %s.", branch->GetName());
939 }
940
941 if (name.ends_with("_size")) {
942 fields.emplace_back(std::make_shared<arrow::Field>(name, arrow::int32()));
943 prevIsSize = true;
944 } else {
945 static TClass* cls;
946 EDataType type;
947 branch->GetExpectedType(cls, type);
948
949 if (prevIsSize) {
950 fields.emplace_back(std::make_shared<arrow::Field>(name, arrowTypeFromROOT(type, -1)));
951 } else {
952 auto listSize = static_cast<TLeaf*>(branch->GetListOfLeaves()->At(0))->GetLenStatic();
953 fields.emplace_back(std::make_shared<arrow::Field>(name, arrowTypeFromROOT(type, listSize)));
954 }
955 prevIsSize = false;
956 }
957 }
958
959 if (fields.back()->name().ends_with("_size")) {
960 throw runtime_error_f("Missing values for VLA indices %s.", fields.back()->name().c_str());
961 }
962 return std::make_shared<arrow::Schema>(fields);
963}
964
966arrow::Result<std::shared_ptr<arrow::dataset::FileFragment>> TTreeFileFormat::MakeFragment(
967 arrow::dataset::FileSource source, arrow::compute::Expression partition_expression,
968 std::shared_ptr<arrow::Schema> physical_schema)
969{
970
971 return std::make_shared<TTreeFileFragment>(source, std::dynamic_pointer_cast<arrow::dataset::FileFormat>(shared_from_this()),
972 std::move(partition_expression),
973 physical_schema);
974}
975
977{
978 std::vector<TBranch*> branches;
979 std::vector<TBranch*> sizesBranches;
980 std::vector<std::shared_ptr<arrow::Array>> valueArrays;
981 std::vector<std::shared_ptr<arrow::Array>> sizeArrays;
982 std::vector<std::shared_ptr<arrow::DataType>> valueTypes;
983
984 std::vector<int64_t> valuesIdealBasketSize;
985 std::vector<int64_t> sizeIdealBasketSize;
986
987 std::vector<int64_t> typeSizes;
988 std::vector<int64_t> listSizes;
989 bool firstBasket = true;
990
991 // This is to create a batsket size according to the first batch.
992 void finaliseBasketSize(std::shared_ptr<arrow::RecordBatch> firstBatch)
993 {
994 O2_SIGNPOST_ID_FROM_POINTER(sid, root_arrow_fs, this);
995 O2_SIGNPOST_START(root_arrow_fs, sid, "finaliseBasketSize", "First batch with %lli rows received and %zu columns",
996 firstBatch->num_rows(), firstBatch->columns().size());
997 for (size_t i = 0; i < branches.size(); i++) {
998 auto* branch = branches[i];
999 auto* sizeBranch = sizesBranches[i];
1000
1001 int valueSize = valueTypes[i]->byte_width();
1002 if (listSizes[i] == 1) {
1003 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, sid, "finaliseBasketSize", "Branch %s exists and uses %d bytes per entry for %lli entries.",
1004 branch->GetName(), valueSize, firstBatch->num_rows());
1005 assert(sizeBranch == nullptr);
1006 branch->SetBasketSize(1024 + firstBatch->num_rows() * valueSize);
1007 } else if (listSizes[i] == -1) {
1008 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, sid, "finaliseBasketSize", "Branch %s exists and uses %d bytes per entry.",
1009 branch->GetName(), valueSize);
1010 // This should probably lookup the
1011 auto column = firstBatch->GetColumnByName(schema_->field(i)->name());
1012 auto list = std::static_pointer_cast<arrow::ListArray>(column);
1013 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.",
1014 branch->GetName(), sizeBranch->GetName(), list->length(), valueSize);
1015 branch->SetBasketSize(1024 + firstBatch->num_rows() * valueSize * list->length());
1016 sizeBranch->SetBasketSize(1024 + firstBatch->num_rows() * 4);
1017 } else {
1018 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, sid, "finaliseBasketSize", "Branch %s needed. There are %lli entries per array of size %d in that list.",
1019 branch->GetName(), listSizes[i], valueSize);
1020 assert(sizeBranch == nullptr);
1021 branch->SetBasketSize(1024 + firstBatch->num_rows() * valueSize * listSizes[i]);
1022 }
1023
1024 auto field = firstBatch->schema()->field(i);
1025 if (field->name().starts_with("fIndexArray")) {
1026 // One int per array to keep track of the size
1027 int idealBasketSize = 4 * firstBatch->num_rows() + 1024 + field->type()->byte_width() * firstBatch->num_rows(); // minimal additional size needed, otherwise we get 2 baskets
1028 int basketSize = std::max(32000, idealBasketSize); // keep a minimum value
1029 sizeBranch->SetBasketSize(basketSize);
1030 branch->SetBasketSize(basketSize);
1031 }
1032 }
1033 O2_SIGNPOST_END(root_arrow_fs, sid, "finaliseBasketSize", "Done");
1034 }
1035
1036 public:
1037 // Create the TTree based on the physical_schema, not the one in the batch.
1038 // The write method will have to reconcile the two schemas.
1039 TTreeFileWriter(std::shared_ptr<arrow::Schema> schema, std::shared_ptr<arrow::dataset::FileWriteOptions> options,
1040 std::shared_ptr<arrow::io::OutputStream> destination,
1041 arrow::fs::FileLocator destination_locator)
1042 : FileWriter(schema, options, destination, destination_locator)
1043 {
1044 // Batches have the same number of entries for each column.
1045 auto directoryStream = std::dynamic_pointer_cast<TDirectoryFileOutputStream>(destination_);
1046 auto treeStream = std::dynamic_pointer_cast<TTreeOutputStream>(destination_);
1047
1048 if (directoryStream.get()) {
1049 TDirectoryFile* dir = directoryStream->GetDirectory();
1050 dir->cd();
1051 auto* tree = new TTree(destination_locator_.path.c_str(), "");
1052 treeStream = std::make_shared<TTreeOutputStream>(tree, "");
1053 } else if (treeStream.get()) {
1054 // We already have a tree stream, let's derive a new one
1055 // with the destination_locator_.path as prefix for the branches
1056 // This way we can multiplex multiple tables in the same tree.
1057 auto* tree = treeStream->GetTree();
1058 treeStream = std::make_shared<TTreeOutputStream>(tree, destination_locator_.path);
1059 } else {
1060 // I could simply set a prefix here to merge to an already existing tree.
1061 throw std::runtime_error("Unsupported backend.");
1062 }
1063
1064 for (auto i = 0u; i < schema->fields().size(); ++i) {
1065 auto& field = schema->field(i);
1066 listSizes.push_back(1);
1067
1068 int valuesIdealBasketSize = 0;
1069 // Construct all the needed branches.
1070 switch (field->type()->id()) {
1071 case arrow::Type::FIXED_SIZE_LIST: {
1072 listSizes.back() = std::static_pointer_cast<arrow::FixedSizeListType>(field->type())->list_size();
1073 valuesIdealBasketSize = 1024 + valueTypes.back()->byte_width() * listSizes.back();
1074 valueTypes.push_back(field->type()->field(0)->type());
1075 sizesBranches.push_back(nullptr);
1076 std::string leafList = fmt::format("{}[{}]{}", field->name(), listSizes.back(), rootSuffixFromArrow(valueTypes.back()->id()));
1077 branches.push_back(treeStream->CreateBranch(field->name().c_str(), leafList.c_str()));
1078 } break;
1079 case arrow::Type::LIST: {
1080 valueTypes.push_back(field->type()->field(0)->type());
1081 std::string leafList = fmt::format("{}[{}_size]{}", field->name(), field->name(), rootSuffixFromArrow(valueTypes.back()->id()));
1082 listSizes.back() = -1; // VLA, we need to calculate it on the fly;
1083 std::string sizeLeafList = field->name() + "_size/I";
1084 sizesBranches.push_back(treeStream->CreateBranch((field->name() + "_size").c_str(), sizeLeafList.c_str()));
1085 branches.push_back(treeStream->CreateBranch(field->name().c_str(), leafList.c_str()));
1086 // Notice that this could be replaced by a better guess of the
1087 // average size of the list elements, but this is not trivial.
1088 } break;
1089 default: {
1090 valueTypes.push_back(field->type());
1091 std::string leafList = field->name() + rootSuffixFromArrow(valueTypes.back()->id());
1092 sizesBranches.push_back(nullptr);
1093 branches.push_back(treeStream->CreateBranch(field->name().c_str(), leafList.c_str()));
1094 } break;
1095 }
1096 }
1097 // We create the branches from the schema
1098 }
1099
1100 arrow::Status Write(const std::shared_ptr<arrow::RecordBatch>& batch) override
1101 {
1102 if (firstBasket) {
1103 firstBasket = false;
1104 finaliseBasketSize(batch);
1105 }
1106
1107 // Support writing empty tables
1108 if (batch->columns().empty() || batch->num_rows() == 0) {
1109 return arrow::Status::OK();
1110 }
1111
1112 // Batches have the same number of entries for each column.
1113 auto directoryStream = std::dynamic_pointer_cast<TDirectoryFileOutputStream>(destination_);
1114 TTree* tree = nullptr;
1115 if (directoryStream.get()) {
1116 TDirectoryFile* dir = directoryStream->GetDirectory();
1117 tree = (TTree*)dir->Get(destination_locator_.path.c_str());
1118 }
1119 auto treeStream = std::dynamic_pointer_cast<TTreeOutputStream>(destination_);
1120
1121 if (!tree) {
1122 // I could simply set a prefix here to merge to an already existing tree.
1123 throw std::runtime_error("Unsupported backend.");
1124 }
1125
1126 for (auto i = 0u; i < batch->columns().size(); ++i) {
1127 auto column = batch->column(i);
1128 auto& field = batch->schema()->field(i);
1129
1130 valueArrays.push_back(nullptr);
1131
1132 switch (field->type()->id()) {
1133 case arrow::Type::FIXED_SIZE_LIST: {
1134 auto list = std::static_pointer_cast<arrow::FixedSizeListArray>(column);
1135 if (list->list_type()->field(0)->type()->id() == arrow::Type::BOOL) {
1136 int64_t length = list->length() * list->list_type()->list_size();
1137 arrow::UInt8Builder builder;
1138 auto ok = builder.Reserve(length);
1139 // I need to build an array of uint8_t for the conversion to ROOT which uses
1140 // bytes for boolans.
1141 auto boolArray = std::static_pointer_cast<arrow::BooleanArray>(list->values());
1142 for (int64_t i = 0; i < length; ++i) {
1143 if (boolArray->IsValid(i)) {
1144 // Expand each boolean value (true/false) to uint8 (1/0)
1145 uint8_t value = boolArray->Value(i) ? 1 : 0;
1146 auto ok = builder.Append(value);
1147 } else {
1148 // Append null for invalid entries
1149 auto ok = builder.AppendNull();
1150 }
1151 }
1152 valueArrays.back() = *builder.Finish();
1153 } else {
1154 valueArrays.back() = list->values();
1155 }
1156 } break;
1157 case arrow::Type::LIST: {
1158 auto list = std::static_pointer_cast<arrow::ListArray>(column);
1159 valueArrays.back() = list->values();
1160 } break;
1161 case arrow::Type::BOOL: {
1162 // In case of arrays of booleans, we need to go back to their
1163 // char based representation for ROOT to save them.
1164 auto boolArray = std::static_pointer_cast<arrow::BooleanArray>(column);
1165
1166 int64_t length = boolArray->length();
1167 arrow::UInt8Builder builder;
1168 auto ok = builder.Reserve(length);
1169
1170 for (int64_t i = 0; i < length; ++i) {
1171 if (boolArray->IsValid(i)) {
1172 // Expand each boolean value (true/false) to uint8 (1/0)
1173 uint8_t value = boolArray->Value(i) ? 1 : 0;
1174 auto ok = builder.Append(value);
1175 } else {
1176 // Append null for invalid entries
1177 auto ok = builder.AppendNull();
1178 }
1179 }
1180 valueArrays.back() = *builder.Finish();
1181 } break;
1182 default:
1183 valueArrays.back() = column;
1184 }
1185 }
1186
1187 int64_t pos = 0;
1188 while (pos < batch->num_rows()) {
1189 for (size_t bi = 0; bi < branches.size(); ++bi) {
1190 auto* branch = branches[bi];
1191 auto* sizeBranch = sizesBranches[bi];
1192 auto array = batch->column(bi);
1193 auto& field = batch->schema()->field(bi);
1194 auto& listSize = listSizes[bi];
1195 auto valueType = valueTypes[bi];
1196 auto valueArray = valueArrays[bi];
1197
1198 switch (field->type()->id()) {
1199 case arrow::Type::LIST: {
1200 auto list = std::static_pointer_cast<arrow::ListArray>(array);
1201 listSize = list->value_length(pos);
1202 uint8_t const* buffer = std::static_pointer_cast<arrow::PrimitiveArray>(valueArray)->values()->data() + array->offset() + list->value_offset(pos) * valueType->byte_width();
1203 branch->SetAddress((void*)buffer);
1204 sizeBranch->SetAddress(&listSize);
1205 } break;
1206 case arrow::Type::FIXED_SIZE_LIST:
1207 default: {
1208 // needed for the boolean case, I should probably cache this.
1209 auto byteWidth = valueType->byte_width() ? valueType->byte_width() : 1;
1210 uint8_t const* buffer = std::static_pointer_cast<arrow::PrimitiveArray>(valueArray)->values()->data() + array->offset() + pos * listSize * byteWidth;
1211 branch->SetAddress((void*)buffer);
1212 };
1213 }
1214 }
1215 tree->Fill();
1216 ++pos;
1217 }
1218 return arrow::Status::OK();
1219 }
1220
1221 arrow::Future<> FinishInternal() override
1222 {
1223 auto treeStream = std::dynamic_pointer_cast<TTreeOutputStream>(destination_);
1224 auto* tree = treeStream->GetTree();
1225 tree->Write("", TObject::kOverwrite);
1226 tree->SetDirectory(nullptr);
1227
1228 return {};
1229 };
1230};
1231arrow::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
1232{
1233 auto writer = std::make_shared<TTreeFileWriter>(schema, options, destination, destination_locator);
1234 return std::dynamic_pointer_cast<arrow::dataset::FileWriter>(writer);
1235}
1236
1237std::shared_ptr<arrow::dataset::FileWriteOptions> TTreeFileFormat::DefaultWriteOptions()
1238{
1239 std::shared_ptr<TTreeFileWriteOptions> options(
1240 new TTreeFileWriteOptions(shared_from_this()));
1241 return options;
1242}
1243
1245
1249} // namespace o2::framework
std::shared_ptr< arrow::Schema > schema
std::vector< std::shared_ptr< arrow::Field > > fields
int32_t i
o2::raw::RawFileWriter * raw
uint32_t op
#define DEFINE_DPL_PLUGIN_INSTANCE(NAME, KIND)
Definition Plugins.h:112
#define DEFINE_DPL_PLUGINS_END
Definition Plugins.h:115
#define DEFINE_DPL_PLUGINS_BEGIN
Definition Plugins.h:107
uint16_t pos
Definition RawData.h:3
#define O2_DECLARE_DYNAMIC_LOG(name)
Definition Signpost.h:490
#define O2_SIGNPOST_ID_FROM_POINTER(name, log, pointer)
Definition Signpost.h:506
#define O2_SIGNPOST_END(log, id, name, format,...)
Definition Signpost.h:609
#define O2_SIGNPOST_EVENT_EMIT(log, id, name, format,...)
Definition Signpost.h:523
#define O2_SIGNPOST_START(log, id, name, format,...)
Definition Signpost.h:603
TBranch * ptr
bool mVLA
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
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()).
TTreeFileFormat(size_t &totalCompressedSize, size_t &totalUncompressedSize)
arrow::Result< arrow::RecordBatchGenerator > ScanBatchesAsync(const std::shared_ptr< arrow::dataset::ScanOptions > &options, const std::shared_ptr< arrow::dataset::FileFragment > &fragment) const override
std::shared_ptr< arrow::dataset::FileWriteOptions > DefaultWriteOptions() override
arrow::Result< bool > IsSupported(const arrow::dataset::FileSource &source) const override
arrow::Result< std::shared_ptr< arrow::Schema > > Inspect(const arrow::dataset::FileSource &source) const override
std::string type_name() const override
bool Equals(const FileFormat &other) const override
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
~TTreeFileFormat() override=default
arrow::Result< std::shared_ptr< arrow::dataset::FileFragment > > MakeFragment(arrow::dataset::FileSource source, arrow::compute::Expression partition_expression, std::shared_ptr< arrow::Schema > physical_schema) override
Create a FileFragment for a FileSource.
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
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
TBranch * CreateBranch(char const *branchName, char const *sizeBranch)
TTreeOutputStream(TTree *, std::string branchPrefix)
GLdouble n
Definition glcorearb.h:1982
GLint GLsizei count
Definition glcorearb.h:399
GLuint64EXT * result
Definition glcorearb.h:5662
GLuint buffer
Definition glcorearb.h:655
GLsizeiptr size
Definition glcorearb.h:659
GLuint GLsizei const GLuint const GLintptr * offsets
Definition glcorearb.h:2595
GLenum array
Definition glcorearb.h:4274
GLuint const GLchar * name
Definition glcorearb.h:781
GLdouble f
Definition glcorearb.h:310
GLsizei GLsizei GLchar * source
Definition glcorearb.h:798
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLenum target
Definition glcorearb.h:1641
GLint GLint GLsizei GLint GLenum GLenum type
Definition glcorearb.h:275
GLboolean * data
Definition glcorearb.h:298
GLintptr offset
Definition glcorearb.h:660
GLuint GLsizei GLsizei * length
Definition glcorearb.h:790
GLsizei const GLchar *const * path
Definition glcorearb.h:3591
GLint ref
Definition glcorearb.h:291
GLint GLint GLsizei GLint GLenum format
Definition glcorearb.h:275
Defining ITS Vertex explicitly as messageable.
Definition Cartesian.h:288
RuntimeErrorRef runtime_error(const char *)
TBufferFile & rootBuffer()
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)
Definition BigEndian.h:26
RuntimeErrorRef runtime_error_f(const char *,...)
std::shared_ptr< arrow::Buffer > targetBuffer
std::function< std::shared_ptr< arrow::dataset::FileWriteOptions >()> options
std::shared_ptr< o2::framework::TTreeFileFormat > format
VectorOfTObjectPtrs other
ctfTree Write()
std::unique_ptr< TTree > tree((TTree *) flIn.Get(std::string(o2::base::NameConf::CTFTREENAME).c_str()))
std::vector< ReadoutWindowData > rows