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:
396 : TTreeFileSystem(),
397 mTree(tree)
398 {
399 }
400
401 arrow::Result<arrow::fs::FileInfo> GetFileInfo(std::string const& path) override;
402
403 std::string type_name() const override
404 {
405 return "ttree";
406 }
407
408 std::shared_ptr<RootObjectHandler> GetObjectHandler(arrow::dataset::FileSource source) override
409 {
410 return std::make_shared<RootObjectHandler>((void*)mTree.get(), std::make_shared<TTreeFileFormat>(mTotCompressedSize, mTotUncompressedSize));
411 }
412
413 std::unique_ptr<TTree>& GetTree(arrow::dataset::FileSource) override
414 {
415 // Simply return the only TTree we have
416 return mTree;
417 }
418
419 private:
420 size_t mTotUncompressedSize;
421 size_t mTotCompressedSize;
422 std::unique_ptr<TTree> mTree;
423};
424
425arrow::Result<arrow::fs::FileInfo> SingleTreeFileSystem::GetFileInfo(std::string const& path)
426{
427 arrow::dataset::FileSource source(path, shared_from_this());
428 arrow::fs::FileInfo result;
429 result.set_path(path);
430 result.set_type(arrow::fs::FileType::File);
431 return result;
432}
433
434// A fragment which holds a tree
436{
437 public:
438 TTreeFileFragment(arrow::dataset::FileSource source,
439 std::shared_ptr<arrow::dataset::FileFormat> format,
440 arrow::compute::Expression partition_expression,
441 std::shared_ptr<arrow::Schema> physical_schema)
442 : FileFragment(source, format, std::move(partition_expression), physical_schema)
443 {
444 auto rootFS = std::dynamic_pointer_cast<VirtualRootFileSystemBase>(this->source().filesystem());
445 if (rootFS.get() == nullptr) {
446 throw runtime_error_f("Unknown filesystem %s when reading %s.",
447 source.filesystem()->type_name().c_str(), source.path().c_str());
448 }
449 auto objectHandler = rootFS->GetObjectHandler(source);
450 if (!objectHandler->format->Equals(*format)) {
451 throw runtime_error_f("Cannot read source %s with format %s to pupulate a TTreeFileFragment.",
452 source.path().c_str(), objectHandler->format->type_name().c_str());
453 };
454 mTree = objectHandler->GetObjectAsOwner<TTree>();
455 }
456
457 TTree* GetTree()
458 {
459 return mTree.get();
460 }
461
462 std::vector<ReadOps>& ops()
463 {
464 return mOps;
465 }
466
469 std::shared_ptr<arrow::Buffer> GetPlaceholderForOp(size_t size)
470 {
471 return std::make_shared<arrow::Buffer>((uint8_t*)(mOps.size() - 1), size);
472 }
473
474 private:
475 std::unique_ptr<TTree> mTree;
476 std::vector<ReadOps> mOps;
477};
478
479// An arrow outputstream which allows to write to a TTree. Eventually
480// with a prefix for the branches.
482{
483 public:
484 // Using a pointer means that the tree itself is owned by another
485 // class
486 TTreeOutputStream(TTree*, std::string branchPrefix);
487
488 arrow::Status Close() override;
489
490 arrow::Result<int64_t> Tell() const override;
491
492 arrow::Status Write(const void* data, int64_t nbytes) override;
493
494 bool closed() const override;
495
496 TBranch* CreateBranch(char const* branchName, char const* sizeBranch);
497
498 TTree* GetTree()
499 {
500 return mTree;
501 }
502
503 private:
504 TTree* mTree;
505 std::string mBranchPrefix;
506};
507
508// An arrow outputstream which allows to write to a ttree
509// @a branch prefix is to be used to identify a set of branches which all belong to
510// the same table.
511TTreeOutputStream::TTreeOutputStream(TTree* f, std::string branchPrefix)
512 : mTree(f),
513 mBranchPrefix(std::move(branchPrefix))
514{
515}
516
518{
519 if (mTree->GetCurrentFile() == nullptr) {
520 return arrow::Status::Invalid("Cannot close a tree not attached to a file");
521 }
522 mTree->GetCurrentFile()->Close();
523 return arrow::Status::OK();
524}
525
526arrow::Result<int64_t> TTreeOutputStream::Tell() const
527{
528 return arrow::Result<int64_t>(arrow::Status::NotImplemented("Cannot move"));
529}
530
531arrow::Status TTreeOutputStream::Write(const void* data, int64_t nbytes)
532{
533 return arrow::Status::NotImplemented("Cannot write raw bytes to a TTree");
534}
535
537{
538 // A standalone tree is never closed.
539 if (mTree->GetCurrentFile() == nullptr) {
540 return false;
541 }
542 return mTree->GetCurrentFile()->IsOpen() == false;
543}
544
545TBranch* TTreeOutputStream::CreateBranch(char const* branchName, char const* sizeBranch)
546{
547 if (mBranchPrefix.empty() == true) {
548 return mTree->Branch(branchName, (char*)nullptr, sizeBranch);
549 }
550 return mTree->Branch((mBranchPrefix + "/" + branchName).c_str(), (char*)nullptr, (mBranchPrefix + sizeBranch).c_str());
551}
552
556 std::shared_ptr<o2::framework::TTreeFileFormat> format = nullptr;
557};
558
561 {
562 auto context = new TTreePluginContext;
563 context->format = std::make_shared<o2::framework::TTreeFileFormat>(context->totalCompressedSize, context->totalUncompressedSize);
564 return new RootArrowFactory{
565 .options = [context]() { return context->format->DefaultWriteOptions(); },
566 .format = [context]() { return context->format; },
567 .deferredOutputStreamer = [](std::shared_ptr<arrow::dataset::FileFragment> fragment, const std::shared_ptr<arrow::ResizableBuffer>& buffer) -> std::shared_ptr<arrow::io::OutputStream> {
568 auto treeFragment = std::dynamic_pointer_cast<TTreeFileFragment>(fragment);
569 return std::make_shared<TTreeDeferredReadOutputStream>(treeFragment->ops(), buffer);
570 }};
571 }
572};
573
579
581 uint32_t offset = 0;
582 std::span<int> offsets;
583 int readEntries = 0;
584 int count = 0;
585 auto* tPtrOffset = reinterpret_cast<int*>(op.targetBuffer->mutable_data());
586 offsets = std::span<int>{tPtrOffset, tPtrOffset + op.rootBranchEntries + 1};
587
588 // read sizes first
589 rootBuffer.Reset();
590 while (readEntries < op.rootBranchEntries) {
591 auto readLast = op.branch->GetBulkRead().GetEntriesSerialized(readEntries, rootBuffer);
592 if (readLast == -1) {
593 throw runtime_error_f("Unable to read from branch %s.", op.branch->GetName());
594 }
595 readEntries += readLast;
596 for (auto i = 0; i < readLast; ++i) {
597 offsets[count++] = (int)offset;
598 uint32_t raw = reinterpret_cast<uint32_t*>(rootBuffer.GetCurrent())[i];
599 offset += (std::endian::native == std::endian::little) ? __builtin_bswap32(raw) : raw;
600 }
601 }
603 op.offsetCount = offset;
604};
605
606arrow::Result<arrow::RecordBatchGenerator> TTreeFileFormat::ScanBatchesAsync(
607 const std::shared_ptr<arrow::dataset::ScanOptions>& options,
608 const std::shared_ptr<arrow::dataset::FileFragment>& fragment) const
609{
610 assert(options->dataset_schema != nullptr);
611 // This is the schema we want to read
612 auto dataset_schema = options->dataset_schema;
613 auto treeFragment = std::dynamic_pointer_cast<TTreeFileFragment>(fragment);
614 if (treeFragment.get() == nullptr) {
615 return {arrow::Status::NotImplemented("Not a ttree fragment")};
616 }
617
618 auto generator = [pool = options->pool, treeFragment, dataset_schema, &totalCompressedSize = mTotCompressedSize,
619 &totalUncompressedSize = mTotUncompressedSize]() -> arrow::Future<std::shared_ptr<arrow::RecordBatch>> {
620 O2_SIGNPOST_ID_FROM_POINTER(tid, root_arrow_fs, treeFragment->GetTree());
621 O2_SIGNPOST_START(root_arrow_fs, tid, "Generator", "Creating batch for tree %{public}s", treeFragment->GetTree()->GetName());
622 std::vector<std::shared_ptr<arrow::Array>> columns;
623 std::vector<std::shared_ptr<arrow::Field>> fields = dataset_schema->fields();
624 auto physical_schema = *treeFragment->ReadPhysicalSchema();
625
626 if (dataset_schema->num_fields() > physical_schema->num_fields()) {
627 throw runtime_error_f("One TTree must have all the fields requested in a table");
628 }
629
630 // Register physical fields into the cache
631 std::vector<BranchFieldMapping> mappings;
632
633 // We need to count the number of readops to avoid moving the vector.
634 int opsCount = 0;
635 for (int fi = 0; fi < dataset_schema->num_fields(); ++fi) {
636 auto dataset_field = dataset_schema->field(fi);
637 // This is needed because for now the dataset_field
638 // is actually the schema of the ttree
639 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid, "Generator", "Processing dataset field %{public}s.", dataset_field->name().c_str());
640 int physicalFieldIdx = physical_schema->GetFieldIndex(dataset_field->name());
641
642 if (physicalFieldIdx < 0) {
643 throw runtime_error_f("Cannot find physical field associated to %s. Possible fields: %s",
644 dataset_field->name().c_str(), physical_schema->ToString().c_str());
645 }
646 if (physicalFieldIdx > 0 && physical_schema->field(physicalFieldIdx - 1)->name().ends_with("_size")) {
647 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid, "Generator", "Field %{public}s has sizes in %{public}s.", dataset_field->name().c_str(),
648 physical_schema->field(physicalFieldIdx - 1)->name().c_str());
649 mappings.push_back({physicalFieldIdx, physicalFieldIdx - 1, fi});
650 opsCount += 2;
651 } else {
652 if (physicalFieldIdx > 0) {
653 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid, "Generator", "Field %{public}s previous field is %{public}s.", dataset_field->name().c_str(),
654 physical_schema->field(physicalFieldIdx - 1)->name().c_str());
655 }
656 mappings.push_back({physicalFieldIdx, -1, fi});
657 opsCount++;
658 }
659 }
660
661 auto* tree = treeFragment->GetTree();
662 auto branches = tree->GetListOfBranches();
663 size_t totalTreeSize = 0;
664 std::vector<TBranch*> selectedBranches;
665 for (auto& mapping : mappings) {
666 selectedBranches.push_back((TBranch*)branches->At(mapping.mainBranchIdx));
667 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid, "Generator", "Adding branch %{public}s to stream.", selectedBranches.back()->GetName());
668 totalTreeSize += selectedBranches.back()->GetTotalSize();
669 if (mapping.vlaIdx != -1) {
670 selectedBranches.push_back((TBranch*)branches->At(mapping.vlaIdx));
671 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid, "Generator", "Adding branch %{public}s to stream.", selectedBranches.back()->GetName());
672 totalTreeSize += selectedBranches.back()->GetTotalSize();
673 }
674 }
675
676 size_t cacheSize = std::max(std::min(totalTreeSize, 25000000UL), 1000000UL);
677 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid, "Generator", "Resizing cache to %zu.", cacheSize);
678 tree->SetCacheSize(cacheSize);
679 for (auto* branch : selectedBranches) {
680 tree->AddBranchToCache(branch, false);
681 }
682 tree->StopCacheLearningPhase();
683
684 // Intermediate buffer to bulk read. Two for now
685 std::vector<ReadOps>& ops = treeFragment->ops();
686 ops.clear();
687 ops.reserve(opsCount);
688 for (size_t mi = 0; mi < mappings.size(); ++mi) {
689 BranchFieldMapping mapping = mappings[mi];
690 // The field actually on disk
691 auto datasetField = dataset_schema->field(mapping.datasetFieldIdx);
692 auto physicalField = physical_schema->field(mapping.mainBranchIdx);
693
694 if (mapping.vlaIdx != -1) {
695 auto* branch = (TBranch*)branches->At(mapping.vlaIdx);
696 ops.emplace_back(ReadOps{
697 .branch = branch,
698 .rootBranchEntries = branch->GetEntries(),
699 .typeSize = 4,
700 .listSize = 1,
701 .kind = ReadOpKind::Offsets,
702 });
703 auto& op = ops.back();
704 ARROW_ASSIGN_OR_RAISE(op.targetBuffer, arrow::AllocateBuffer((op.rootBranchEntries + 1) * op.typeSize, pool));
705 // Offsets need to be read immediately to know how many values are there
707 }
708 ops.push_back({});
709 auto& valueOp = ops.back();
710 valueOp.branch = (TBranch*)branches->At(mapping.mainBranchIdx);
711 valueOp.rootBranchEntries = valueOp.branch->GetEntries();
712 // In case this is a vla, we set the offsetCount as totalEntries
713 // In case we read booleans we need a special coversion from bytes to bits.
714 auto listType = std::dynamic_pointer_cast<arrow::FixedSizeListType>(datasetField->type());
715 valueOp.typeSize = physicalField->type()->byte_width();
716 // Notice how we are not (yet) allocating buffers at this point. We merely
717 // create placeholders to subsequently fill.
718 if ((datasetField->type() == arrow::boolean())) {
719 valueOp.kind = ReadOpKind::Booleans;
720 valueOp.listSize = 1;
721 valueOp.targetBuffer = treeFragment->GetPlaceholderForOp((valueOp.rootBranchEntries + 7) / 8);
722 } else if (listType && datasetField->type()->field(0)->type() == arrow::boolean()) {
723 valueOp.typeSize = physicalField->type()->field(0)->type()->byte_width();
724 valueOp.listSize = listType->list_size();
725 valueOp.kind = ReadOpKind::Booleans;
726 valueOp.targetBuffer = treeFragment->GetPlaceholderForOp((valueOp.rootBranchEntries * valueOp.listSize) / 8 + 1);
727 } else if (mapping.vlaIdx != -1) {
728 valueOp.typeSize = physicalField->type()->field(0)->type()->byte_width();
729 valueOp.listSize = -1;
730 // -1 is the current one, -2 is the one with for the offsets
731 valueOp.kind = ReadOpKind::VLA;
732 valueOp.targetBuffer = treeFragment->GetPlaceholderForOp(ops[ops.size() - 2].offsetCount * valueOp.typeSize);
733 } else if (listType) {
734 valueOp.kind = ReadOpKind::Values;
735 valueOp.listSize = listType->list_size();
736 valueOp.typeSize = physicalField->type()->field(0)->type()->byte_width();
737 valueOp.targetBuffer = treeFragment->GetPlaceholderForOp(valueOp.rootBranchEntries * valueOp.typeSize * valueOp.listSize);
738 } else {
739 valueOp.typeSize = physicalField->type()->byte_width();
740 valueOp.kind = ReadOpKind::Values;
741 valueOp.listSize = 1;
742 valueOp.targetBuffer = treeFragment->GetPlaceholderForOp(valueOp.rootBranchEntries * valueOp.typeSize);
743 }
744 arrow::Status status;
745 std::shared_ptr<arrow::Array> array;
746
747 if (listType) {
748 auto vdata = std::make_shared<arrow::ArrayData>(datasetField->type()->field(0)->type(), valueOp.rootBranchEntries * valueOp.listSize,
749 std::vector<std::shared_ptr<arrow::Buffer>>{nullptr, valueOp.targetBuffer});
750 array = std::make_shared<arrow::FixedSizeListArray>(datasetField->type(), valueOp.rootBranchEntries, arrow::MakeArray(vdata));
751 // This is a vla, there is also an offset op
752 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid, "Op", "Created op for branch %{public}s with %lli entries, size of the buffer %lli.",
753 valueOp.branch->GetName(),
754 valueOp.rootBranchEntries,
755 valueOp.targetBuffer->size());
756 } else if (mapping.vlaIdx != -1) {
757 auto& offsetOp = ops[ops.size() - 2];
758 auto vdata = std::make_shared<arrow::ArrayData>(datasetField->type()->field(0)->type(), offsetOp.offsetCount,
759 std::vector<std::shared_ptr<arrow::Buffer>>{nullptr, valueOp.targetBuffer});
760 // We have pushed an offset op if this was the case.
761 array = std::make_shared<arrow::ListArray>(datasetField->type(), offsetOp.rootBranchEntries, offsetOp.targetBuffer, arrow::MakeArray(vdata));
762 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid, "Op", "Created op for branch %{public}s with %lli entries, size of the buffer %lli.",
763 offsetOp.branch->GetName(), offsetOp.rootBranchEntries, offsetOp.targetBuffer->size());
764 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid, "Op", "Created op for branch %{public}s with %lli entries, size of the buffer %lli.",
765 valueOp.branch->GetName(),
766 offsetOp.offsetCount,
767 valueOp.targetBuffer->size());
768 } else {
769 auto data = std::make_shared<arrow::ArrayData>(datasetField->type(), valueOp.rootBranchEntries,
770 std::vector<std::shared_ptr<arrow::Buffer>>{nullptr, valueOp.targetBuffer});
771 array = arrow::MakeArray(data);
772 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, tid, "Op", "Created op for branch %{public}s with %lli entries, size of the buffer %lli.",
773 valueOp.branch->GetName(),
774 valueOp.rootBranchEntries,
775 valueOp.targetBuffer->size());
776 }
777
778 columns.push_back(array);
779 }
780
781 // Do the actual filling of the buffers. This happens after we have created the whole structure
782 // so that we can read directly in shared memory.
783 int64_t rows = -1;
784 for (size_t i = 0; i < ops.size(); ++i) {
785 auto& op = ops[i];
786 if (rows == -1 && op.kind != ReadOpKind::VLA) {
787 rows = op.rootBranchEntries;
788 }
789 if (rows == -1 && op.kind == ReadOpKind::VLA) {
790 auto& offsetOp = ops[i - 1];
791 rows = offsetOp.rootBranchEntries;
792 }
793 if (op.kind != ReadOpKind::VLA && rows != op.rootBranchEntries) {
794 throw runtime_error_f("Unmatching number of rows for branch %s. Expected %lli, found %lli", op.branch->GetName(), rows, op.rootBranchEntries);
795 }
796 if (op.kind == ReadOpKind::VLA && rows != ops[i - 1].rootBranchEntries) {
797 throw runtime_error_f("Unmatching number of rows for branch %s. Expected %lli, found %lli", op.branch->GetName(), rows, ops[i - 1].offsetCount);
798 }
799 }
800
801 auto batch = arrow::RecordBatch::Make(dataset_schema, rows, columns);
802 totalCompressedSize += tree->GetZipBytes();
803 totalUncompressedSize += tree->GetTotBytes();
804 O2_SIGNPOST_END(root_arrow_fs, tid, "Generator", "Done creating batch compressed:%zu uncompressed:%zu", totalCompressedSize, totalUncompressedSize);
805 return batch;
806 };
807 return generator;
808}
809
810char const* rootSuffixFromArrow(arrow::Type::type id)
811{
812 switch (id) {
813 case arrow::Type::BOOL:
814 return "/O";
815 case arrow::Type::UINT8:
816 return "/b";
817 case arrow::Type::UINT16:
818 return "/s";
819 case arrow::Type::UINT32:
820 return "/i";
821 case arrow::Type::UINT64:
822 return "/l";
823 case arrow::Type::INT8:
824 return "/B";
825 case arrow::Type::INT16:
826 return "/S";
827 case arrow::Type::INT32:
828 return "/I";
829 case arrow::Type::INT64:
830 return "/L";
831 case arrow::Type::FLOAT:
832 return "/F";
833 case arrow::Type::DOUBLE:
834 return "/D";
835 default:
836 throw runtime_error("Unsupported arrow column type");
837 }
838}
839
840arrow::Result<std::shared_ptr<arrow::io::OutputStream>> TTreeFileSystem::OpenOutputStream(
841 const std::string& path,
842 const std::shared_ptr<const arrow::KeyValueMetadata>& metadata)
843{
844 arrow::dataset::FileSource source{path, shared_from_this()};
845 auto prefix = metadata->Get("branch_prefix");
846 if (prefix.ok()) {
847 return std::make_shared<TTreeOutputStream>(GetTree(source).get(), *prefix);
848 }
849 return std::make_shared<TTreeOutputStream>(GetTree(source).get(), "");
850}
851
852namespace
853{
854struct BranchInfo {
855 std::string name;
856 TBranch* ptr;
857 bool mVLA;
858};
859} // namespace
860
861auto arrowTypeFromROOT(EDataType type, int size)
862{
863 auto typeGenerator = [](std::shared_ptr<arrow::DataType> const& type, int size) -> std::shared_ptr<arrow::DataType> {
864 switch (size) {
865 case -1:
866 return arrow::list(type);
867 case 1:
868 return std::move(type);
869 default:
870 return arrow::fixed_size_list(type, size);
871 }
872 };
873
874 switch (type) {
875 case EDataType::kBool_t:
876 return typeGenerator(arrow::boolean(), size);
877 case EDataType::kUChar_t:
878 return typeGenerator(arrow::uint8(), size);
879 case EDataType::kUShort_t:
880 return typeGenerator(arrow::uint16(), size);
881 case EDataType::kUInt_t:
882 return typeGenerator(arrow::uint32(), size);
883 case EDataType::kULong64_t:
884 return typeGenerator(arrow::uint64(), size);
885 case EDataType::kChar_t:
886 return typeGenerator(arrow::int8(), size);
887 case EDataType::kShort_t:
888 return typeGenerator(arrow::int16(), size);
889 case EDataType::kInt_t:
890 return typeGenerator(arrow::int32(), size);
891 case EDataType::kLong64_t:
892 return typeGenerator(arrow::int64(), size);
893 case EDataType::kFloat_t:
894 return typeGenerator(arrow::float32(), size);
895 case EDataType::kDouble_t:
896 return typeGenerator(arrow::float64(), size);
897 default:
898 throw o2::framework::runtime_error_f("Unsupported branch type: %d", static_cast<int>(type));
899 }
900}
901
902// This is a datatype for branches which implies
903struct RootTransientIndexType : arrow::ExtensionType {
904};
905
906arrow::Result<std::shared_ptr<arrow::Schema>> TTreeFileFormat::Inspect(const arrow::dataset::FileSource& source) const
907{
908 auto fs = std::dynamic_pointer_cast<VirtualRootFileSystemBase>(source.filesystem());
909
910 if (!fs.get()) {
911 throw runtime_error_f("Unknown filesystem %s\n", source.filesystem()->type_name().c_str());
912 }
913 auto objectHandler = fs->GetObjectHandler(source);
914
915 if (!objectHandler->format->Equals(*this)) {
916 throw runtime_error_f("Unknown filesystem %s\n", source.filesystem()->type_name().c_str());
917 }
918
919 // Notice that we abuse of the API here and do not release the TTree,
920 // so that it's still managed by ROOT.
921 auto tree = objectHandler->GetObjectAsOwner<TTree>().release();
922
923 auto branches = tree->GetListOfBranches();
924 auto n = branches->GetEntries();
925
926 std::vector<std::shared_ptr<arrow::Field>> fields;
927
928 bool prevIsSize = false;
929 for (auto i = 0; i < n; ++i) {
930 auto branch = static_cast<TBranch*>(branches->At(i));
931 std::string name = branch->GetName();
932 if (prevIsSize && fields.back()->name() != name + "_size") {
933 throw runtime_error_f("Unexpected layout for VLA container %s.", branch->GetName());
934 }
935
936 if (name.ends_with("_size")) {
937 fields.emplace_back(std::make_shared<arrow::Field>(name, arrow::int32()));
938 prevIsSize = true;
939 } else {
940 static TClass* cls;
941 EDataType type;
942 branch->GetExpectedType(cls, type);
943
944 if (prevIsSize) {
945 fields.emplace_back(std::make_shared<arrow::Field>(name, arrowTypeFromROOT(type, -1)));
946 } else {
947 auto listSize = static_cast<TLeaf*>(branch->GetListOfLeaves()->At(0))->GetLenStatic();
948 fields.emplace_back(std::make_shared<arrow::Field>(name, arrowTypeFromROOT(type, listSize)));
949 }
950 prevIsSize = false;
951 }
952 }
953
954 if (fields.back()->name().ends_with("_size")) {
955 throw runtime_error_f("Missing values for VLA indices %s.", fields.back()->name().c_str());
956 }
957 return std::make_shared<arrow::Schema>(fields);
958}
959
961arrow::Result<std::shared_ptr<arrow::dataset::FileFragment>> TTreeFileFormat::MakeFragment(
962 arrow::dataset::FileSource source, arrow::compute::Expression partition_expression,
963 std::shared_ptr<arrow::Schema> physical_schema)
964{
965
966 return std::make_shared<TTreeFileFragment>(source, std::dynamic_pointer_cast<arrow::dataset::FileFormat>(shared_from_this()),
967 std::move(partition_expression),
968 physical_schema);
969}
970
972{
973 std::vector<TBranch*> branches;
974 std::vector<TBranch*> sizesBranches;
975 std::vector<std::shared_ptr<arrow::Array>> valueArrays;
976 std::vector<std::shared_ptr<arrow::Array>> sizeArrays;
977 std::vector<std::shared_ptr<arrow::DataType>> valueTypes;
978
979 std::vector<int64_t> valuesIdealBasketSize;
980 std::vector<int64_t> sizeIdealBasketSize;
981
982 std::vector<int64_t> typeSizes;
983 std::vector<int64_t> listSizes;
984 bool firstBasket = true;
985
986 // This is to create a batsket size according to the first batch.
987 void finaliseBasketSize(std::shared_ptr<arrow::RecordBatch> firstBatch)
988 {
989 O2_SIGNPOST_ID_FROM_POINTER(sid, root_arrow_fs, this);
990 O2_SIGNPOST_START(root_arrow_fs, sid, "finaliseBasketSize", "First batch with %lli rows received and %zu columns",
991 firstBatch->num_rows(), firstBatch->columns().size());
992 for (size_t i = 0; i < branches.size(); i++) {
993 auto* branch = branches[i];
994 auto* sizeBranch = sizesBranches[i];
995
996 int valueSize = valueTypes[i]->byte_width();
997 if (listSizes[i] == 1) {
998 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, sid, "finaliseBasketSize", "Branch %s exists and uses %d bytes per entry for %lli entries.",
999 branch->GetName(), valueSize, firstBatch->num_rows());
1000 assert(sizeBranch == nullptr);
1001 branch->SetBasketSize(1024 + firstBatch->num_rows() * valueSize);
1002 } else if (listSizes[i] == -1) {
1003 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, sid, "finaliseBasketSize", "Branch %s exists and uses %d bytes per entry.",
1004 branch->GetName(), valueSize);
1005 // This should probably lookup the
1006 auto column = firstBatch->GetColumnByName(schema_->field(i)->name());
1007 auto list = std::static_pointer_cast<arrow::ListArray>(column);
1008 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.",
1009 branch->GetName(), sizeBranch->GetName(), list->length(), valueSize);
1010 branch->SetBasketSize(1024 + firstBatch->num_rows() * valueSize * list->length());
1011 sizeBranch->SetBasketSize(1024 + firstBatch->num_rows() * 4);
1012 } else {
1013 O2_SIGNPOST_EVENT_EMIT(root_arrow_fs, sid, "finaliseBasketSize", "Branch %s needed. There are %lli entries per array of size %d in that list.",
1014 branch->GetName(), listSizes[i], valueSize);
1015 assert(sizeBranch == nullptr);
1016 branch->SetBasketSize(1024 + firstBatch->num_rows() * valueSize * listSizes[i]);
1017 }
1018
1019 auto field = firstBatch->schema()->field(i);
1020 if (field->name().starts_with("fIndexArray")) {
1021 // One int per array to keep track of the size
1022 int idealBasketSize = 4 * firstBatch->num_rows() + 1024 + field->type()->byte_width() * firstBatch->num_rows(); // minimal additional size needed, otherwise we get 2 baskets
1023 int basketSize = std::max(32000, idealBasketSize); // keep a minimum value
1024 sizeBranch->SetBasketSize(basketSize);
1025 branch->SetBasketSize(basketSize);
1026 }
1027 }
1028 O2_SIGNPOST_END(root_arrow_fs, sid, "finaliseBasketSize", "Done");
1029 }
1030
1031 public:
1032 // Create the TTree based on the physical_schema, not the one in the batch.
1033 // The write method will have to reconcile the two schemas.
1034 TTreeFileWriter(std::shared_ptr<arrow::Schema> schema, std::shared_ptr<arrow::dataset::FileWriteOptions> options,
1035 std::shared_ptr<arrow::io::OutputStream> destination,
1036 arrow::fs::FileLocator destination_locator)
1037 : FileWriter(schema, options, destination, destination_locator)
1038 {
1039 // Batches have the same number of entries for each column.
1040 auto directoryStream = std::dynamic_pointer_cast<TDirectoryFileOutputStream>(destination_);
1041 auto treeStream = std::dynamic_pointer_cast<TTreeOutputStream>(destination_);
1042
1043 if (directoryStream.get()) {
1044 TDirectoryFile* dir = directoryStream->GetDirectory();
1045 dir->cd();
1046 auto* tree = new TTree(destination_locator_.path.c_str(), "");
1047 treeStream = std::make_shared<TTreeOutputStream>(tree, "");
1048 } else if (treeStream.get()) {
1049 // We already have a tree stream, let's derive a new one
1050 // with the destination_locator_.path as prefix for the branches
1051 // This way we can multiplex multiple tables in the same tree.
1052 auto* tree = treeStream->GetTree();
1053 treeStream = std::make_shared<TTreeOutputStream>(tree, destination_locator_.path);
1054 } else {
1055 // I could simply set a prefix here to merge to an already existing tree.
1056 throw std::runtime_error("Unsupported backend.");
1057 }
1058
1059 for (auto i = 0u; i < schema->fields().size(); ++i) {
1060 auto& field = schema->field(i);
1061 listSizes.push_back(1);
1062
1063 int valuesIdealBasketSize = 0;
1064 // Construct all the needed branches.
1065 switch (field->type()->id()) {
1066 case arrow::Type::FIXED_SIZE_LIST: {
1067 listSizes.back() = std::static_pointer_cast<arrow::FixedSizeListType>(field->type())->list_size();
1068 valuesIdealBasketSize = 1024 + valueTypes.back()->byte_width() * listSizes.back();
1069 valueTypes.push_back(field->type()->field(0)->type());
1070 sizesBranches.push_back(nullptr);
1071 std::string leafList = fmt::format("{}[{}]{}", field->name(), listSizes.back(), rootSuffixFromArrow(valueTypes.back()->id()));
1072 branches.push_back(treeStream->CreateBranch(field->name().c_str(), leafList.c_str()));
1073 } break;
1074 case arrow::Type::LIST: {
1075 valueTypes.push_back(field->type()->field(0)->type());
1076 std::string leafList = fmt::format("{}[{}_size]{}", field->name(), field->name(), rootSuffixFromArrow(valueTypes.back()->id()));
1077 listSizes.back() = -1; // VLA, we need to calculate it on the fly;
1078 std::string sizeLeafList = field->name() + "_size/I";
1079 sizesBranches.push_back(treeStream->CreateBranch((field->name() + "_size").c_str(), sizeLeafList.c_str()));
1080 branches.push_back(treeStream->CreateBranch(field->name().c_str(), leafList.c_str()));
1081 // Notice that this could be replaced by a better guess of the
1082 // average size of the list elements, but this is not trivial.
1083 } break;
1084 default: {
1085 valueTypes.push_back(field->type());
1086 std::string leafList = field->name() + rootSuffixFromArrow(valueTypes.back()->id());
1087 sizesBranches.push_back(nullptr);
1088 branches.push_back(treeStream->CreateBranch(field->name().c_str(), leafList.c_str()));
1089 } break;
1090 }
1091 }
1092 // We create the branches from the schema
1093 }
1094
1095 arrow::Status Write(const std::shared_ptr<arrow::RecordBatch>& batch) override
1096 {
1097 if (firstBasket) {
1098 firstBasket = false;
1099 finaliseBasketSize(batch);
1100 }
1101
1102 // Support writing empty tables
1103 if (batch->columns().empty() || batch->num_rows() == 0) {
1104 return arrow::Status::OK();
1105 }
1106
1107 // Batches have the same number of entries for each column.
1108 auto directoryStream = std::dynamic_pointer_cast<TDirectoryFileOutputStream>(destination_);
1109 TTree* tree = nullptr;
1110 if (directoryStream.get()) {
1111 TDirectoryFile* dir = directoryStream->GetDirectory();
1112 tree = (TTree*)dir->Get(destination_locator_.path.c_str());
1113 }
1114 auto treeStream = std::dynamic_pointer_cast<TTreeOutputStream>(destination_);
1115
1116 if (!tree) {
1117 // I could simply set a prefix here to merge to an already existing tree.
1118 throw std::runtime_error("Unsupported backend.");
1119 }
1120
1121 for (auto i = 0u; i < batch->columns().size(); ++i) {
1122 auto column = batch->column(i);
1123 auto& field = batch->schema()->field(i);
1124
1125 valueArrays.push_back(nullptr);
1126
1127 switch (field->type()->id()) {
1128 case arrow::Type::FIXED_SIZE_LIST: {
1129 auto list = std::static_pointer_cast<arrow::FixedSizeListArray>(column);
1130 if (list->list_type()->field(0)->type()->id() == arrow::Type::BOOL) {
1131 int64_t length = list->length() * list->list_type()->list_size();
1132 arrow::UInt8Builder builder;
1133 auto ok = builder.Reserve(length);
1134 // I need to build an array of uint8_t for the conversion to ROOT which uses
1135 // bytes for boolans.
1136 auto boolArray = std::static_pointer_cast<arrow::BooleanArray>(list->values());
1137 for (int64_t i = 0; i < length; ++i) {
1138 if (boolArray->IsValid(i)) {
1139 // Expand each boolean value (true/false) to uint8 (1/0)
1140 uint8_t value = boolArray->Value(i) ? 1 : 0;
1141 auto ok = builder.Append(value);
1142 } else {
1143 // Append null for invalid entries
1144 auto ok = builder.AppendNull();
1145 }
1146 }
1147 valueArrays.back() = *builder.Finish();
1148 } else {
1149 valueArrays.back() = list->values();
1150 }
1151 } break;
1152 case arrow::Type::LIST: {
1153 auto list = std::static_pointer_cast<arrow::ListArray>(column);
1154 valueArrays.back() = list->values();
1155 } break;
1156 case arrow::Type::BOOL: {
1157 // In case of arrays of booleans, we need to go back to their
1158 // char based representation for ROOT to save them.
1159 auto boolArray = std::static_pointer_cast<arrow::BooleanArray>(column);
1160
1161 int64_t length = boolArray->length();
1162 arrow::UInt8Builder builder;
1163 auto ok = builder.Reserve(length);
1164
1165 for (int64_t i = 0; i < length; ++i) {
1166 if (boolArray->IsValid(i)) {
1167 // Expand each boolean value (true/false) to uint8 (1/0)
1168 uint8_t value = boolArray->Value(i) ? 1 : 0;
1169 auto ok = builder.Append(value);
1170 } else {
1171 // Append null for invalid entries
1172 auto ok = builder.AppendNull();
1173 }
1174 }
1175 valueArrays.back() = *builder.Finish();
1176 } break;
1177 default:
1178 valueArrays.back() = column;
1179 }
1180 }
1181
1182 int64_t pos = 0;
1183 while (pos < batch->num_rows()) {
1184 for (size_t bi = 0; bi < branches.size(); ++bi) {
1185 auto* branch = branches[bi];
1186 auto* sizeBranch = sizesBranches[bi];
1187 auto array = batch->column(bi);
1188 auto& field = batch->schema()->field(bi);
1189 auto& listSize = listSizes[bi];
1190 auto valueType = valueTypes[bi];
1191 auto valueArray = valueArrays[bi];
1192
1193 switch (field->type()->id()) {
1194 case arrow::Type::LIST: {
1195 auto list = std::static_pointer_cast<arrow::ListArray>(array);
1196 listSize = list->value_length(pos);
1197 uint8_t const* buffer = std::static_pointer_cast<arrow::PrimitiveArray>(valueArray)->values()->data() + array->offset() + list->value_offset(pos) * valueType->byte_width();
1198 branch->SetAddress((void*)buffer);
1199 sizeBranch->SetAddress(&listSize);
1200 } break;
1201 case arrow::Type::FIXED_SIZE_LIST:
1202 default: {
1203 // needed for the boolean case, I should probably cache this.
1204 auto byteWidth = valueType->byte_width() ? valueType->byte_width() : 1;
1205 uint8_t const* buffer = std::static_pointer_cast<arrow::PrimitiveArray>(valueArray)->values()->data() + array->offset() + pos * listSize * byteWidth;
1206 branch->SetAddress((void*)buffer);
1207 };
1208 }
1209 }
1210 tree->Fill();
1211 ++pos;
1212 }
1213 return arrow::Status::OK();
1214 }
1215
1216 arrow::Future<> FinishInternal() override
1217 {
1218 auto treeStream = std::dynamic_pointer_cast<TTreeOutputStream>(destination_);
1219 auto* tree = treeStream->GetTree();
1220 tree->Write("", TObject::kOverwrite);
1221 tree->SetDirectory(nullptr);
1222
1223 return {};
1224 };
1225};
1226arrow::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
1227{
1228 auto writer = std::make_shared<TTreeFileWriter>(schema, options, destination, destination_locator);
1229 return std::dynamic_pointer_cast<arrow::dataset::FileWriter>(writer);
1230}
1231
1232std::shared_ptr<arrow::dataset::FileWriteOptions> TTreeFileFormat::DefaultWriteOptions()
1233{
1234 std::shared_ptr<TTreeFileWriteOptions> options(
1235 new TTreeFileWriteOptions(shared_from_this()));
1236 return options;
1237}
1238
1240
1244} // 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
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