• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rust-bio / rust-htslib / 8009831853

22 Feb 2024 07:17PM UTC coverage: 79.823% (+0.03%) from 79.791%
8009831853

Pull #419

github

web-flow
Merge 237f9889d into 01c884945
Pull Request #419: feat: making several RecordBuffer methods public

0 of 3 new or added lines in 1 file covered. (0.0%)

2441 of 3058 relevant lines covered (79.82%)

2.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

50.88
/src/bam/buffer.rs
1
// Copyright 2017 Johannes Köster.
2
// Licensed under the MIT license (http://opensource.org/licenses/MIT)
3
// This file may not be copied, modified, or distributed
4
// except according to those terms.
5

6
use std::collections::{vec_deque, VecDeque};
7
use std::mem;
8
use std::rc::Rc;
9
use std::str;
10

11
use crate::bam;
12
use crate::bam::Read;
13
use crate::errors::{Error, Result};
14

15
/// A buffer for BAM records. This allows access regions in a sorted BAM file while iterating
16
/// over it in a single pass.
17
/// The buffer is implemented as a ringbuffer, such that extension or movement to the right has
18
/// linear complexity. The buffer makes use of indexed random access. Hence, when fetching a
19
/// region at the very end of the BAM, everything before is omitted without cost.
20
#[derive(Debug)]
21
pub struct RecordBuffer {
22
    reader: bam::IndexedReader,
23
    inner: VecDeque<Rc<bam::Record>>,
24
    overflow: Option<Rc<bam::Record>>,
25
    cache_cigar: bool,
26
    min_refetch_distance: u64,
27
    buffer_record: Rc<bam::Record>,
28
}
29

30
unsafe impl Sync for RecordBuffer {}
31
unsafe impl Send for RecordBuffer {}
32

