@@ -800,10 +800,9 @@ Status VariantProjectionHandler::fetch_and_project_virtual_slots(const std::unor
800800 }
801801
802802 // 2. Project each needed virtual slot from its source column.
803- // Re-project every iteration — Chunk::reset() clears column data
804- // but preserves slot indices (is_slot_exist stays true), so a
805- // prior range that was fully filtered would leave an empty stale
806- // column that skipping would reuse without fresh projection.
803+ // active_chunk is rebuilt per range, so append_column is the
804+ // common case. When called multiple times within the same
805+ // iteration update_column replaces a previously projected slot.
807806 for (SlotId virtual_slot_id : virtual_slot_ids) {
808807 auto proj_it = _projections.find (virtual_slot_id);
809808 DCHECK (proj_it != _projections.end ());
@@ -909,11 +908,9 @@ Status VariantProjectionHandler::backfill_sources(const Range<uint64_t>& full_ra
909908
910909 auto lazy_hidden_chunk = std::make_shared<Chunk>();
911910 for (SlotId slot_id : _lazy_hidden_slot_ids) {
912- // Use _fetched_hidden_slots, not active_chunk->is_slot_exist().
913- // Chunk::reset() preserves slot indices from a prior iteration that
914- // was fully filtered, so is_slot_exist() would return true for stale
915- // empty columns. _fetched_hidden_slots tracks what was actually
916- // populated THIS iteration.
911+ // Skip slots already populated this iteration via on-demand
912+ // materialization (materialize_hidden_source) or early fetch
913+ // (fetch_and_project_virtual_slots).
917914 if (_fetched_hidden_slots.count (slot_id)) continue ;
918915
919916 auto * hidden_src = _hidden_slot_index.at (slot_id);
@@ -946,23 +943,21 @@ Status VariantProjectionHandler::materialize_hidden_source(SlotId slot_id, const
946943 if (std::find (lazy_ids.begin (), lazy_ids.end (), slot_id) == lazy_ids.end ()) {
947944 return Status::OK ();
948945 }
949- // Chunk::reset() preserves slot indices but clears data. A slot may
950- // exist from a prior iteration that was fully filtered. Re-read the
951- // source in every case — update_column replaces stale data, append_column
952- // is used only on the first population.
946+ // Already populated this iteration (by an earlier on-demand trigger).
947+ if (_fetched_hidden_slots. count (slot_id)) {
948+ return Status::OK ();
949+ }
953950 auto it = _hidden_slot_index.find (slot_id);
954951 if (it == _hidden_slot_index.end ()) {
955952 return Status::InternalError (
956953 fmt::format (" materialize_hidden_source: slot {} not in hidden_slot_index" , slot_id));
957954 }
955+ // active_chunk is rebuilt per range, so the slot never exists from a
956+ // prior iteration — always append.
958957 ColumnPtr col = ColumnHelper::create_column (TypeDescriptor::from_logical_type (TYPE_VARIANT ), true );
959958 RETURN_IF_ERROR (it->second ->reader ->read_range (range, filter, col));
960959 RETURN_IF_ERROR (it->second ->reader ->finalize_lazy_state (col));
961- if (active_chunk->is_slot_exist (slot_id)) {
962- active_chunk->update_column (std::move (col), slot_id);
963- } else {
964- active_chunk->append_column (std::move (col), slot_id);
965- }
960+ active_chunk->append_column (std::move (col), slot_id);
966961 _fetched_hidden_slots.insert (slot_id);
967962 return Status::OK ();
968963}
0 commit comments