multisql/glue/
mod.rs

1use {
2	crate::{
3		parse, parse_single, CSVSettings, Connection, Database, ExecuteError, Payload, Query,
4		Result, Value, WIPError,
5	},
6	futures::executor::block_on,
7	sqlparser::ast::{
8		Expr, Ident, ObjectName, Query as AstQuery, SetExpr, Statement, Value as AstValue, Values,
9	},
10	std::collections::HashMap,
11};
12
13mod database;
14mod error;
15mod insert;
16mod payload;
17mod select;
18mod tempdb;
19
20pub use {error::InterfaceError, insert::*, tempdb::TempDB};
21
22/// # Glue
23/// Glue is *the* interface for interacting with MultiSQL; a Glue instance comprises any number of stores, each with their own identifier.
24/// Once built, one will typically interact with Glue via queries.
25///
26/// There is a number of ways to deposit queries however, depending on the desired output:
27/// - [`Glue::execute()`] -- Might be considered the most generic.
28///     Replies with a [Result]<[Payload]>
29///     (payload being the response from any type of query).
30/// - [`Glue::execute_many()`] -- Same as `execute()` but will find any number of seperate queries in given text and provide a [Vec] in response.
31/// - [`Glue::select_as_string()`] -- Provides data, only for `SELECT` queries, as [String]s (rather than [Value]s).
32/// - [`Glue::select_as_json()`] -- Provides data, only for `SELECT` queries, as one big [String]; generally useful for webby interactions.
33pub struct Glue {
34	pub primary: String,
35	databases: HashMap<String, Database>,
36	pub tempdb: TempDB,
37}
38
39/// ## Creation of new interfaces
40impl Glue {
41	/// Creates a [Glue] instance with just one [Database].
42	pub fn new(name: String, database: Database) -> Self {
43		let mut databases = HashMap::new();
44		databases.insert(name, database);
45		Self::new_multi(databases)
46	}
47	/// Creates a [Glue] instance with access to all provided storages.
48	/// Argument is: [Vec]<(Identifier, [Database])>
49	pub fn new_multi(databases: HashMap<String, Database>) -> Self {
50		let primary = databases.keys().next().cloned().unwrap_or_default();
51		Self {
52			databases,
53			tempdb: TempDB::default(),
54			primary,
55		}
56	}
57	/// Merges existing [Glue] instances
58	pub fn new_multi_glue(glues: Vec<Glue>) -> Self {
59		glues
60			.into_iter()
61			.reduce(|mut main, other| {
62				main.databases.extend(other.databases);
63				main
64			})
65			.unwrap()
66	}
67	/// Merge existing [Glue] with [Vec] of other [Glue]s
68	/// For example:
69	/// ```
70	/// use multisql::{SledDatabase, Database, Glue};
71	/// let storage = SledDatabase::new("data/example_location/example")
72	///   .map(Database::new_sled)
73	///   .expect("Database Creation Failed");
74	/// let mut glue = Glue::new(String::from("main"), storage);
75	///
76	/// glue.execute_many("
77	///   DROP TABLE IF EXISTS test;
78	///   CREATE TABLE test (id INTEGER);
79	///   INSERT INTO test VALUES (1),(2);
80	///   SELECT * FROM test WHERE id > 1;
81	/// ");
82	///
83	/// let other_storage = SledDatabase::new("data/example_location/example_other")
84	///   .map(Database::new_sled)
85	///   .expect("Database Creation Failed");
86	/// let mut other_glue = Glue::new(String::from("other"), other_storage);
87	///
88	/// glue.extend_many_glues(vec![other_glue]);
89	/// ```
90	///
91	pub fn extend_many_glues(&mut self, glues: Vec<Glue>) {
92		self.databases.extend(
93			glues
94				.into_iter()
95				.reduce(|mut main, other| {
96					main.databases.extend(other.databases);
97					main
98				})
99				.unwrap()
100				.databases,
101		)
102	}
103	pub fn extend_glue(&mut self, glue: Glue) {
104		self.databases.extend(glue.databases)
105	}
106
107	/// Extend using a ~~[Path]~~ [String] which represents a path
108	/// Guesses the type of database based on the extension
109	/// Returns [bool] of whether action was taken
110	pub fn try_extend_from_path(
111		&mut self,
112		database_name: String,
113		database_path: String,
114	) -> Result<bool> {
115		if self.databases.contains_key(&database_name) {
116			return Ok(false);
117		}
118		let connection = if database_path.ends_with('/') {
119			Connection::Sled(database_path)
120		} else if database_path.ends_with(".csv") {
121			Connection::CSV(database_path, CSVSettings::default())
122		} else if database_path.ends_with(".xlsx") {
123			Connection::Sheet(database_path)
124		} else {
125			return Err(ExecuteError::InvalidDatabaseLocation.into());
126		};
127		let database = connection.try_into()?;
128		Ok(self.extend(database_name, database))
129	}
130
131	/// Extend [Glue] by single database
132	/// Returns [bool] of whether action was taken
133	pub fn extend(&mut self, database_name: String, database: Database) -> bool {
134		let database_present = self.databases.contains_key(&database_name);
135		if !database_present {
136			self.databases.insert(database_name, database);
137		}
138		!database_present
139	}
140
141	/// Opposite of [Glue::extend], removes database
142	/// Returns [bool] of whether action was taken
143	pub fn reduce(&mut self, database_name: &String) -> bool {
144		let database_present = self.databases.contains_key(database_name);
145		if database_present {
146			self.databases.remove(database_name);
147		}
148		database_present
149	}
150}
151
152impl Glue {
153	pub fn into_connections(self) -> Vec<(String, Connection)> {
154		self.databases
155			.into_iter()
156			.map(|(name, storage)| (name, storage.into_source()))
157			.collect()
158	}
159}
160
161/// ## Execute (Generic)
162impl Glue {
163	/// Will execute a single query.
164	pub fn execute(&mut self, query: &str) -> Result<Payload> {
165		let parsed_query =
166			parse_single(query).map_err(|error| WIPError::Debug(format!("{:?}", error)))?;
167		self.execute_parsed(parsed_query)
168	}
169	/// Will execute a set of queries.
170	pub fn execute_many(&mut self, query: &str) -> Result<Vec<Payload>> {
171		let parsed_queries =
172			parse(query).map_err(|error| WIPError::Debug(format!("{:?}", error)))?;
173		parsed_queries
174			.into_iter()
175			.map(|parsed_query| self.execute_parsed(parsed_query))
176			.collect::<Result<Vec<Payload>>>()
177	}
178	/// Will execute a pre-parsed query (see [Glue::pre_parse()] for more).
179	pub fn execute_parsed(&mut self, query: Query) -> Result<Payload> {
180		block_on(self.execute_query(&query))
181	}
182	/// Provides a parsed query to execute later.
183	/// Particularly useful if executing a small query many times as parsing is not (computationally) free.
184	pub fn pre_parse(query: &str) -> Result<Vec<Query>> {
185		parse(query).map_err(|error| WIPError::Debug(format!("{:?}", error)).into())
186	}
187}
188
189/// ## Insert (`INSERT`)
190impl Glue {
191	pub fn insert_vec(
192		&mut self,
193		table_name: String,
194		columns: Vec<String>,
195		rows: Vec<Vec<Value>>,
196	) -> Result<Payload> {
197		// TODO: Make this more efficient and nicer by checking the way we execute
198		let table_name = ObjectName(vec![Ident {
199			value: table_name,
200			quote_style: None,
201		}]);
202		let columns = columns
203			.into_iter()
204			.map(|name| Ident {
205				value: name,
206				quote_style: None,
207			})
208			.collect();
209		let sqlparser_rows: Vec<Vec<Expr>> = rows
210			.into_iter()
211			.map(|row| {
212				row.into_iter()
213					.map(|cell| {
214						Expr::Value(match cell {
215							Value::Null => AstValue::Null,
216							Value::Bool(value) => AstValue::Boolean(value),
217							Value::I64(value) => AstValue::Number(value.to_string(), false),
218							Value::F64(value) => AstValue::Number(value.to_string(), false),
219							Value::Str(value) => AstValue::SingleQuotedString(value),
220							_ => unimplemented!(),
221						})
222					})
223					.collect()
224			})
225			.collect();
226		let body = SetExpr::Values(Values(sqlparser_rows));
227		let query = Query(Statement::Insert {
228			table_name, // !
229			columns,    // !
230			source: Box::new(AstQuery {
231				body, // !
232				order_by: vec![],
233				with: None,
234				limit: None,
235				offset: None,
236				fetch: None,
237				lock: None,
238			}),
239			after_columns: vec![],
240			table: false,
241			overwrite: false,
242			or: None,
243			partitioned: None,
244			on: None,
245		});
246		self.execute_parsed(query)
247	}
248}