1use rudb_common::{Error, Field, LogicalType, Result, Value};
4use rudb_storage::MemoryTable;
5use rudb_vector::{Chunk, Form};
6
7use crate::name::{QualifiedName, same_name};
8
9pub fn duplicate_check(columns: &[Field]) -> Result<()> {
20 for (at, column) in columns.iter().enumerate() {
21 if columns[..at].iter().any(|held| same_name(&held.name, &column.name)) {
22 return Err(Error::catalog(format!(
25 "Column with name {} already exists!",
26 column.name
27 )));
28 }
29 }
30 Ok(())
31}
32
33#[derive(Debug, Clone)]
39pub struct Table {
40 name: QualifiedName,
41 columns: Vec<Field>,
42 rows: MemoryTable,
43}
44
45impl Table {
46 pub fn new(name: QualifiedName, columns: Vec<Field>) -> Result<Self> {
56 duplicate_check(&columns)?;
57 let types = columns.iter().map(|column| column.ty.clone()).collect();
58 Ok(Self { name, columns, rows: MemoryTable::new(types) })
59 }
60
61 #[must_use]
63 pub fn name(&self) -> &QualifiedName {
64 &self.name
65 }
66
67 #[must_use]
69 pub fn columns(&self) -> &[Field] {
70 &self.columns
71 }
72
73 #[must_use]
75 pub fn types(&self) -> Vec<LogicalType> {
76 self.columns.iter().map(|column| column.ty.clone()).collect()
77 }
78
79 #[must_use]
81 pub fn column_index(&self, name: &str) -> Option<usize> {
82 self.columns.iter().position(|column| same_name(&column.name, name))
83 }
84
85 #[must_use]
87 pub fn rows(&self) -> &MemoryTable {
88 &self.rows
89 }
90
91 pub fn rows_mut(&mut self) -> &mut MemoryTable {
97 &mut self.rows
98 }
99
100 pub fn append(&mut self, chunk: Chunk) -> Result<()> {
108 self.refuse_nulls(&chunk)?;
109 self.rows.append(chunk)
110 }
111
112 pub fn append_rows(&mut self, rows: &[Vec<Value>]) -> Result<()> {
119 for row in rows {
120 for (at, column) in self.columns.iter().enumerate() {
121 if column.not_null && row.get(at).is_some_and(Value::is_null) {
122 return Err(self.null_in(&column.name));
123 }
124 }
125 }
126 self.rows.append_rows(rows)
127 }
128
129 fn refuse_nulls(&self, chunk: &Chunk) -> Result<()> {
137 for (at, column) in self.columns.iter().enumerate() {
138 if !column.not_null {
139 continue;
140 }
141 let vector = chunk.column(at)?;
142 let found = match vector.form() {
143 Form::Flat | Form::Sequence => {
144 vector.validity().has_nulls(vector.len())
145 && (0..vector.len()).any(|row| !vector.validity().is_valid(row))
146 }
147 _ => (0..vector.len()).any(|row| vector.value_at(row).is_null()),
148 };
149 if found {
150 return Err(self.null_in(&column.name));
151 }
152 }
153 Ok(())
154 }
155
156 fn null_in(&self, column: &str) -> Error {
158 Error::constraint(format!("NOT NULL constraint failed: {}.{}", self.name.table, column))
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use rudb_vector::Vector;
165
166 use super::*;
167
168 fn hits() -> Table {
169 Table::new(
170 QualifiedName::new("memory", "main", "hits"),
171 vec![
172 Field::new("UserID", LogicalType::BigInt),
173 Field::new("SearchPhrase", LogicalType::Varchar),
174 ],
175 )
176 .expect("two columns with different names")
177 }
178
179 #[test]
180 fn a_column_is_found_however_it_is_spelled() {
181 let table = hits();
182 assert_eq!(table.column_index("userid"), Some(0));
183 assert_eq!(table.column_index("SEARCHPHRASE"), Some(1));
184 assert_eq!(table.column_index("nope"), None);
185 }
186
187 #[test]
188 fn two_columns_with_one_name_is_caught() {
189 let error = Table::new(
190 QualifiedName::new("memory", "main", "t"),
191 vec![Field::new("a", LogicalType::Integer), Field::new("A", LogicalType::Varchar)],
192 )
193 .expect_err("two columns called a");
194 assert_eq!(error.to_string(), "Catalog Error: Column with name A already exists!");
197 }
198
199 #[test]
200 fn a_new_table_is_empty_and_typed() {
201 let mut table = hits();
202 assert!(table.rows().is_empty());
203 assert_eq!(table.rows().types(), table.types());
204 table
205 .rows_mut()
206 .append_rows(&[vec![Value::BigInt(1), Value::Varchar("a".to_string())]])
207 .expect("a row of the table's own types");
208 assert_eq!(table.rows().len(), 1);
209 }
210
211 fn required() -> Table {
213 Table::new(
214 QualifiedName::new("memory", "main", "hits"),
215 vec![
216 Field::required("UserID", LogicalType::BigInt),
217 Field::new("SearchPhrase", LogicalType::Varchar),
218 ],
219 )
220 .expect("two columns with different names")
221 }
222
223 #[test]
224 fn a_null_in_a_not_null_column_is_refused() {
225 let mut table = required();
226 let error = table
227 .append_rows(&[vec![Value::Null, Value::Varchar("a".to_string())]])
228 .expect_err("a null in UserID");
229 assert_eq!(error.message(), "NOT NULL constraint failed: hits.UserID");
230 assert!(table.rows().is_empty(), "the row was kept anyway");
231 }
232
233 #[test]
234 fn a_null_in_a_column_that_allows_them_is_kept() {
235 let mut table = required();
236 table.append_rows(&[vec![Value::BigInt(7), Value::Null]]).expect("a null in SearchPhrase");
237 assert_eq!(table.rows().len(), 1);
238 }
239
240 #[test]
241 fn a_chunk_is_checked_through_its_mask() {
242 let mut table = required();
243 let phrase = Vector::constant(LogicalType::Varchar, Value::Varchar("a".to_string()), 2);
244 let good = Chunk::new(vec![
245 Vector::from_values(LogicalType::BigInt, &[Value::BigInt(1), Value::BigInt(2)])
246 .expect("two ids"),
247 phrase.clone(),
248 ])
249 .expect("two columns of two rows");
250 table.append(good).expect("no nulls anywhere");
251 let bad = Chunk::new(vec![
252 Vector::from_values(LogicalType::BigInt, &[Value::BigInt(1), Value::Null])
253 .expect("an id and a null"),
254 phrase,
255 ])
256 .expect("two columns of two rows");
257 let error = table.append(bad).expect_err("a null in UserID");
258 assert_eq!(error.message(), "NOT NULL constraint failed: hits.UserID");
259 assert_eq!(table.rows().len(), 2, "the bad chunk was kept anyway");
260 }
261
262 #[test]
263 fn a_null_hiding_in_a_constant_is_found() {
264 let mut table = required();
265 let chunk = Chunk::new(vec![
266 Vector::constant(LogicalType::BigInt, Value::Null, 4),
267 Vector::constant(LogicalType::Varchar, Value::Varchar("a".to_string()), 4),
268 ])
269 .expect("two columns of four rows");
270 let error = table.append(chunk).expect_err("a constant null in UserID");
271 assert_eq!(error.message(), "NOT NULL constraint failed: hits.UserID");
272 }
273}