1use rudb_common::{Error, Field, LogicalType, Result, Value};
13use rudb_io::File;
14use rudb_kernels::cast_value;
15use rudb_vector::{Chunk, VECTOR_SIZE, Vector};
16
17use crate::dialect::{self, Dialect};
18use crate::infer;
19
20const BLOCK: usize = 1 << 20;
26
27#[derive(Debug)]
29pub struct Reader {
30 file: Box<dyn File>,
31 path: String,
32 dialect: Dialect,
33 fields: Vec<Field>,
34 projection: Vec<usize>,
35 buffer: Vec<u8>,
36 at: usize,
37 offset: u64,
38 drained: bool,
39 line: u64,
40 scratch: Vec<String>,
41}
42
43impl Reader {
44 pub fn open(file: Box<dyn File>, path: &str) -> Result<Self> {
53 let mut reader = Self {
54 file,
55 path: path.to_string(),
56 dialect: Dialect::comma_separated(),
57 fields: Vec::new(),
58 projection: Vec::new(),
59 buffer: Vec::new(),
60 at: 0,
61 offset: 0,
62 drained: false,
63 line: 1,
64 scratch: Vec::new(),
65 };
66 reader.fill()?;
67 let sample = reader.buffer.clone();
68 let quote = dialect::quote(&sample);
69 let delimiter = dialect::delimiter(&sample, quote)?;
70 reader.dialect = Dialect { delimiter, quote, escape: quote, header: false };
71 let rows = reader.sample_rows(&sample)?;
72 let (header, fields) = describe(&rows);
73 reader.dialect.header = header;
74 reader.fields = fields;
75 reader.projection = (0..reader.fields.len()).collect();
76 if header {
77 reader.skip_record()?;
78 }
79 Ok(reader)
80 }
81
82 #[must_use]
84 pub fn fields(&self) -> Vec<Field> {
85 self.projection.iter().map(|&at| self.fields[at].clone()).collect()
86 }
87
88 pub fn project(&mut self, columns: &[usize]) -> Result<()> {
94 for &column in columns {
95 if column >= self.fields.len() {
96 return Err(Error::io(format!(
97 "column {column} is past the {} the file has",
98 self.fields.len()
99 )));
100 }
101 }
102 self.projection = columns.to_vec();
103 Ok(())
104 }
105
106 #[must_use]
108 pub const fn dialect(&self) -> Dialect {
109 self.dialect
110 }
111
112 pub fn next_chunk(&mut self) -> Result<Option<Chunk>> {
119 let mut rows: Vec<Vec<Option<String>>> = Vec::new();
120 while rows.len() < VECTOR_SIZE {
121 match self.next_record()? {
122 Some(fields) => rows.push(fields),
123 None => break,
124 }
125 }
126 if rows.is_empty() {
127 return Ok(None);
128 }
129 let mut columns = Vec::with_capacity(self.projection.len());
130 for &at in &self.projection {
131 let field = &self.fields[at];
132 let mut values = Vec::with_capacity(rows.len());
133 for (row, held) in rows.iter().enumerate() {
134 let text = held.get(at).and_then(Option::as_deref);
135 values.push(self.convert(
136 text,
137 field,
138 self.line - rows.len() as u64 + row as u64,
139 )?);
140 }
141 columns.push(Vector::from_values(field.ty.clone(), &values)?);
142 }
143 Ok(Some(Chunk::with_rows(columns, rows.len())?))
144 }
145
146 fn convert(&self, text: Option<&str>, field: &Field, line: u64) -> Result<Value> {
148 let Some(text) = text else { return Ok(Value::Null) };
149 if field.ty == LogicalType::Varchar {
150 return Ok(Value::Varchar(text.to_string()));
151 }
152 let value = Value::Varchar(text.to_string());
153 match cast_value(&value, &field.ty, false) {
154 Ok(converted) => Ok(converted),
155 Err(_) => Err(Error::conversion(self.conversion_error(text, field, line))),
156 }
157 }
158
159 fn conversion_error(&self, text: &str, field: &Field, line: u64) -> String {
166 format!(
167 "CSV Error on Line: {line}\nOriginal Line: {text}\nError when converting column \
168 \"{}\". Could not convert string \"{text}\" to '{}'\n\nColumn {} is being converted \
169 as type {}\nThis type was auto-detected from the CSV file.\nPossible solutions:\n* \
170 Override the type for this column manually by setting the type explicitly, e.g., \
171 types={{'{}': 'VARCHAR'}}\n* Set the sample size to a larger value to enable the \
172 auto-detection to scan more values, e.g., sample_size=-1\n* Use a COPY statement to \
173 automatically derive types from an existing table.\n* Check whether the null string \
174 value is set correctly (e.g., nullstr = 'N/A')\n\n file = {}\n delimiter = {} \
175 (Auto-Detected)\n quote = {} (Auto-Detected)\n escape = {} (Auto-Detected)\n \
176 header = {} (Auto-Detected)\n sample_size = {}\n",
177 field.name,
178 field.ty,
179 field.name,
180 field.ty,
181 field.name,
182 self.path,
183 Dialect::shown(Some(self.dialect.delimiter)),
184 Dialect::shown(self.dialect.quote),
185 Dialect::shown(self.dialect.escape),
186 self.dialect.header,
187 infer::SAMPLE,
188 )
189 }
190
191 fn next_record(&mut self) -> Result<Option<Vec<Option<String>>>> {
193 let Some(()) = self.advance()? else { return Ok(None) };
194 Ok(Some(
195 self.scratch
196 .iter()
197 .map(|text| if text.is_empty() { None } else { Some(text.clone()) })
198 .collect(),
199 ))
200 }
201
202 fn advance(&mut self) -> Result<Option<()>> {
204 loop {
205 let mut scratch = std::mem::take(&mut self.scratch);
206 let outcome = crate::scan::record(
207 &self.buffer,
208 self.at,
209 self.dialect,
210 self.drained,
211 &mut scratch,
212 );
213 self.scratch = scratch;
214 match outcome? {
215 Some(next) => {
216 self.at = next;
217 self.line += 1;
218 return Ok(Some(()));
219 }
220 None if self.drained => return Ok(None),
221 None => self.fill()?,
222 }
223 }
224 }
225
226 fn skip_record(&mut self) -> Result<()> {
228 self.advance()?;
229 Ok(())
230 }
231
232 fn fill(&mut self) -> Result<()> {
234 self.buffer.drain(..self.at);
235 self.at = 0;
236 let held = self.buffer.len();
237 self.buffer.resize(held + BLOCK, 0);
238 let read = self.file.read_at(self.offset, &mut self.buffer[held..])?;
239 self.buffer.truncate(held + read);
240 self.offset += read as u64;
241 if read == 0 {
242 self.drained = true;
243 }
244 Ok(())
245 }
246
247 fn sample_rows(&self, sample: &[u8]) -> Result<Vec<Vec<Option<String>>>> {
250 let mut rows = Vec::new();
251 let mut fields = Vec::new();
252 let mut at = 0;
253 while rows.len() <= infer::SAMPLE {
254 let Some(next) = crate::scan::record(sample, at, self.dialect, false, &mut fields)?
257 else {
258 break;
259 };
260 at = next;
261 rows.push(
262 fields
263 .iter()
264 .map(|text| if text.is_empty() { None } else { Some(text.clone()) })
265 .collect(),
266 );
267 }
268 Ok(rows)
269 }
270}
271
272fn describe(rows: &[Vec<Option<String>>]) -> (bool, Vec<Field>) {
279 let width = rows.iter().map(Vec::len).max().unwrap_or(0);
280 let body = types(&rows[1.min(rows.len())..], width);
281 let all_text = body.iter().all(|ty| *ty == LogicalType::Varchar);
282 let first_fits = rows.first().is_some_and(|first| {
283 first.iter().zip(&body).all(|(text, ty)| match text {
284 None => true,
285 Some(text) => infer::fits(text, ty),
286 })
287 });
288 let header = rows.len() > 1 && (all_text || !first_fits);
289 if !header {
290 let types = types(rows, width);
291 let fields = types
292 .into_iter()
293 .enumerate()
294 .map(|(at, ty)| Field::new(format!("column{at}"), ty))
295 .collect();
296 return (false, fields);
297 }
298 let names = unique(&rows[0], width);
299 let fields = body.into_iter().zip(names).map(|(ty, name)| Field::new(name, ty)).collect();
300 (true, fields)
301}
302
303fn unique(header: &[Option<String>], width: usize) -> Vec<String> {
316 let mut taken: Vec<String> = Vec::with_capacity(width);
317 for at in 0..width {
318 let base = match header.get(at).and_then(Option::as_deref) {
319 Some(written) => written.to_string(),
320 None => format!("column{at}"),
321 };
322 let mut name = base.clone();
323 let mut next = 1;
324 while taken.iter().any(|held| held.eq_ignore_ascii_case(&name)) {
325 name = format!("{base}_{next}");
326 next += 1;
327 }
328 taken.push(name);
329 }
330 taken
331}
332
333fn types(rows: &[Vec<Option<String>>], width: usize) -> Vec<LogicalType> {
335 (0..width)
336 .map(|at| {
337 let values: Vec<Option<&str>> =
338 rows.iter().map(|row| row.get(at).and_then(Option::as_deref)).collect();
339 infer::column(&values)
340 })
341 .collect()
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347 use rudb_io::{Filesystem, OpenMode, SimFilesystem};
348 use std::path::Path;
349
350 fn read(text: &str) -> Reader {
351 let filesystem = SimFilesystem::new();
352 let path = Path::new("/t.csv");
353 let file = filesystem.open(path, OpenMode::Create).expect("creates");
354 file.write_at(0, text.as_bytes()).expect("writes");
355 drop(file);
356 let file = filesystem.open(path, OpenMode::Read).expect("opens");
357 Reader::open(file, "/t.csv").expect("sniffs")
358 }
359
360 fn names_and_types(reader: &Reader) -> Vec<(String, String)> {
361 reader.fields().iter().map(|f| (f.name.clone(), f.ty.to_string())).collect()
362 }
363
364 fn all(reader: &mut Reader) -> Vec<Vec<Value>> {
365 let mut rows = Vec::new();
366 while let Some(chunk) = reader.next_chunk().expect("reads") {
367 for row in 0..chunk.len() {
368 rows.push((0..chunk.width()).map(|at| chunk.value_at(row, at)).collect());
369 }
370 }
371 rows
372 }
373
374 #[test]
375 fn a_header_that_names_two_columns_the_same_thing_counts_the_second_one_up() {
376 let names: Vec<String> =
377 read("a,a,A,a_1\n1,2,3,4\nx,y,z,w\n").fields().into_iter().map(|f| f.name).collect();
378 assert_eq!(names, ["a", "a_1", "A_2", "a_1_1"]);
382 }
383
384 #[test]
385 fn a_header_and_three_types_are_what_duckdb_sniffs_for_the_same_bytes() {
386 let reader = read("a,b,c\n1,x,2.5\n2,y,3.5\n");
387 assert_eq!(
388 names_and_types(&reader),
389 [
390 ("a".to_string(), "BIGINT".to_string()),
391 ("b".to_string(), "VARCHAR".to_string()),
392 ("c".to_string(), "DOUBLE".to_string()),
393 ]
394 );
395 }
396
397 #[test]
398 fn a_file_with_no_header_gets_the_names_duckdb_gives_it() {
399 let reader = read("1,x\n2,y\n");
400 assert_eq!(
401 names_and_types(&reader),
402 [
403 ("column0".to_string(), "BIGINT".to_string()),
404 ("column1".to_string(), "VARCHAR".to_string()),
405 ]
406 );
407 }
408
409 #[test]
410 fn two_rows_of_words_are_a_header_and_a_row() {
411 let reader = read("a,b\nc,d\n");
412 assert_eq!(reader.fields().iter().map(|f| f.name.clone()).collect::<Vec<_>>(), ["a", "b"]);
413 }
414
415 #[test]
416 fn one_column_of_words_under_a_row_of_numbers_is_still_a_header() {
417 let reader = read("a,2\n3,4\n");
420 assert_eq!(reader.fields().iter().map(|f| f.name.clone()).collect::<Vec<_>>(), ["a", "2"]);
421 }
422
423 #[test]
424 fn the_rows_are_the_rows_of_the_file() {
425 let mut reader = read("a,b\n1,x\n2,y\n");
426 assert_eq!(
427 all(&mut reader),
428 [
429 vec![Value::BigInt(1), Value::Varchar("x".into())],
430 vec![Value::BigInt(2), Value::Varchar("y".into())],
431 ]
432 );
433 }
434
435 #[test]
436 fn an_empty_field_is_a_null_whether_it_was_quoted_or_not() {
437 let mut reader = read("a,b\n1,\n\"\",y\n");
440 assert_eq!(
441 all(&mut reader),
442 [vec![Value::BigInt(1), Value::Null], vec![Value::Null, Value::Varchar("y".into())],]
443 );
444 }
445
446 #[test]
447 fn a_projection_picks_columns_out_by_position_and_can_reorder_them() {
448 let mut reader = read("a,b,c\n1,x,2.5\n");
449 reader.project(&[2, 0]).expect("projects");
450 assert_eq!(reader.fields().iter().map(|f| f.name.clone()).collect::<Vec<_>>(), ["c", "a"]);
451 assert_eq!(all(&mut reader), [vec![Value::Double(2.5), Value::BigInt(1)]]);
452 }
453
454 #[test]
455 fn a_projection_of_nothing_still_counts_the_rows() {
456 let mut reader = read("a,b\n1,x\n2,y\n3,z\n");
457 reader.project(&[]).expect("projects");
458 let chunk = reader.next_chunk().expect("reads").expect("a chunk");
459 assert_eq!(chunk.len(), 3);
460 assert_eq!(chunk.width(), 0);
461 }
462
463 #[test]
464 fn a_pipe_separated_file_reads_as_one() {
465 let mut reader = read("a|b\n1|x\n");
466 assert_eq!(reader.dialect().delimiter, b'|');
467 assert_eq!(all(&mut reader), [vec![Value::BigInt(1), Value::Varchar("x".into())]]);
468 }
469
470 #[test]
471 fn a_quoted_field_with_a_delimiter_in_it_is_one_value() {
472 let mut reader = read("a,b\n1,\"x,y\"\n");
473 assert_eq!(all(&mut reader), [vec![Value::BigInt(1), Value::Varchar("x,y".into())]]);
474 }
475
476 #[test]
477 fn more_rows_than_fit_one_chunk_arrive_as_more_than_one_chunk() {
478 let mut text = String::from("a\n");
479 for row in 0..VECTOR_SIZE + 5 {
480 text.push_str(&format!("{row}\n"));
481 }
482 let mut reader = read(&text);
483 let first = reader.next_chunk().expect("reads").expect("a chunk");
484 assert_eq!(first.len(), VECTOR_SIZE);
485 let second = reader.next_chunk().expect("reads").expect("a second chunk");
486 assert_eq!(second.len(), 5);
487 assert!(reader.next_chunk().expect("reads").is_none());
488 }
489
490 #[test]
491 fn a_value_the_sniffer_never_saw_is_an_error_rather_than_a_wider_column() {
492 let mut text = String::from("c\n");
496 for row in 0..infer::SAMPLE {
497 text.push_str(&format!("{row}\n"));
498 }
499 text.push_str("oops\n");
500 let mut reader = read(&text);
501 assert_eq!(reader.fields()[0].ty, LogicalType::BigInt);
502 let error = all_or_error(&mut reader).unwrap_err();
503 let line = infer::SAMPLE + 2;
504 assert!(error.message().starts_with(&format!("CSV Error on Line: {line}")), "{error}");
505 assert!(
506 error.message().contains("Could not convert string \"oops\" to 'BIGINT'"),
507 "{error}"
508 );
509 assert!(error.message().contains("sample_size = 20480"), "{error}");
510 }
511
512 fn all_or_error(reader: &mut Reader) -> Result<Vec<Vec<Value>>> {
513 let mut rows = Vec::new();
514 while let Some(chunk) = reader.next_chunk()? {
515 for row in 0..chunk.len() {
516 rows.push((0..chunk.width()).map(|at| chunk.value_at(row, at)).collect());
517 }
518 }
519 Ok(rows)
520 }
521}