33
impl RecordBuffer {
34
    /// Create a new `RecordBuffer`.
35
    ///
36
    /// # Arguments
37
    ///
38
    /// * `bam` - BAM reader
39
    /// * `cache_cigar` - whether to call `bam::Record::cache_cigar()` for each record.
40
    pub fn new(bam: bam::IndexedReader, cache_cigar: bool) -> Self {
1✔
41
        RecordBuffer {
42
            reader: bam,
43
            inner: VecDeque::new(),
1✔
44
            overflow: None,
45
            cache_cigar,
46
            min_refetch_distance: 1,
47
            buffer_record: Rc::new(bam::Record::new()),
2✔
48
        }
49
    }
50

51
    /// maximum distance to previous fetch window such that a
52
    /// new fetch operation is performed. If the distance is smaller, buffer will simply
53
    /// read through until the start of the new fetch window (probably saving some time
54
    /// by avoiding the random access).
55
    pub fn set_min_refetch_distance(&mut self, min_refetch_distance: u64) {
×
56
        self.min_refetch_distance = min_refetch_distance;
×
57
    }
58

59
    /// Return start position of buffer
NEW
60
    pub fn start(&self) -> Option<u64> {
×
61
        self.inner.front().map(|rec| rec.pos() as u64)
×
62
    }
63

64
    /// Return end position of buffer.
NEW
65
    pub fn end(&self) -> Option<u64> {
×
66
        self.inner.back().map(|rec| rec.pos() as u64)
×
67
    }
68

NEW
69
    pub fn tid(&self) -> Option<i32> {
×
70
        self.inner.back().map(|rec| rec.tid())
×
71
    }
72

73
    /// Fill buffer at the given interval. If the start coordinate is left of
74
    /// the previous start coordinate, this will use an additional BAM fetch IO operation.
75
    /// Coordinates are 0-based, and end is exclusive.
76
    /// Returns tuple with numbers of added and deleted records since the previous fetch.
77
    #[allow(unused_assignments)] // TODO this is needed because rustc thinks that deleted is unused
78
    pub fn fetch(&mut self, chrom: &[u8], start: u64, end: u64) -> Result<(usize, usize)> {
1✔
79
        let mut added = 0;
1✔
80
        // move overflow from last fetch into ringbuffer
81
        if self.overflow.is_some() {
1✔
82
            added += 1;
×
83
            self.inner.push_back(self.overflow.take().unwrap());
×
84
        }
85

86
        if let Some(tid) = self.reader.header.tid(chrom) {
4✔
87
            let mut deleted = 0;
1✔
88
            let window_start = start;
1✔
89
            if self.inner.is_empty()
1✔
90
                || window_start.saturating_sub(self.end().unwrap()) >= self.min_refetch_distance
×
91
                || self.tid().unwrap() != tid as i32
×
92
                || self.start().unwrap() > window_start
×
93
            {
94
                let end = self.reader.header.target_len(tid).unwrap();
3✔
95
                self.reader.fetch((tid, window_start, end))?;
1✔
96
                deleted = self.inner.len();
1✔
97
                self.inner.clear();
1✔
98
            } else {
99
                // remove records too far left
100
                let to_remove = self
×
101
                    .inner
102
                    .iter()
103
                    .take_while(|rec| rec.pos() < window_start as i64)
×
104
                    .count();
105
                for _ in 0..to_remove {
×
106
                    self.inner.pop_front();
×
107
                }
108
                deleted = to_remove;
×
109
            }
110

111
            // extend to the right
112
            loop {
1✔
113
                match self
1✔
114
                    .reader
115
                    .read(Rc::get_mut(&mut self.buffer_record).unwrap())
1✔
116
                {
117
                    None => break,
118
                    Some(res) => res?,
2✔
119
                }
120

121
                if self.buffer_record.is_unmapped() {
2✔
122
                    continue;
123
                }
124

125
                let pos = self.buffer_record.pos();
2✔
126

127
                // skip records before the start
128
                if pos < start as i64 {
1✔
129
                    continue;
130
                }
131

132
                // Record is kept, do not reuse it for next iteration
133
                // and thus create a new one.
134
                let mut record = mem::replace(&mut self.buffer_record, Rc::new(bam::Record::new()));
1✔
135

136
                if self.cache_cigar {
1✔
137
                    Rc::get_mut(&mut record).unwrap().cache_cigar();
×
138
                }
139

140
                if pos >= end as i64 {
1✔
141
                    self.overflow = Some(record);
×
142
                    break;
143
                } else {
144
                    self.inner.push_back(record);
1✔
145
                    added += 1;
1✔
146
                }
147
            }
148

149
            Ok((added, deleted))
1✔
150
        } else {
151
            Err(Error::UnknownSequence {
×
152
                sequence: str::from_utf8(chrom).unwrap().to_owned(),
×
153
            })
154
        }
155
    }
156

157
    /// Iterate over records that have been fetched with `fetch`.
158
    pub fn iter(&self) -> vec_deque::Iter<Rc<bam::Record>> {
1✔
159
        self.inner.iter()
1✔
160
    }
161

162
    /// Iterate over mutable references to records that have been fetched with `fetch`.
163
    pub fn iter_mut(&mut self) -> vec_deque::IterMut<Rc<bam::Record>> {
×
164
        self.inner.iter_mut()
×
165
    }
166

167
    pub fn len(&self) -> usize {
×
168
        self.inner.len()
×
169
    }
170

171
    pub fn is_empty(&self) -> bool {
×
172
        self.len() == 0
×
173
    }
174
}
175

176
#[cfg(test)]
177
mod tests {
178
    use super::*;
179
    use crate::bam;
180

181
    #[test]
182
    fn test_buffer() {
183
        let reader = bam::IndexedReader::from_path(&"test/test.bam").unwrap();
184
        let mut buffer = RecordBuffer::new(reader, false);
185

186
        buffer.fetch(b"CHROMOSOME_I", 1, 5).unwrap();
187
        {
188
            let records: Vec<_> = buffer.iter().collect();
189
            assert_eq!(records.len(), 6);
190
            assert_eq!(records[0].pos(), 1);
191
            assert_eq!(records[1].pos(), 1);
192
            assert_eq!(records[2].pos(), 1);
193
            assert_eq!(records[3].pos(), 1);
194
            assert_eq!(records[4].pos(), 1);
195
            assert_eq!(records[5].pos(), 1);
196
        }
197
    }
198
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc