Skip to main content

rudb_storage/
memory.rs

1//! A table that lives in memory, which is what M0 stores rows in.
2//!
3//! This is not the storage format. There are no blocks, no row groups, no statistics, no
4//! compression and no buffer manager in here, and every one of those is what the rest of this crate
5//! becomes at M2. What this is, is somewhere for rows to be so that the binder and the executor can
6//! be written and tested against something real, and a shape that the real thing can replace
7//! without the layers above it noticing: a table is a sequence of chunks, a scan reads them in
8//! order, and a scan asks for the columns it wants rather than all of them.
9//!
10//! The one thing it does get right on purpose is that a read is by chunk and by column, and not by
11//! row. A row-at-a-time interface here would be an interface every operator above would grow
12//! against, and unwinding that later is the rewrite this project exists to avoid.
13
14use rudb_common::{Error, LogicalType, Result, Value};
15use rudb_vector::vector::VECTOR_SIZE;
16use rudb_vector::{Chunk, Vector};
17
18/// A table held in memory as a sequence of chunks.
19#[derive(Debug, Clone)]
20pub struct MemoryTable {
21    types: Vec<LogicalType>,
22    chunks: Vec<Chunk>,
23    rows: usize,
24}
25
26impl MemoryTable {
27    /// An empty table of the given column types.
28    #[must_use]
29    pub fn new(types: Vec<LogicalType>) -> Self {
30        Self { types, chunks: Vec::new(), rows: 0 }
31    }
32
33    /// The column types.
34    #[must_use]
35    pub fn types(&self) -> &[LogicalType] {
36        &self.types
37    }
38
39    /// How many columns.
40    #[must_use]
41    pub fn width(&self) -> usize {
42        self.types.len()
43    }
44
45    /// How many rows, across every chunk.
46    #[must_use]
47    pub fn len(&self) -> usize {
48        self.rows
49    }
50
51    /// Whether the table has no rows.
52    #[must_use]
53    pub fn is_empty(&self) -> bool {
54        self.rows == 0
55    }
56
57    /// How many chunks a scan will read.
58    #[must_use]
59    pub fn chunk_count(&self) -> usize {
60        self.chunks.len()
61    }
62
63    /// Appends a chunk, which has to have the table's column types.
64    ///
65    /// An empty chunk is dropped rather than stored, because a scan that has to skip empty chunks
66    /// is a scan with a branch in it that exists only because an operator upstream was sloppy.
67    ///
68    /// # Errors
69    ///
70    /// If the chunk's columns are not the table's columns.
71    pub fn append(&mut self, chunk: Chunk) -> Result<()> {
72        if chunk.width() != self.types.len() {
73            return Err(Error::internal(format!(
74                "a chunk of {} columns appended to a table of {}",
75                chunk.width(),
76                self.types.len()
77            )));
78        }
79        for (index, (held, wanted)) in chunk.types().iter().zip(&self.types).enumerate() {
80            if held != wanted {
81                return Err(Error::internal(format!(
82                    "column {index} of the chunk is {held} and the table's is {wanted}"
83                )));
84            }
85        }
86        if chunk.is_empty() {
87            return Ok(());
88        }
89        self.rows += chunk.len();
90        self.chunks.push(chunk);
91        Ok(())
92    }
93
94    /// Appends rows given one at a time, splitting them into chunks.
95    ///
96    /// The slow way in, for an `INSERT` and for a test. It transposes, which is the whole cost:
97    /// rows arrive across the columns and a chunk is down them.
98    ///
99    /// # Errors
100    ///
101    /// If a row is not as wide as the table, or if a value is not one its column can hold.
102    pub fn append_rows(&mut self, rows: &[Vec<Value>]) -> Result<()> {
103        for (index, row) in rows.iter().enumerate() {
104            if row.len() != self.types.len() {
105                return Err(Error::internal(format!(
106                    "row {index} has {} values and the table has {} columns",
107                    row.len(),
108                    self.types.len()
109                )));
110            }
111        }
112        for batch in rows.chunks(VECTOR_SIZE) {
113            let mut columns = Vec::with_capacity(self.types.len());
114            for (position, ty) in self.types.iter().enumerate() {
115                let down: Vec<Value> = batch.iter().map(|row| row[position].clone()).collect();
116                columns.push(Vector::from_values(ty.clone(), &down)?);
117            }
118            self.append(Chunk::with_rows(columns, batch.len())?)?;
119        }
120        Ok(())
121    }
122
123    /// One chunk's worth of the named columns, in the order they are named.
124    ///
125    /// The columns are copied, because a vector owns its buffer and there is nothing to borrow
126    /// from yet. Borrowed buffers with a pin are the M2 item in `spec/07-execution.md` section 7.1,
127    /// and this is the call that will stop copying when they arrive. Asking for the columns rather
128    /// than taking them all is what makes that copy proportional to the query instead of to the
129    /// table, which is the same reason projection pushdown exists.
130    ///
131    /// # Errors
132    ///
133    /// If there is no such chunk, or if a column is past the end of the table.
134    pub fn read(&self, chunk: usize, columns: &[usize]) -> Result<Chunk> {
135        let held = self.chunks.get(chunk).ok_or_else(|| {
136            Error::internal(format!(
137                "chunk {chunk} of a table that has {} chunks",
138                self.chunks.len()
139            ))
140        })?;
141        let mut picked = Vec::with_capacity(columns.len());
142        for &column in columns {
143            picked.push(held.column(column)?.clone());
144        }
145        Chunk::with_rows(picked, held.len())
146    }
147
148    /// One stored chunk, whole.
149    #[must_use]
150    pub fn chunk(&self, index: usize) -> Option<&Chunk> {
151        self.chunks.get(index)
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    fn people() -> MemoryTable {
160        let mut table = MemoryTable::new(vec![LogicalType::Integer, LogicalType::Varchar]);
161        table
162            .append_rows(&[
163                vec![Value::Integer(1), Value::Varchar("ada".to_string())],
164                vec![Value::Integer(2), Value::Null],
165                vec![Value::Integer(3), Value::Varchar("grace".to_string())],
166            ])
167            .expect("three rows of the table's own types");
168        table
169    }
170
171    #[test]
172    fn rows_go_in_and_come_back_out() {
173        let table = people();
174        assert_eq!(table.len(), 3);
175        assert_eq!(table.chunk_count(), 1);
176        let chunk = table.read(0, &[0, 1]).expect("both columns of the only chunk");
177        assert_eq!(chunk.value_at(0, 1), Value::Varchar("ada".to_string()));
178        assert_eq!(chunk.value_at(1, 1), Value::Null);
179        assert_eq!(chunk.value_at(2, 0), Value::Integer(3));
180    }
181
182    #[test]
183    fn a_read_gives_back_only_the_columns_it_was_asked_for() {
184        let table = people();
185        let chunk = table.read(0, &[1]).expect("the second column");
186        assert_eq!(chunk.width(), 1);
187        assert_eq!(chunk.len(), 3);
188        assert_eq!(chunk.value_at(2, 0), Value::Varchar("grace".to_string()));
189    }
190
191    /// `SELECT count(*) FROM t` reads no columns and still has to be told how many rows there were.
192    #[test]
193    fn a_read_of_no_columns_still_says_how_many_rows() {
194        let table = people();
195        let chunk = table.read(0, &[]).expect("no columns");
196        assert_eq!(chunk.width(), 0);
197        assert_eq!(chunk.len(), 3);
198    }
199
200    #[test]
201    fn more_rows_than_a_vector_become_more_than_one_chunk() {
202        let mut table = MemoryTable::new(vec![LogicalType::BigInt]);
203        let rows: Vec<Vec<Value>> =
204            (0..VECTOR_SIZE + 5).map(|n| vec![Value::BigInt(n as i64)]).collect();
205        table.append_rows(&rows).expect("bigints");
206        assert_eq!(table.len(), VECTOR_SIZE + 5);
207        assert_eq!(table.chunk_count(), 2);
208        let last = table.read(1, &[0]).expect("the second chunk");
209        assert_eq!(last.len(), 5);
210        assert_eq!(last.value_at(4, 0), Value::BigInt((VECTOR_SIZE + 4) as i64));
211    }
212
213    #[test]
214    fn a_chunk_of_the_wrong_types_is_caught() {
215        let mut table = MemoryTable::new(vec![LogicalType::Integer]);
216        let wrong = Chunk::new(vec![
217            Vector::from_values(LogicalType::Varchar, &[Value::Varchar("x".to_string())])
218                .expect("a string column"),
219        ])
220        .expect("one column");
221        let error = table.append(wrong).expect_err("a varchar is not an integer");
222        assert!(error.message().contains("column 0"), "{error}");
223    }
224
225    #[test]
226    fn a_row_of_the_wrong_width_is_caught() {
227        let mut table = MemoryTable::new(vec![LogicalType::Integer, LogicalType::Integer]);
228        let error =
229            table.append_rows(&[vec![Value::Integer(1)]]).expect_err("a row of one is not a row");
230        assert!(error.message().contains("row 0"), "{error}");
231    }
232
233    #[test]
234    fn an_empty_chunk_is_not_stored() {
235        let mut table = MemoryTable::new(vec![LogicalType::Integer]);
236        table.append(Chunk::empty(&[LogicalType::Integer])).expect("an empty chunk is allowed");
237        assert_eq!(table.chunk_count(), 0);
238        assert!(table.is_empty());
239    }
240}