|
16 | 16 |
|
17 | 17 | #pragma once |
18 | 18 |
|
| 19 | +#include <cmath> |
19 | 20 | #include <set> |
20 | 21 |
|
21 | 22 | #include <folly/Executor.h> |
@@ -299,22 +300,19 @@ class FieldWriterContext { |
299 | 300 | } |
300 | 301 |
|
301 | 302 | inline std::vector<ColumnStatistics*> columnStats() { |
302 | | - if (!statsFinalized_) { |
303 | | - return {}; |
304 | | - } |
305 | | - // TODO: add a build method for this pattern. |
306 | 303 | std::vector<ColumnStatistics*> statsViews; |
307 | | - statsViews.reserve(statsCollectors_.size()); |
308 | | - for (auto& collector : statsCollectors_) { |
309 | | - // FIXME: don't need this branching. |
310 | | - statsViews.push_back( |
311 | | - collector->shared() |
312 | | - ? collector->as<SharedStatisticsCollector>()->getBaseStatistics() |
313 | | - : collector->getStatsView()); |
| 304 | + statsViews.reserve(fileStats_.size()); |
| 305 | + for (auto& stat : fileStats_) { |
| 306 | + statsViews.push_back(stat.get()); |
314 | 307 | } |
315 | 308 | return statsViews; |
316 | 309 | } |
317 | 310 |
|
| 311 | + inline const std::vector<std::vector<std::unique_ptr<ColumnStatistics>>>& |
| 312 | + stripeStats() const { |
| 313 | + return stripeStats_; |
| 314 | + } |
| 315 | + |
318 | 316 | void handleFlatmapFieldAddEvent( |
319 | 317 | const TypeBuilder& typeBuilder, |
320 | 318 | std::string_view flatmapKey, |
@@ -479,10 +477,40 @@ class FieldWriterContext { |
479 | 477 | // 1. roll up logical and physical sizes |
480 | 478 | // 2. wrap all ancestors of deduplicated types |
481 | 479 | // 3. backfill both known and newly wrapped deduplicated stats |
482 | | - void finalizeStatsCollectors() { |
| 480 | + void finalizeStripeStatsCollectors() { |
483 | 481 | NIMBLE_CHECK(!statsFinalized_); |
484 | 482 | finalizeStatsCollector(schemaWithId_); |
485 | 483 | statsFinalized_ = true; |
| 484 | + snapshotStripeStats(); |
| 485 | + resetStatsCollectors(); |
| 486 | + } |
| 487 | + |
| 488 | + void finalizeFileStatsFromStripes() { |
| 489 | + fileStats_.clear(); |
| 490 | + if (stripeStats_.empty()) { |
| 491 | + if (!statsFinalized_) { |
| 492 | + finalizeStatsCollector(schemaWithId_); |
| 493 | + statsFinalized_ = true; |
| 494 | + } |
| 495 | + fileStats_.reserve(statsCollectors_.size()); |
| 496 | + for (auto& collector : statsCollectors_) { |
| 497 | + auto* stats = collector->shared() |
| 498 | + ? collector->as<SharedStatisticsCollector>()->getBaseStatistics() |
| 499 | + : collector->getStatsView(); |
| 500 | + fileStats_.push_back(stats->clone()); |
| 501 | + } |
| 502 | + return; |
| 503 | + } |
| 504 | + fileStats_.reserve(stripeStats_.front().size()); |
| 505 | + for (const auto& stat : stripeStats_.front()) { |
| 506 | + fileStats_.push_back(stat->clone()); |
| 507 | + } |
| 508 | + for (size_t stripe = 1; stripe < stripeStats_.size(); ++stripe) { |
| 509 | + NIMBLE_CHECK_EQ(stripeStats_[stripe].size(), fileStats_.size()); |
| 510 | + for (size_t column = 0; column < fileStats_.size(); ++column) { |
| 511 | + mergeColumnStats(*fileStats_[column], *stripeStats_[stripe][column]); |
| 512 | + } |
| 513 | + } |
486 | 514 | } |
487 | 515 |
|
488 | 516 | protected: |
@@ -513,6 +541,157 @@ class FieldWriterContext { |
513 | 541 | [](TypeBuilder&, uint32_t) {}}; |
514 | 542 |
|
515 | 543 | private: |
| 544 | + // Merges min/max from source into target for a scalar statistics subclass |
| 545 | + // (Integral/FloatingPoint/String). Keeps the widest range across stripes. |
| 546 | + template <typename StatsT> |
| 547 | + static void mergeMinMax( |
| 548 | + ColumnStatistics& target, |
| 549 | + const ColumnStatistics& source) { |
| 550 | + auto* targetStats = target.as<StatsT>(); |
| 551 | + const auto* sourceStats = source.as<StatsT>(); |
| 552 | + if (sourceStats->getMin().has_value() && |
| 553 | + (!targetStats->getMin().has_value() || |
| 554 | + *sourceStats->getMin() < *targetStats->getMin())) { |
| 555 | + targetStats->min_ = sourceStats->getMin(); |
| 556 | + } |
| 557 | + if (sourceStats->getMax().has_value() && |
| 558 | + (!targetStats->getMax().has_value() || |
| 559 | + *sourceStats->getMax() > *targetStats->getMax())) { |
| 560 | + targetStats->max_ = sourceStats->getMax(); |
| 561 | + } |
| 562 | + } |
| 563 | + |
| 564 | + static bool shouldUseSourceFloatingPointMin( |
| 565 | + const FloatingPointStatistics& target, |
| 566 | + const FloatingPointStatistics& source) { |
| 567 | + if (!source.getMin().has_value()) { |
| 568 | + return false; |
| 569 | + } |
| 570 | + if (!target.getMin().has_value()) { |
| 571 | + return true; |
| 572 | + } |
| 573 | + if (std::isnan(*target.getMin())) { |
| 574 | + return false; |
| 575 | + } |
| 576 | + if (std::isnan(*source.getMin())) { |
| 577 | + return true; |
| 578 | + } |
| 579 | + return *source.getMin() < *target.getMin(); |
| 580 | + } |
| 581 | + |
| 582 | + static bool shouldUseSourceFloatingPointMax( |
| 583 | + const FloatingPointStatistics& target, |
| 584 | + const FloatingPointStatistics& source) { |
| 585 | + if (!source.getMax().has_value()) { |
| 586 | + return false; |
| 587 | + } |
| 588 | + if (!target.getMax().has_value()) { |
| 589 | + return true; |
| 590 | + } |
| 591 | + if (std::isnan(*target.getMax())) { |
| 592 | + return false; |
| 593 | + } |
| 594 | + if (std::isnan(*source.getMax())) { |
| 595 | + return true; |
| 596 | + } |
| 597 | + return *source.getMax() > *target.getMax(); |
| 598 | + } |
| 599 | + |
| 600 | + static void mergeFloatingPointMinMax( |
| 601 | + ColumnStatistics& target, |
| 602 | + const ColumnStatistics& source) { |
| 603 | + auto* targetStats = target.as<FloatingPointStatistics>(); |
| 604 | + const auto* sourceStats = source.as<FloatingPointStatistics>(); |
| 605 | + if (shouldUseSourceFloatingPointMin(*targetStats, *sourceStats)) { |
| 606 | + targetStats->min_ = sourceStats->getMin(); |
| 607 | + } |
| 608 | + if (shouldUseSourceFloatingPointMax(*targetStats, *sourceStats)) { |
| 609 | + targetStats->max_ = sourceStats->getMax(); |
| 610 | + } |
| 611 | + } |
| 612 | + |
| 613 | + static void mergeColumnStats( |
| 614 | + ColumnStatistics& target, |
| 615 | + const ColumnStatistics& source) { |
| 616 | + NIMBLE_CHECK( |
| 617 | + target.getType() == source.getType(), |
| 618 | + "Merging stats with mismatched types: {} vs {}.", |
| 619 | + static_cast<uint8_t>(target.getType()), |
| 620 | + static_cast<uint8_t>(source.getType())); |
| 621 | + // Deduplicated stats delegate their base metrics (value/null count, sizes) |
| 622 | + // to the wrapped base statistics, so merge into that base rather than |
| 623 | + // target's own (unused) member fields, and accumulate the dedup-specific |
| 624 | + // counters separately. |
| 625 | + if (target.getType() == StatType::DEDUPLICATED) { |
| 626 | + auto* targetStats = target.as<DeduplicatedColumnStatistics>(); |
| 627 | + const auto* sourceStats = source.as<DeduplicatedColumnStatistics>(); |
| 628 | + targetStats->dedupedCount_ += sourceStats->getDedupedCount(); |
| 629 | + targetStats->dedupedLogicalSize_ += sourceStats->getDedupedLogicalSize(); |
| 630 | + auto* targetBase = targetStats->getBaseStatistics(); |
| 631 | + const auto* sourceBase = sourceStats->getBaseStatistics(); |
| 632 | + NIMBLE_CHECK( |
| 633 | + (targetBase == nullptr) == (sourceBase == nullptr), |
| 634 | + "Mismatched deduplicated stats structure during merge."); |
| 635 | + if (targetBase != nullptr && sourceBase != nullptr) { |
| 636 | + mergeColumnStats(*targetBase, *sourceBase); |
| 637 | + } |
| 638 | + return; |
| 639 | + } |
| 640 | + |
| 641 | + target.valueCount_ += source.getValueCount(); |
| 642 | + target.nullCount_ += source.getNullCount(); |
| 643 | + target.logicalSize_ += source.getLogicalSize(); |
| 644 | + target.physicalSize_ += source.getPhysicalSize(); |
| 645 | + switch (target.getType()) { |
| 646 | + case StatType::INTEGRAL: |
| 647 | + mergeMinMax<IntegralStatistics>(target, source); |
| 648 | + break; |
| 649 | + case StatType::FLOATING_POINT: |
| 650 | + mergeFloatingPointMinMax(target, source); |
| 651 | + break; |
| 652 | + case StatType::STRING: |
| 653 | + mergeMinMax<StringStatistics>(target, source); |
| 654 | + break; |
| 655 | + case StatType::DEFAULT: |
| 656 | + break; |
| 657 | + case StatType::DEDUPLICATED: |
| 658 | + NIMBLE_UNREACHABLE("Deduplicated stats are handled above."); |
| 659 | + } |
| 660 | + } |
| 661 | + |
| 662 | + void snapshotStripeStats() { |
| 663 | + auto& stripe = stripeStats_.emplace_back(); |
| 664 | + stripe.reserve(statsCollectors_.size()); |
| 665 | + for (auto& collector : statsCollectors_) { |
| 666 | + auto* stats = collector->shared() |
| 667 | + ? collector->as<SharedStatisticsCollector>()->getBaseStatistics() |
| 668 | + : collector->getStatsView(); |
| 669 | + stripe.push_back(stats->clone()); |
| 670 | + } |
| 671 | + } |
| 672 | + |
| 673 | + void resetStatsCollectors() { |
| 674 | + // finalizeStatsCollector() wraps ancestors of deduplicated nodes into a |
| 675 | + // DeduplicatedStatisticsCollector in place. That wrapping must be undone |
| 676 | + // between stripes; otherwise the next stripe's finalization would take the |
| 677 | + // "already deduplicated" branch and skip rolling child logical/physical |
| 678 | + // sizes up into the ancestor, corrupting the file-level stats |
| 679 | + // reconstruction. Genuine deduplicated nodes (configured at init via |
| 680 | + // dictionary array / deduplicated map ids) must remain wrapped. |
| 681 | + for (uint32_t id = 0; id < statsCollectors_.size(); ++id) { |
| 682 | + auto& collector = statsCollectors_[id]; |
| 683 | + if (collector->getType() == StatType::DEDUPLICATED && |
| 684 | + !hasDictionaryArrayNodeId(id) && !hasDeduplicatedMapNodeId(id)) { |
| 685 | + collector = collector->asChecked<DeduplicatedStatisticsCollector>() |
| 686 | + ->releaseBaseCollector(); |
| 687 | + } |
| 688 | + } |
| 689 | + for (auto& collector : statsCollectors_) { |
| 690 | + collector->reset(); |
| 691 | + } |
| 692 | + statsFinalized_ = false; |
| 693 | + } |
| 694 | + |
516 | 695 | void finalizeStatsCollector( |
517 | 696 | const std::shared_ptr<const velox::dwio::common::TypeWithId>& type) { |
518 | 697 | auto statsCollector = statsCollectors_[type->id()].get(); |
@@ -627,6 +806,8 @@ class FieldWriterContext { |
627 | 806 | std::vector<std::pair<uint32_t, std::unique_ptr<StreamData>>> streams_; |
628 | 807 | std::shared_ptr<const velox::dwio::common::TypeWithId> schemaWithId_; |
629 | 808 | std::vector<std::unique_ptr<StatisticsCollector>> statsCollectors_; |
| 809 | + std::vector<std::vector<std::unique_ptr<ColumnStatistics>>> stripeStats_; |
| 810 | + std::vector<std::unique_ptr<ColumnStatistics>> fileStats_; |
630 | 811 | bool statsFinalized_{false}; |
631 | 812 | }; |
632 | 813 |
|
|
0 commit comments