@@ -299,22 +299,19 @@ class FieldWriterContext {
299299 }
300300
301301 inline std::vector<ColumnStatistics*> columnStats () {
302- if (!statsFinalized_) {
303- return {};
304- }
305- // TODO: add a build method for this pattern.
306302 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 ());
303+ statsViews.reserve (fileStats_.size ());
304+ for (auto & stat : fileStats_) {
305+ statsViews.push_back (stat.get ());
314306 }
315307 return statsViews;
316308 }
317309
310+ inline const std::vector<std::vector<std::unique_ptr<ColumnStatistics>>>&
311+ stripeStats () const {
312+ return stripeStats_;
313+ }
314+
318315 void handleFlatmapFieldAddEvent (
319316 const TypeBuilder& typeBuilder,
320317 std::string_view flatmapKey,
@@ -479,10 +476,40 @@ class FieldWriterContext {
479476 // 1. roll up logical and physical sizes
480477 // 2. wrap all ancestors of deduplicated types
481478 // 3. backfill both known and newly wrapped deduplicated stats
482- void finalizeStatsCollectors () {
479+ void finalizeStripeStatsCollectors () {
483480 NIMBLE_CHECK (!statsFinalized_);
484481 finalizeStatsCollector (schemaWithId_);
485482 statsFinalized_ = true ;
483+ snapshotStripeStats ();
484+ resetStatsCollectors ();
485+ }
486+
487+ void finalizeFileStatsFromStripes () {
488+ fileStats_.clear ();
489+ if (stripeStats_.empty ()) {
490+ if (!statsFinalized_) {
491+ finalizeStatsCollector (schemaWithId_);
492+ statsFinalized_ = true ;
493+ }
494+ fileStats_.reserve (statsCollectors_.size ());
495+ for (auto & collector : statsCollectors_) {
496+ auto * stats = collector->shared ()
497+ ? collector->as <SharedStatisticsCollector>()->getBaseStatistics ()
498+ : collector->getStatsView ();
499+ fileStats_.push_back (stats->clone ());
500+ }
501+ return ;
502+ }
503+ fileStats_.reserve (stripeStats_.front ().size ());
504+ for (const auto & stat : stripeStats_.front ()) {
505+ fileStats_.push_back (stat->clone ());
506+ }
507+ for (size_t stripe = 1 ; stripe < stripeStats_.size (); ++stripe) {
508+ NIMBLE_CHECK_EQ (stripeStats_[stripe].size (), fileStats_.size ());
509+ for (size_t column = 0 ; column < fileStats_.size (); ++column) {
510+ mergeColumnStats (*fileStats_[column], *stripeStats_[stripe][column]);
511+ }
512+ }
486513 }
487514
488515 protected:
@@ -513,6 +540,100 @@ class FieldWriterContext {
513540 [](TypeBuilder&, uint32_t ) {}};
514541
515542 private:
543+ // Merges min/max from source into target for a scalar statistics subclass
544+ // (Integral/FloatingPoint/String). Keeps the widest range across stripes.
545+ template <typename StatsT>
546+ static void mergeMinMax (
547+ ColumnStatistics& target,
548+ const ColumnStatistics& source) {
549+ auto * targetStats = target.as <StatsT>();
550+ const auto * sourceStats = source.as <StatsT>();
551+ if (sourceStats->getMin ().has_value () &&
552+ (!targetStats->getMin ().has_value () ||
553+ *sourceStats->getMin () < *targetStats->getMin ())) {
554+ targetStats->min_ = sourceStats->getMin ();
555+ }
556+ if (sourceStats->getMax ().has_value () &&
557+ (!targetStats->getMax ().has_value () ||
558+ *sourceStats->getMax () > *targetStats->getMax ())) {
559+ targetStats->max_ = sourceStats->getMax ();
560+ }
561+ }
562+
563+ static void mergeColumnStats (
564+ ColumnStatistics& target,
565+ const ColumnStatistics& source) {
566+ NIMBLE_CHECK_EQ (target.getType (), source.getType ());
567+ // Deduplicated stats delegate their base metrics (value/null count, sizes)
568+ // to the wrapped base statistics, so merge into that base rather than
569+ // target's own (unused) member fields, and accumulate the dedup-specific
570+ // counters separately.
571+ if (target.getType () == StatType::DEDUPLICATED ) {
572+ auto * targetStats = target.as <DeduplicatedColumnStatistics>();
573+ const auto * sourceStats = source.as <DeduplicatedColumnStatistics>();
574+ targetStats->dedupedCount_ += sourceStats->getDedupedCount ();
575+ targetStats->dedupedLogicalSize_ += sourceStats->getDedupedLogicalSize ();
576+ auto * targetBase = targetStats->getBaseStatistics ();
577+ const auto * sourceBase = sourceStats->getBaseStatistics ();
578+ if (targetBase != nullptr && sourceBase != nullptr ) {
579+ mergeColumnStats (*targetBase, *sourceBase);
580+ }
581+ return ;
582+ }
583+
584+ target.valueCount_ += source.getValueCount ();
585+ target.nullCount_ += source.getNullCount ();
586+ target.logicalSize_ += source.getLogicalSize ();
587+ target.physicalSize_ += source.getPhysicalSize ();
588+ switch (target.getType ()) {
589+ case StatType::INTEGRAL :
590+ mergeMinMax<IntegralStatistics>(target, source);
591+ break ;
592+ case StatType::FLOATING_POINT :
593+ mergeMinMax<FloatingPointStatistics>(target, source);
594+ break ;
595+ case StatType::STRING :
596+ mergeMinMax<StringStatistics>(target, source);
597+ break ;
598+ case StatType::DEFAULT :
599+ case StatType::DEDUPLICATED :
600+ break ;
601+ }
602+ }
603+
604+ void snapshotStripeStats () {
605+ auto & stripe = stripeStats_.emplace_back ();
606+ stripe.reserve (statsCollectors_.size ());
607+ for (auto & collector : statsCollectors_) {
608+ auto * stats = collector->shared ()
609+ ? collector->as <SharedStatisticsCollector>()->getBaseStatistics ()
610+ : collector->getStatsView ();
611+ stripe.push_back (stats->clone ());
612+ }
613+ }
614+
615+ void resetStatsCollectors () {
616+ // finalizeStatsCollector() wraps ancestors of deduplicated nodes into a
617+ // DeduplicatedStatisticsCollector in place. That wrapping must be undone
618+ // between stripes; otherwise the next stripe's finalization would take the
619+ // "already deduplicated" branch and skip rolling child logical/physical
620+ // sizes up into the ancestor, corrupting the file-level stats
621+ // reconstruction. Genuine deduplicated nodes (configured at init via
622+ // dictionary array / deduplicated map ids) must remain wrapped.
623+ for (uint32_t id = 0 ; id < statsCollectors_.size (); ++id) {
624+ auto & collector = statsCollectors_[id];
625+ if (collector->getType () == StatType::DEDUPLICATED &&
626+ !hasDictionaryArrayNodeId (id) && !hasDeduplicatedMapNodeId (id)) {
627+ collector = collector->asChecked <DeduplicatedStatisticsCollector>()
628+ ->releaseBaseCollector ();
629+ }
630+ }
631+ for (auto & collector : statsCollectors_) {
632+ collector->reset ();
633+ }
634+ statsFinalized_ = false ;
635+ }
636+
516637 void finalizeStatsCollector (
517638 const std::shared_ptr<const velox::dwio::common::TypeWithId>& type) {
518639 auto statsCollector = statsCollectors_[type->id ()].get ();
@@ -627,6 +748,8 @@ class FieldWriterContext {
627748 std::vector<std::pair<uint32_t , std::unique_ptr<StreamData>>> streams_;
628749 std::shared_ptr<const velox::dwio::common::TypeWithId> schemaWithId_;
629750 std::vector<std::unique_ptr<StatisticsCollector>> statsCollectors_;
751+ std::vector<std::vector<std::unique_ptr<ColumnStatistics>>> stripeStats_;
752+ std::vector<std::unique_ptr<ColumnStatistics>> fileStats_;
630753 bool statsFinalized_{false };
631754};
632755
0 commit comments