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
97
98
99
100
101
102
103
mod auto_increment;
mod store;
mod store_mut;
mod utils;
/*mod alter_table;
mod error;
#[cfg(not(feature = "alter-table"))]
impl crate::AlterTable for CSVStorage {}
#[cfg(not(feature = "auto-increment"))]
impl crate::AutoIncrement for CSVStorage {}*/

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

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

pub struct CSVStorage {
	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 AlterTable for CSVStorage {}
impl FullStorage for CSVStorage {}

impl Storage {
	pub fn new_csv(storage: CSVStorage) -> Self {
		Self::new(Box::new(storage))
	}
}
impl CSVStorage {
	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![],
		}))
	}
}