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