1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
mod auto_increment;
mod base;
mod mutable;
mod utils;

use {
	crate::{data::Schema, Column, DBFull, Database, Result, ValueType, WIPError},
	csv::ReaderBuilder,
	serde::{Deserialize, Serialize},
	std::{
		default::Default,
		fmt::Debug,
		fs::{File, OpenOptions},
	},
	thiserror::Error,
};

#[derive(Error, Serialize, Debug, PartialEq)]
pub enum CSVDatabaseError {
	#[error("CSV storages only support one table at a time")]
	OnlyOneTableAllowed,
}

pub struct CSVDatabase {
	schema: Option<Schema>,
	path: String,
	pub csv_settings: CSVSettings,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct CSVSettings {
	pub delimiter: u8,
	pub quoting: bool,
}
impl Default for CSVSettings {
	fn default() -> Self {
		Self {
			delimiter: b',',
			quoting: true,
		}
	}
}

impl DBFull for CSVDatabase {}

impl Database {
	pub fn new_csv(storage: CSVDatabase) -> Self {
		Self::new(Box::new(storage))
	}
}
impl CSVDatabase {
	pub fn new(path: &str) -> Result<Self> {
		Self::new_with_settings(path, CSVSettings::default())
	}
	pub fn new_with_settings(path: &str, csv_settings: CSVSettings) -> Result<Self> {
		let file = OpenOptions::new()
			.read(true)
			.write(true)
			.create(true)
			.open(path)
			.map_err(|error| WIPError::Debug(format!("{:?}", error)))?;

		let schema = discern_schema(file, &csv_settings)?;
		Ok(Self {
			schema,
			path: path.to_string(),
			csv_settings,
		})
	}
}

fn discern_schema(file: File, csv_settings: &CSVSettings) -> Result<Option<Schema>> {
	let mut reader = ReaderBuilder::new()
		.delimiter(csv_settings.delimiter)
		.from_reader(file);
	let headers = reader
		.headers()
		.map_err(|error| WIPError::Debug(format!("{:?}", error)))?;
	let column_defs = headers
		.iter()
		.map(|header| {
			let mut column = Column::default();
			column.name = header.to_string();
			column.data_type = ValueType::Str;
			column
		})
		.collect();
	if headers.is_empty() {
		Ok(None)
	} else {
		Ok(Some(Schema {
			table_name: String::new(),
			column_defs,
			indexes: vec![],
		}))
	}
}