Skip to main content

radixdb_core/
row_vec.rs

1// Copyright 2026 RadixDB Contributors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! RowVec - Cached row vector for zero-allocation table scans
16//!
17//! This type wraps Vec<(i64, Row)> and returns it to a thread-local pool on drop.
18//! Uses a capacity-aware pool with best-fit allocation for efficient buffer reuse.
19//!
20//! Pool Design:
21//! - Sorted by capacity (ascending) for O(log n) best-fit search
22//! - 16 slots to handle concurrent usage patterns
23//! - Best-fit allocation: returns smallest buffer >= requested capacity
24//! - Smart eviction: keeps larger buffers which are more versatile
25
26use crate::Row;
27use std::cell::RefCell;
28
29/// Maximum buffers to keep in the thread-local pool.
30/// 16 slots handles most concurrent usage patterns including complex queries
31/// with multiple JOINs, subqueries, and window functions.
32const POOL_SIZE: usize = 16;
33
34/// Maximum capacity to cache (prevents unbounded memory retention)
35/// 64K elements = ~2MB at 32 bytes per (i64, Row) tuple
36/// This allows caching large table scan buffers from window functions and version store
37const MAX_CACHED_CAPACITY: usize = 64_000;
38
39// Thread-local pool for row vectors - kept sorted by capacity (ascending)
40thread_local! {
41    static ROW_VEC_POOL: RefCell<Vec<Vec<(i64, Row)>>> = const { RefCell::new(Vec::new()) };
42}
43
44/// Clear the thread-local RowVec pool, releasing all cached buffers.
45/// Call this when dropping a database to prevent memory retention.
46#[inline]
47pub fn clear_row_vec_pool() {
48    ROW_VEC_POOL.with(|pool| {
49        if let Ok(mut p) = pool.try_borrow_mut() {
50            p.clear();
51        }
52    });
53}
54
55// ============================================================================
56// Pool Statistics (only when dhat-heap feature is enabled)
57// ============================================================================
58
59/// Pool statistics for debugging and profiling
60#[cfg(feature = "dhat-heap")]
61#[derive(Debug, Default)]
62pub struct PoolStats {
63    /// Number of successful pool hits (buffer reused)
64    pub hits: u64,
65    /// Number of pool misses (new allocation needed)
66    pub misses: u64,
67    /// Number of buffers returned to pool
68    pub returns: u64,
69    /// Number of buffers evicted (pool full, smaller buffer discarded)
70    pub evictions: u64,
71    /// Number of oversized buffers discarded (exceeded MAX_CACHED_CAPACITY)
72    pub oversized_discards: u64,
73    /// Total bytes requested via with_capacity
74    pub bytes_requested: u64,
75    /// Total bytes served from pool (capacity * 16 bytes per element)
76    pub bytes_from_pool: u64,
77    /// Current pool size
78    pub current_pool_size: usize,
79    /// Total capacity in pool (sum of all buffer capacities)
80    pub total_pool_capacity: usize,
81}
82
83#[cfg(feature = "dhat-heap")]
84impl PoolStats {
85    /// Calculate hit rate as percentage
86    pub fn hit_rate(&self) -> f64 {
87        let total = self.hits + self.misses;
88        if total == 0 {
89            0.0
90        } else {
91            (self.hits as f64 / total as f64) * 100.0
92        }
93    }
94
95    /// Estimated bytes saved by pool reuse
96    pub fn bytes_saved(&self) -> u64 {
97        self.bytes_from_pool
98    }
99}
100
101#[cfg(feature = "dhat-heap")]
102impl std::fmt::Display for PoolStats {
103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        writeln!(f, "RowVec Pool Statistics:")?;
105        writeln!(f, "  Hits:              {:>10}", self.hits)?;
106        writeln!(f, "  Misses:            {:>10}", self.misses)?;
107        writeln!(f, "  Hit Rate:          {:>9.1}%", self.hit_rate())?;
108        writeln!(f, "  Returns:           {:>10}", self.returns)?;
109        writeln!(f, "  Evictions:         {:>10}", self.evictions)?;
110        writeln!(f, "  Oversized Discards:{:>10}", self.oversized_discards)?;
111        writeln!(
112            f,
113            "  Bytes Requested:   {:>10}",
114            format_bytes(self.bytes_requested)
115        )?;
116        writeln!(
117            f,
118            "  Bytes From Pool:   {:>10}",
119            format_bytes(self.bytes_from_pool)
120        )?;
121        writeln!(
122            f,
123            "  Bytes Saved:       {:>10}",
124            format_bytes(self.bytes_saved())
125        )?;
126        writeln!(f, "  Current Pool Size: {:>10}", self.current_pool_size)?;
127        writeln!(
128            f,
129            "  Pool Capacity:     {:>10}",
130            format_bytes(self.total_pool_capacity as u64 * 16)
131        )?;
132        Ok(())
133    }
134}
135
136#[cfg(feature = "dhat-heap")]
137fn format_bytes(bytes: u64) -> String {
138    if bytes >= 1_073_741_824 {
139        format!("{:.2} GB", bytes as f64 / 1_073_741_824.0)
140    } else if bytes >= 1_048_576 {
141        format!("{:.2} MB", bytes as f64 / 1_048_576.0)
142    } else if bytes >= 1024 {
143        format!("{:.2} KB", bytes as f64 / 1024.0)
144    } else {
145        format!("{} B", bytes)
146    }
147}
148
149#[cfg(feature = "dhat-heap")]
150thread_local! {
151    static POOL_STATS: RefCell<PoolStats> = RefCell::new(PoolStats::default());
152}
153
154/// Get current pool statistics (only available with dhat-heap feature)
155#[cfg(feature = "dhat-heap")]
156pub fn get_pool_stats() -> PoolStats {
157    POOL_STATS.with(|stats| {
158        let mut s = stats.borrow().clone();
159        // Update current pool state
160        ROW_VEC_POOL.with(|pool| {
161            let pool = pool.borrow();
162            s.current_pool_size = pool.len();
163            s.total_pool_capacity = pool.iter().map(|v| v.capacity()).sum();
164        });
165        s
166    })
167}
168
169/// Print pool statistics to stderr (only available with dhat-heap feature)
170#[cfg(feature = "dhat-heap")]
171pub fn print_pool_stats() {
172    eprintln!("{}", get_pool_stats());
173}
174
175/// Reset pool statistics (only available with dhat-heap feature)
176#[cfg(feature = "dhat-heap")]
177pub fn reset_pool_stats() {
178    POOL_STATS.with(|stats| {
179        *stats.borrow_mut() = PoolStats::default();
180    });
181}
182
183#[cfg(feature = "dhat-heap")]
184impl Clone for PoolStats {
185    fn clone(&self) -> Self {
186        Self {
187            hits: self.hits,
188            misses: self.misses,
189            returns: self.returns,
190            evictions: self.evictions,
191            oversized_discards: self.oversized_discards,
192            bytes_requested: self.bytes_requested,
193            bytes_from_pool: self.bytes_from_pool,
194            current_pool_size: self.current_pool_size,
195            total_pool_capacity: self.total_pool_capacity,
196        }
197    }
198}
199
200// Helper macros for stats tracking (no-op when feature is disabled)
201#[cfg(feature = "dhat-heap")]
202macro_rules! track_hit {
203    ($capacity:expr) => {
204        POOL_STATS.with(|stats| {
205            let mut s = stats.borrow_mut();
206            s.hits += 1;
207            s.bytes_from_pool += ($capacity as u64) * 16;
208        });
209    };
210}
211
212#[cfg(not(feature = "dhat-heap"))]
213macro_rules! track_hit {
214    ($capacity:expr) => {};
215}
216
217#[cfg(feature = "dhat-heap")]
218macro_rules! track_miss {
219    ($capacity:expr) => {
220        POOL_STATS.with(|stats| {
221            let mut s = stats.borrow_mut();
222            s.misses += 1;
223            s.bytes_requested += ($capacity as u64) * 16;
224        });
225    };
226}
227
228#[cfg(not(feature = "dhat-heap"))]
229macro_rules! track_miss {
230    ($capacity:expr) => {};
231}
232
233#[cfg(feature = "dhat-heap")]
234macro_rules! track_return {
235    () => {
236        POOL_STATS.with(|stats| {
237            stats.borrow_mut().returns += 1;
238        });
239    };
240}
241
242#[cfg(not(feature = "dhat-heap"))]
243macro_rules! track_return {
244    () => {};
245}
246
247#[cfg(feature = "dhat-heap")]
248macro_rules! track_eviction {
249    () => {
250        POOL_STATS.with(|stats| {
251            stats.borrow_mut().evictions += 1;
252        });
253    };
254}
255
256#[cfg(not(feature = "dhat-heap"))]
257macro_rules! track_eviction {
258    () => {};
259}
260
261#[cfg(feature = "dhat-heap")]
262macro_rules! track_oversized {
263    () => {
264        POOL_STATS.with(|stats| {
265            stats.borrow_mut().oversized_discards += 1;
266        });
267    };
268}
269
270#[cfg(not(feature = "dhat-heap"))]
271macro_rules! track_oversized {
272    () => {};
273}
274
275/// Cached row vector that returns to thread-local cache on drop.
276///
277/// Use this for table scans to reuse Vec allocations across queries.
278/// Derefs to `Vec<(i64, Row)>` for transparent access.
279#[derive(Debug)]
280pub struct RowVec {
281    inner: Option<Vec<(i64, Row)>>,
282}
283
284impl RowVec {
285    /// Create from thread-local pool or allocate new.
286    /// Takes the largest available buffer (end of sorted list).
287    #[inline]
288    pub fn new() -> Self {
289        // Use try_borrow_mut to avoid panic on re-entrant access
290        // (can happen if Drop triggers nested RowVec creation on the same thread)
291        let v = ROW_VEC_POOL.with(|pool| pool.try_borrow_mut().ok().and_then(|mut p| p.pop()));
292        match v {
293            Some(buf) => {
294                track_hit!(buf.capacity());
295                Self { inner: Some(buf) }
296            }
297            None => {
298                track_miss!(16);
299                Self {
300                    inner: Some(Vec::with_capacity(16)),
301                }
302            }
303        }
304    }
305
306    /// Create with specific capacity using best-fit allocation.
307    /// Uses binary search to find smallest buffer >= requested capacity.
308    #[inline]
309    pub fn with_capacity(capacity: usize) -> Self {
310        // Use try_borrow_mut to avoid panic on re-entrant access
311        let v = ROW_VEC_POOL.with(|pool| {
312            let mut pool = match pool.try_borrow_mut() {
313                Ok(p) => p,
314                Err(_) => return None, // Pool already borrowed — allocate fresh
315            };
316            if pool.is_empty() {
317                return None;
318            }
319            // Binary search for smallest buffer >= capacity
320            // Pool is sorted ascending by capacity
321            let idx = pool.partition_point(|b| b.capacity() < capacity);
322            if idx < pool.len() {
323                // Found a buffer with sufficient capacity
324                Some(pool.remove(idx))
325            } else {
326                // No buffer large enough - take largest and let it grow
327                pool.pop()
328            }
329        });
330
331        match v {
332            Some(mut buf) => {
333                let buf_cap = buf.capacity();
334                if buf_cap >= capacity {
335                    // Perfect hit - buffer is large enough
336                    track_hit!(buf_cap);
337                } else {
338                    // Partial hit - need to grow buffer
339                    track_hit!(buf_cap);
340                    buf.reserve_exact(capacity - buf.len());
341                }
342                Self { inner: Some(buf) }
343            }
344            None => {
345                track_miss!(capacity);
346                Self {
347                    inner: Some(Vec::with_capacity(capacity)),
348                }
349            }
350        }
351    }
352
353    /// Create from an existing Vec<(i64, Row)>.
354    /// The provided vec becomes the inner storage (no copy if move is possible).
355    /// NOTE: This bypasses the cache - use when you already have allocated data.
356    #[inline]
357    pub fn from_vec(v: Vec<(i64, Row)>) -> Self {
358        Self { inner: Some(v) }
359    }
360
361    /// Extract the inner Vec directly, bypassing cache return.
362    /// Use this when you need to pass the Vec to APIs that require Vec<(i64, Row)>.
363    /// The allocation is NOT returned to the cache.
364    #[inline]
365    pub fn into_vec(mut self) -> Vec<(i64, Row)> {
366        self.inner.take().unwrap_or_default()
367    }
368
369    /// Get length
370    #[inline]
371    pub fn len(&self) -> usize {
372        self.inner.as_ref().map(|v| v.len()).unwrap_or(0)
373    }
374
375    /// Check if empty
376    #[inline]
377    pub fn is_empty(&self) -> bool {
378        self.len() == 0
379    }
380
381    /// Push a row
382    #[inline]
383    pub fn push(&mut self, item: (i64, Row)) {
384        if let Some(v) = self.inner.as_mut() {
385            v.push(item);
386        }
387    }
388
389    /// Clear the vector (keeps allocation)
390    #[inline]
391    pub fn clear(&mut self) {
392        if let Some(v) = self.inner.as_mut() {
393            v.clear();
394        }
395    }
396
397    /// Get iterator over rows (borrows)
398    #[inline]
399    pub fn iter(&self) -> impl Iterator<Item = &(i64, Row)> {
400        self.inner.as_ref().unwrap().iter()
401    }
402
403    /// Get mutable iterator
404    #[inline]
405    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut (i64, Row)> {
406        self.inner.as_mut().unwrap().iter_mut()
407    }
408
409    /// Get row by index
410    #[inline]
411    pub fn get(&self, index: usize) -> Option<&(i64, Row)> {
412        self.inner.as_ref().and_then(|v| v.get(index))
413    }
414
415    /// Drain and extract just the rows, discarding row IDs.
416    /// The RowVec allocation returns to the cache on drop.
417    #[inline]
418    pub fn drain_rows(&mut self) -> impl Iterator<Item = Row> + '_ {
419        self.inner.as_mut().unwrap().drain(..).map(|(_, row)| row)
420    }
421
422    /// Get iterator over just the Row references
423    #[inline]
424    pub fn rows(&self) -> impl Iterator<Item = &Row> {
425        self.inner.as_ref().unwrap().iter().map(|(_, row)| row)
426    }
427}
428
429impl Default for RowVec {
430    fn default() -> Self {
431        Self::new()
432    }
433}
434
435impl Clone for RowVec {
436    fn clone(&self) -> Self {
437        let mut cloned = RowVec::with_capacity(self.len());
438        for (id, row) in self.inner.as_ref().unwrap().iter() {
439            cloned.push((*id, row.clone()));
440        }
441        cloned
442    }
443}
444
445impl std::ops::Deref for RowVec {
446    type Target = Vec<(i64, Row)>;
447
448    #[inline]
449    fn deref(&self) -> &Self::Target {
450        self.inner.as_ref().unwrap()
451    }
452}
453
454impl std::ops::DerefMut for RowVec {
455    #[inline]
456    fn deref_mut(&mut self) -> &mut Self::Target {
457        self.inner.as_mut().unwrap()
458    }
459}
460
461impl Drop for RowVec {
462    #[inline]
463    fn drop(&mut self) {
464        if let Some(mut v) = self.inner.take() {
465            let cap = v.capacity();
466            // Don't cache very large buffers to prevent unbounded memory retention
467            if cap > MAX_CACHED_CAPACITY {
468                track_oversized!();
469                return; // Let it deallocate
470            }
471            v.clear();
472            ROW_VEC_POOL.with(|pool| {
473                // Use try_borrow_mut to avoid panic on re-entrant access
474                let mut pool = match pool.try_borrow_mut() {
475                    Ok(p) => p,
476                    Err(_) => return, // Pool already borrowed — let buffer deallocate
477                };
478                if pool.len() < POOL_SIZE {
479                    // Pool has room - insert in sorted position (by capacity, ascending)
480                    let insert_idx = pool.partition_point(|b| b.capacity() < cap);
481                    pool.insert(insert_idx, v);
482                    track_return!();
483                } else {
484                    // Pool full - smart eviction: keep larger buffers (more versatile)
485                    // Replace smallest if this buffer is larger
486                    if !pool.is_empty() && pool[0].capacity() < cap {
487                        // Remove smallest (index 0), insert this one in sorted position
488                        pool.remove(0);
489                        let insert_idx = pool.partition_point(|b| b.capacity() < cap);
490                        pool.insert(insert_idx, v);
491                        track_return!();
492                        track_eviction!();
493                    }
494                    // Otherwise let v deallocate - it's smaller than everything in pool
495                }
496            });
497        }
498    }
499}
500
501impl std::ops::Index<usize> for RowVec {
502    type Output = (i64, Row);
503
504    #[inline]
505    fn index(&self, index: usize) -> &Self::Output {
506        &self.inner.as_ref().unwrap()[index]
507    }
508}
509
510impl std::ops::IndexMut<usize> for RowVec {
511    #[inline]
512    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
513        &mut self.inner.as_mut().unwrap()[index]
514    }
515}
516
517/// Draining iterator that preserves allocation for cache reuse.
518/// Uses ptr::read for zero-allocation iteration - no dummy rows created.
519pub struct RowVecIter {
520    inner: std::mem::ManuallyDrop<RowVec>,
521    front: usize,
522    back: usize,
523}
524
525impl Iterator for RowVecIter {
526    type Item = (i64, Row);
527
528    #[inline]
529    fn next(&mut self) -> Option<Self::Item> {
530        if self.front >= self.back {
531            return None;
532        }
533        let vec = self.inner.inner.as_ref()?;
534        // SAFETY: Each position is read exactly once. We track front/back indices
535        // and set_len(0) in Drop prevents double-free of moved elements.
536        let item = unsafe { std::ptr::read(vec.as_ptr().add(self.front)) };
537        self.front += 1;
538        Some(item)
539    }
540
541    #[inline]
542    fn size_hint(&self) -> (usize, Option<usize>) {
543        let len = self.back.saturating_sub(self.front);
544        (len, Some(len))
545    }
546}
547
548impl DoubleEndedIterator for RowVecIter {
549    #[inline]
550    fn next_back(&mut self) -> Option<Self::Item> {
551        if self.front >= self.back {
552            return None;
553        }
554        self.back -= 1;
555        let vec = self.inner.inner.as_ref()?;
556        // SAFETY: Each position is read exactly once from back.
557        let item = unsafe { std::ptr::read(vec.as_ptr().add(self.back)) };
558        Some(item)
559    }
560}
561
562impl ExactSizeIterator for RowVecIter {}
563
564impl Drop for RowVecIter {
565    fn drop(&mut self) {
566        // Drop any remaining items that weren't yielded
567        if let Some(vec) = self.inner.inner.as_mut() {
568            for i in self.front..self.back {
569                // SAFETY: Items in range [front, back) haven't been read yet
570                // and are valid initialized elements.
571                unsafe {
572                    std::ptr::drop_in_place(vec.as_mut_ptr().add(i));
573                }
574            }
575            // SAFETY: We've dropped all elements above. Set len to 0 so Vec's
576            // drop doesn't double-free. Capacity is preserved for cache reuse.
577            unsafe {
578                vec.set_len(0);
579            }
580        }
581        // Now drop the RowVec so it returns the empty Vec to cache
582        // SAFETY: We're in Drop, and we've cleared the vec's length
583        unsafe {
584            std::mem::ManuallyDrop::drop(&mut self.inner);
585        }
586    }
587}
588
589impl IntoIterator for RowVec {
590    type Item = (i64, Row);
591    type IntoIter = RowVecIter;
592
593    #[inline]
594    fn into_iter(self) -> Self::IntoIter {
595        let len = self.len();
596        RowVecIter {
597            inner: std::mem::ManuallyDrop::new(self),
598            front: 0,
599            back: len,
600        }
601    }
602}
603
604impl<'a> IntoIterator for &'a RowVec {
605    type Item = &'a (i64, Row);
606    type IntoIter = std::slice::Iter<'a, (i64, Row)>;
607
608    #[inline]
609    fn into_iter(self) -> Self::IntoIter {
610        self.inner.as_ref().unwrap().iter()
611    }
612}
613
614impl<'a> IntoIterator for &'a mut RowVec {
615    type Item = &'a mut (i64, Row);
616    type IntoIter = std::slice::IterMut<'a, (i64, Row)>;
617
618    #[inline]
619    fn into_iter(self) -> Self::IntoIter {
620        self.inner.as_mut().unwrap().iter_mut()
621    }
622}
623
624// Allow collecting into RowVec
625impl FromIterator<(i64, Row)> for RowVec {
626    fn from_iter<I: IntoIterator<Item = (i64, Row)>>(iter: I) -> Self {
627        let iter = iter.into_iter();
628        let (lower, upper) = iter.size_hint();
629        // Use upper bound if available, otherwise lower bound, minimum 16
630        let capacity = upper.unwrap_or(lower).max(16);
631        let mut rv = RowVec::with_capacity(capacity);
632        for item in iter {
633            rv.push(item);
634        }
635        rv
636    }
637}
638
639// ============================================================================
640// RowIdVec - Cached Vec<i64> for zero-allocation index lookups
641// ============================================================================
642
643/// Maximum capacity to cache for RowIdVec (prevents unbounded memory retention)
644/// 256K elements = ~2MB at 8 bytes per i64
645const ROW_ID_MAX_CACHED_CAPACITY: usize = 256_000;
646
647/// Pool size for RowIdVec - same as RowVec
648const ROW_ID_POOL_SIZE: usize = 16;
649
650// Thread-local pool for row ID vectors - kept sorted by capacity (ascending)
651thread_local! {
652    static ROW_ID_VEC_POOL: RefCell<Vec<Vec<i64>>> = const { RefCell::new(Vec::new()) };
653}
654
655/// Clear the thread-local RowIdVec pool, releasing all cached buffers.
656/// Call this when dropping a database to prevent memory retention.
657#[inline]
658pub fn clear_row_id_vec_pool() {
659    ROW_ID_VEC_POOL.with(|pool| {
660        if let Ok(mut p) = pool.try_borrow_mut() {
661            p.clear();
662        }
663    });
664}
665
666/// Cached row ID vector that returns to thread-local cache on drop.
667///
668/// Use this for index lookups to reuse `Vec<i64>` allocations across queries.
669/// Derefs to `Vec<i64>` for transparent access.
670#[derive(Debug)]
671pub struct RowIdVec {
672    inner: Option<Vec<i64>>,
673}
674
675impl RowIdVec {
676    /// Create from thread-local pool or allocate new.
677    /// Takes the largest available buffer (end of sorted list).
678    #[inline]
679    pub fn new() -> Self {
680        let v = ROW_ID_VEC_POOL.with(|pool| pool.try_borrow_mut().ok().and_then(|mut p| p.pop()));
681        match v {
682            Some(buf) => Self { inner: Some(buf) },
683            None => Self {
684                inner: Some(Vec::with_capacity(16)),
685            },
686        }
687    }
688
689    /// Create with specific capacity using best-fit allocation.
690    /// Uses binary search to find smallest buffer >= requested capacity.
691    #[inline]
692    pub fn with_capacity(capacity: usize) -> Self {
693        let v = ROW_ID_VEC_POOL.with(|pool| {
694            let mut pool = match pool.try_borrow_mut() {
695                Ok(p) => p,
696                Err(_) => return None,
697            };
698            if pool.is_empty() {
699                return None;
700            }
701            // Binary search for smallest buffer >= capacity
702            let idx = pool.partition_point(|b| b.capacity() < capacity);
703            if idx < pool.len() {
704                // Found a buffer with sufficient capacity
705                Some(pool.remove(idx))
706            } else {
707                // No buffer large enough - take largest and let it grow
708                pool.pop()
709            }
710        });
711
712        match v {
713            Some(mut buf) => {
714                let buf_cap = buf.capacity();
715                if buf_cap < capacity {
716                    // Need to grow buffer
717                    buf.reserve_exact(capacity - buf.len());
718                }
719                Self { inner: Some(buf) }
720            }
721            None => Self {
722                inner: Some(Vec::with_capacity(capacity)),
723            },
724        }
725    }
726
727    /// Create from an existing `Vec<i64>`.
728    /// NOTE: This bypasses the cache - use when you already have allocated data.
729    #[inline]
730    pub fn from_vec(v: Vec<i64>) -> Self {
731        Self { inner: Some(v) }
732    }
733
734    /// Extract the inner Vec directly, bypassing cache return.
735    /// Use this when you need to pass the `Vec` to APIs that require `Vec<i64>`.
736    /// The allocation is NOT returned to the cache.
737    #[inline]
738    pub fn into_vec(mut self) -> Vec<i64> {
739        self.inner.take().unwrap_or_default()
740    }
741
742    /// Get length
743    #[inline]
744    pub fn len(&self) -> usize {
745        self.inner.as_ref().map(|v| v.len()).unwrap_or(0)
746    }
747
748    /// Check if empty
749    #[inline]
750    pub fn is_empty(&self) -> bool {
751        self.len() == 0
752    }
753
754    /// Push a row ID
755    #[inline]
756    pub fn push(&mut self, item: i64) {
757        if let Some(v) = self.inner.as_mut() {
758            v.push(item);
759        }
760    }
761
762    /// Extend from iterator
763    #[inline]
764    pub fn extend<I: IntoIterator<Item = i64>>(&mut self, iter: I) {
765        if let Some(v) = self.inner.as_mut() {
766            v.extend(iter);
767        }
768    }
769
770    /// Clear the vector (keeps allocation)
771    #[inline]
772    pub fn clear(&mut self) {
773        if let Some(v) = self.inner.as_mut() {
774            v.clear();
775        }
776    }
777
778    /// Get iterator over row IDs
779    #[inline]
780    pub fn iter(&self) -> impl Iterator<Item = &i64> {
781        self.inner.as_ref().unwrap().iter()
782    }
783
784    /// Reserve capacity
785    #[inline]
786    pub fn reserve(&mut self, additional: usize) {
787        if let Some(v) = self.inner.as_mut() {
788            v.reserve(additional);
789        }
790    }
791
792    /// Sort the row IDs
793    #[inline]
794    pub fn sort(&mut self) {
795        if let Some(v) = self.inner.as_mut() {
796            v.sort_unstable();
797        }
798    }
799
800    /// Dedup the row IDs (requires sorted)
801    #[inline]
802    pub fn dedup(&mut self) {
803        if let Some(v) = self.inner.as_mut() {
804            v.dedup();
805        }
806    }
807}
808
809impl Default for RowIdVec {
810    fn default() -> Self {
811        Self::new()
812    }
813}
814
815impl Clone for RowIdVec {
816    fn clone(&self) -> Self {
817        let mut cloned = RowIdVec::with_capacity(self.len());
818        if let Some(v) = self.inner.as_ref() {
819            cloned.extend(v.iter().copied());
820        }
821        cloned
822    }
823}
824
825impl std::ops::Deref for RowIdVec {
826    type Target = Vec<i64>;
827
828    #[inline]
829    fn deref(&self) -> &Self::Target {
830        self.inner.as_ref().unwrap()
831    }
832}
833
834impl std::ops::DerefMut for RowIdVec {
835    #[inline]
836    fn deref_mut(&mut self) -> &mut Self::Target {
837        self.inner.as_mut().unwrap()
838    }
839}
840
841impl Drop for RowIdVec {
842    #[inline]
843    fn drop(&mut self) {
844        if let Some(mut v) = self.inner.take() {
845            let cap = v.capacity();
846            // Don't cache very large buffers to prevent unbounded memory retention
847            if cap > ROW_ID_MAX_CACHED_CAPACITY {
848                return; // Let it deallocate
849            }
850            v.clear();
851            ROW_ID_VEC_POOL.with(|pool| {
852                let mut pool = match pool.try_borrow_mut() {
853                    Ok(p) => p,
854                    Err(_) => return,
855                };
856                if pool.len() < ROW_ID_POOL_SIZE {
857                    // Pool has room - insert in sorted position (by capacity, ascending)
858                    let insert_idx = pool.partition_point(|b| b.capacity() < cap);
859                    pool.insert(insert_idx, v);
860                } else {
861                    // Pool full - smart eviction: keep larger buffers (more versatile)
862                    // Replace smallest if this buffer is larger
863                    if !pool.is_empty() && pool[0].capacity() < cap {
864                        // Remove smallest (index 0), insert this one in sorted position
865                        pool.remove(0);
866                        let insert_idx = pool.partition_point(|b| b.capacity() < cap);
867                        pool.insert(insert_idx, v);
868                    }
869                    // Otherwise let v deallocate - it's smaller than everything in pool
870                }
871            });
872        }
873    }
874}
875
876impl<'a> IntoIterator for &'a RowIdVec {
877    type Item = &'a i64;
878    type IntoIter = std::slice::Iter<'a, i64>;
879
880    #[inline]
881    fn into_iter(self) -> Self::IntoIter {
882        self.inner.as_ref().unwrap().iter()
883    }
884}
885
886/// Owning iterator for RowIdVec.
887///
888/// Note: The buffer is NOT returned to pool when using into_iter().
889/// This is because std::vec::IntoIter consumes the buffer and it cannot
890/// be recovered in stable Rust. For buffer reuse, prefer iter() + collect().
891pub struct RowIdVecIntoIter {
892    inner: std::vec::IntoIter<i64>,
893}
894
895impl Iterator for RowIdVecIntoIter {
896    type Item = i64;
897
898    #[inline]
899    fn next(&mut self) -> Option<Self::Item> {
900        self.inner.next()
901    }
902
903    #[inline]
904    fn size_hint(&self) -> (usize, Option<usize>) {
905        self.inner.size_hint()
906    }
907}
908
909impl ExactSizeIterator for RowIdVecIntoIter {}
910
911impl IntoIterator for RowIdVec {
912    type Item = i64;
913    type IntoIter = RowIdVecIntoIter;
914
915    #[inline]
916    fn into_iter(mut self) -> Self::IntoIter {
917        let v = self.inner.take().unwrap_or_default();
918        RowIdVecIntoIter {
919            inner: v.into_iter(),
920        }
921    }
922}
923
924// Allow collecting into RowIdVec
925impl FromIterator<i64> for RowIdVec {
926    fn from_iter<I: IntoIterator<Item = i64>>(iter: I) -> Self {
927        let iter = iter.into_iter();
928        let (lower, upper) = iter.size_hint();
929        let capacity = upper.unwrap_or(lower).max(16);
930        let mut rv = RowIdVec::with_capacity(capacity);
931        for item in iter {
932            rv.push(item);
933        }
934        rv
935    }
936}
937
938#[cfg(test)]
939mod tests {
940    use super::*;
941    use crate::Value;
942
943    #[test]
944    fn test_row_vec_basic() {
945        let mut rv = RowVec::new();
946        rv.push((1, Row::from_values(vec![Value::Integer(1)])));
947        rv.push((2, Row::from_values(vec![Value::Integer(2)])));
948
949        assert_eq!(rv.len(), 2);
950        assert!(!rv.is_empty());
951    }
952
953    #[test]
954    fn test_row_vec_cache_reuse() {
955        // First allocation with known capacity
956        {
957            let mut rv = RowVec::with_capacity(100);
958            rv.push((1, Row::from_values(vec![Value::Integer(1)])));
959            // rv drops here, returns to cache
960        }
961
962        // Should reuse from cache - capacity preserved
963        let rv2 = RowVec::new();
964        assert!(
965            rv2.capacity() >= 100,
966            "Expected capacity >= 100, got {}",
967            rv2.capacity()
968        );
969    }
970
971    #[test]
972    fn test_reused_buffers_guarantee_adjacent_requested_capacity() {
973        clear_row_vec_pool();
974        clear_row_id_vec_pool();
975
976        {
977            let row_vec = RowVec::with_capacity(16);
978            assert_eq!(row_vec.capacity(), 16);
979        }
980        let row_vec = RowVec::with_capacity(17);
981        assert!(row_vec.capacity() >= 17);
982
983        {
984            let row_ids = RowIdVec::with_capacity(16);
985            assert_eq!(row_ids.capacity(), 16);
986        }
987        let row_ids = RowIdVec::with_capacity(17);
988        assert!(row_ids.capacity() >= 17);
989    }
990
991    #[test]
992    fn test_row_vec_into_iter() {
993        let mut rv = RowVec::new();
994        rv.push((1, Row::from_values(vec![Value::Integer(1)])));
995        rv.push((2, Row::from_values(vec![Value::Integer(2)])));
996
997        let collected: Vec<_> = rv.into_iter().collect();
998        assert_eq!(collected.len(), 2);
999        // Verify forward iteration order
1000        assert_eq!(collected[0].0, 1);
1001        assert_eq!(collected[1].0, 2);
1002    }
1003
1004    #[test]
1005    fn test_row_vec_rev() {
1006        let mut rv = RowVec::new();
1007        rv.push((1, Row::from_values(vec![Value::Integer(1)])));
1008        rv.push((2, Row::from_values(vec![Value::Integer(2)])));
1009        rv.push((3, Row::from_values(vec![Value::Integer(3)])));
1010
1011        // Test .rev() iterator
1012        let collected: Vec<_> = rv.into_iter().rev().collect();
1013        assert_eq!(collected.len(), 3);
1014        assert_eq!(collected[0].0, 3); // Last becomes first
1015        assert_eq!(collected[1].0, 2);
1016        assert_eq!(collected[2].0, 1); // First becomes last
1017    }
1018
1019    #[test]
1020    fn test_row_vec_skip_take_rev() {
1021        let mut rv = RowVec::new();
1022        for i in 1..=10 {
1023            rv.push((i, Row::from_values(vec![Value::Integer(i)])));
1024        }
1025
1026        // Test .rev().skip().take() pattern
1027        let collected: Vec<_> = rv.into_iter().rev().skip(2).take(3).collect();
1028        assert_eq!(collected.len(), 3);
1029        assert_eq!(collected[0].0, 8); // 10-2 skip = 8
1030        assert_eq!(collected[1].0, 7);
1031        assert_eq!(collected[2].0, 6);
1032    }
1033
1034    #[test]
1035    fn test_row_vec_pool_keeps_buffers() {
1036        // Create a buffer and return it to pool
1037        {
1038            let mut rv = RowVec::with_capacity(500);
1039            rv.push((1, Row::from_values(vec![Value::Integer(1)])));
1040            // rv drops here, returns to pool
1041        }
1042
1043        // Get a buffer - should come from pool with capacity >= 500
1044        let rv2 = RowVec::new();
1045        assert!(
1046            rv2.capacity() >= 16, // At least default capacity
1047            "Expected capacity >= 16, got {}",
1048            rv2.capacity()
1049        );
1050    }
1051
1052    #[test]
1053    fn test_row_vec_pool_respects_max_capacity() {
1054        // Create a buffer larger than MAX_CACHED_CAPACITY
1055        // This should NOT be pooled
1056        {
1057            let mut rv = RowVec::with_capacity(MAX_CACHED_CAPACITY + 1000);
1058            rv.push((1, Row::from_values(vec![Value::Integer(1)])));
1059            // rv drops here, but should NOT be pooled due to size limit
1060        }
1061
1062        // Verify the pool didn't store the oversized buffer by checking
1063        // that new allocations don't get the oversized capacity
1064        let rv2 = RowVec::with_capacity(16);
1065        assert!(
1066            rv2.capacity() < MAX_CACHED_CAPACITY,
1067            "Expected small capacity, got {}",
1068            rv2.capacity()
1069        );
1070    }
1071
1072    #[test]
1073    fn test_row_vec_pool_concurrent_usage() {
1074        // This test verifies multi-slot pool handles concurrent RowVecs
1075        // Create multiple RowVecs simultaneously with specific capacities
1076        let mut rv1 = RowVec::with_capacity(100);
1077        let mut rv2 = RowVec::with_capacity(200);
1078        let mut rv3 = RowVec::with_capacity(300);
1079        rv1.push((1, Row::from_values(vec![Value::Integer(1)])));
1080        rv2.push((2, Row::from_values(vec![Value::Integer(2)])));
1081        rv3.push((3, Row::from_values(vec![Value::Integer(3)])));
1082
1083        // Verify they all have at least the requested capacity
1084        assert!(
1085            rv1.capacity() >= 100,
1086            "rv1 capacity {} < 100",
1087            rv1.capacity()
1088        );
1089        assert!(
1090            rv2.capacity() >= 200,
1091            "rv2 capacity {} < 200",
1092            rv2.capacity()
1093        );
1094        assert!(
1095            rv3.capacity() >= 300,
1096            "rv3 capacity {} < 300",
1097            rv3.capacity()
1098        );
1099
1100        // Drop all - they should return to pool
1101        drop(rv1);
1102        drop(rv2);
1103        drop(rv3);
1104
1105        // Now get three more - should come from pool with preserved capacities
1106        let rv_a = RowVec::new();
1107        let rv_b = RowVec::new();
1108        let rv_c = RowVec::new();
1109
1110        // Pool returns largest first, so we should get buffers with good capacity
1111        // At minimum, we should have some capacity from the pool
1112        assert!(
1113            rv_a.capacity() >= 16,
1114            "rv_a capacity {} < 16",
1115            rv_a.capacity()
1116        );
1117        assert!(
1118            rv_b.capacity() >= 16,
1119            "rv_b capacity {} < 16",
1120            rv_b.capacity()
1121        );
1122        assert!(
1123            rv_c.capacity() >= 16,
1124            "rv_c capacity {} < 16",
1125            rv_c.capacity()
1126        );
1127    }
1128}