From b3d76df033705fa254180fc194d255885f8036ea Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Wed, 3 Aug 2022 20:27:34 -0400 Subject: [PATCH] Rewrite SQL system to allow for transpiling SQL, and implement PoC --- interpreter/src/context.rs | 4 +- .../vocabulary/filters/field_like_filter.rs | 6 +- .../src/lang/vocabulary/filters/utility.rs | 9 ++ interpreter/src/processing/mod.rs | 2 +- .../processing/{sql.rs => sql/executor.rs} | 12 +- interpreter/src/processing/sql/mod.rs | 5 + interpreter/src/processing/sql/simple_emit.rs | 114 ++++++++++++++++++ 7 files changed, 139 insertions(+), 13 deletions(-) rename interpreter/src/processing/{sql.rs => sql/executor.rs} (96%) create mode 100644 interpreter/src/processing/sql/mod.rs create mode 100644 interpreter/src/processing/sql/simple_emit.rs diff --git a/interpreter/src/context.rs b/interpreter/src/context.rs index 3e54fe4..1588e1d 100644 --- a/interpreter/src/context.rs +++ b/interpreter/src/context.rs @@ -1,6 +1,6 @@ #[cfg(feature = "advanced")] use super::processing::advanced::{DefaultAnalyzer, MusicAnalyzer}; -use super::processing::database::{DatabaseQuerier, SQLiteExecutor}; +use super::processing::database::{DatabaseQuerier, SQLiteTranspileExecutor}; #[cfg(feature = "mpd")] use super::processing::database::{MpdExecutor, MpdQuerier}; use super::processing::general::{ @@ -22,7 +22,7 @@ pub struct Context { impl Default for Context { fn default() -> Self { Self { - database: Box::new(SQLiteExecutor::default()), + database: Box::new(SQLiteTranspileExecutor::default()), variables: Box::new(OpStorage::default()), filesystem: Box::new(FilesystemExecutor::default()), #[cfg(feature = "advanced")] diff --git a/interpreter/src/lang/vocabulary/filters/field_like_filter.rs b/interpreter/src/lang/vocabulary/filters/field_like_filter.rs index 3032cad..cbb65f3 100644 --- a/interpreter/src/lang/vocabulary/filters/field_like_filter.rs +++ b/interpreter/src/lang/vocabulary/filters/field_like_filter.rs @@ -22,11 +22,7 @@ pub struct FieldLikeFilter { impl FieldLikeFilter { fn sanitise_string(s: &str) -> String { - #[cfg(feature = "unidecode")] - let s = unidecode::unidecode(s); - s.replace(|c: char| c.is_whitespace() || c == '_' || c == '-', "") - .replace(|c: char| !(c.is_whitespace() || c.is_alphanumeric()), "") - .to_lowercase() + super::utility::sanitise_string(s) } } diff --git a/interpreter/src/lang/vocabulary/filters/utility.rs b/interpreter/src/lang/vocabulary/filters/utility.rs index de00c43..0a41d1a 100644 --- a/interpreter/src/lang/vocabulary/filters/utility.rs +++ b/interpreter/src/lang/vocabulary/filters/utility.rs @@ -4,6 +4,15 @@ use crate::lang::utility::assert_token_raw; use crate::lang::SyntaxError; use crate::tokens::Token; +#[inline] +pub fn sanitise_string(s: &str) -> String { + #[cfg(feature = "unidecode")] + let s = unidecode::unidecode(s); + s.replace(|c: char| c.is_whitespace() || c == '_' || c == '-', "") + .replace(|c: char| !(c.is_whitespace() || c.is_alphanumeric()), "") + .to_lowercase() +} + pub fn assert_comparison_operator(tokens: &mut VecDeque) -> Result<[i8; 2], SyntaxError> { let token1 = tokens.pop_front().unwrap(); match token1 { diff --git a/interpreter/src/processing/mod.rs b/interpreter/src/processing/mod.rs index 7e897f8..7e5d86e 100644 --- a/interpreter/src/processing/mod.rs +++ b/interpreter/src/processing/mod.rs @@ -11,7 +11,7 @@ mod variables; pub mod database { #[cfg(feature = "mpd")] pub use super::mpd::{MpdExecutor, MpdQuerier}; - pub use super::sql::{DatabaseQuerier, QueryResult, SQLiteExecutor}; + pub use super::sql::{DatabaseQuerier, QueryResult, SQLiteExecutor, SQLiteTranspileExecutor}; } pub mod general { diff --git a/interpreter/src/processing/sql.rs b/interpreter/src/processing/sql/executor.rs similarity index 96% rename from interpreter/src/processing/sql.rs rename to interpreter/src/processing/sql/executor.rs index 8fc3094..37105c9 100644 --- a/interpreter/src/processing/sql.rs +++ b/interpreter/src/processing/sql/executor.rs @@ -30,6 +30,7 @@ pub trait DatabaseQuerier: Debug { /// `"folder" = "path"` - path to root music directory /// `"database" = "uri"` - connection URI for database (for SQLite this is just a filepath) /// `"generate" = "true"|"yes"|"false"|"no"` - whether to populate the database using the music directory + /// it is up to the specific implementation to use/ignore these parameters fn init_with_params(&mut self, params: &HashMap) -> Result<(), RuntimeMsg>; } @@ -305,24 +306,25 @@ fn rows_to_item( pub struct SQLiteTranspileExecutor; impl DatabaseQuerier for SQLiteTranspileExecutor { - fn raw(&mut self, query: &str) -> QueryResult { + fn raw(&mut self, _query: &str) -> QueryResult { + // TODO Err(RuntimeMsg("Unimplemented".to_owned())) } fn artist_like(&mut self, query: &str) -> QueryResult { - Err(RuntimeMsg("Unimplemented".to_owned())) + Ok(Box::new(super::SimpleSqlQuery::emit("artist", query))) } fn album_like(&mut self, query: &str) -> QueryResult { - Err(RuntimeMsg("Unimplemented".to_owned())) + Ok(Box::new(super::SimpleSqlQuery::emit("album", query))) } fn song_like(&mut self, query: &str) -> QueryResult { - Err(RuntimeMsg("Unimplemented".to_owned())) + Ok(Box::new(super::SimpleSqlQuery::emit("title", query))) } fn genre_like(&mut self, query: &str) -> QueryResult { - Err(RuntimeMsg("Unimplemented".to_owned())) + Ok(Box::new(super::SimpleSqlQuery::emit("genre", query))) } fn init_with_params(&mut self, _params: &HashMap) -> Result<(), RuntimeMsg> { diff --git a/interpreter/src/processing/sql/mod.rs b/interpreter/src/processing/sql/mod.rs new file mode 100644 index 0000000..ea6653a --- /dev/null +++ b/interpreter/src/processing/sql/mod.rs @@ -0,0 +1,5 @@ +mod executor; +mod simple_emit; + +pub use executor::*; +pub use simple_emit::SimpleSqlQuery; diff --git a/interpreter/src/processing/sql/simple_emit.rs b/interpreter/src/processing/sql/simple_emit.rs new file mode 100644 index 0000000..21f3952 --- /dev/null +++ b/interpreter/src/processing/sql/simple_emit.rs @@ -0,0 +1,114 @@ +use std::fmt::{Debug, Display, Error, Formatter}; +use std::iter::Iterator; + +use crate::Context; + +use crate::lang::{IteratorItem, Op, PseudoOp}; +use crate::lang::{RuntimeError, RuntimeOp, TypePrimitive}; +use crate::processing::general::FileIter; + +#[derive(Debug)] +pub struct SimpleSqlQuery { + context: Option, + file_iter: Option, + field_name: String, + val: String, + has_tried: bool, +} + +impl SimpleSqlQuery { + pub fn emit(field: &str, value: &str) -> Self { + Self { + context: None, + file_iter: None, + field_name: field.to_owned(), + val: crate::lang::vocabulary::filters::utility::sanitise_string(value), + has_tried: false, + } + } +} + +impl Display for SimpleSqlQuery { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "{}(`{}`)", self.field_name, self.val) + } +} + +impl std::clone::Clone for SimpleSqlQuery { + fn clone(&self) -> Self { + Self { + context: None, + file_iter: None, + field_name: self.field_name.clone(), + val: self.val.clone(), + has_tried: self.has_tried, + } + } +} + +impl Iterator for SimpleSqlQuery { + type Item = IteratorItem; + + fn next(&mut self) -> Option { + if self.file_iter.is_none() { + if self.has_tried { + return None; + } else { + self.has_tried = true; + } + let iter = self.context.as_mut().unwrap().filesystem.raw( + None, + None, + true, + ); + self.file_iter = Some(match iter { + Ok(x) => x, + Err(e) => return Some(Err(e.with(RuntimeOp(PseudoOp::from_printable(self))))), + }); + } + while let Some(item) = self.file_iter.as_mut().unwrap().next() { + match item { + Ok(item) => { + // apply filter + if let Some(TypePrimitive::String(field_val)) = item.field(&self.field_name) { + if crate::lang::vocabulary::filters::utility::sanitise_string(field_val).contains(&self.val) { + return Some(Ok(item)); + } + } + }, + Err(e) => return Some(Err(RuntimeError { + line: 0, + op: PseudoOp::from_printable(self), + msg: e, + })) + } + } + None + } + + fn size_hint(&self) -> (usize, Option) { + self.file_iter.as_ref().map(|x| x.size_hint()).unwrap_or_default() + } +} + +impl Op for SimpleSqlQuery { + fn enter(&mut self, ctx: Context) { + self.context = Some(ctx) + } + + fn escape(&mut self) -> Context { + self.context.take().unwrap() + } + + fn is_resetable(&self) -> bool { + true + } + + fn reset(&mut self) -> Result<(), RuntimeError> { + Ok(()) + } + + fn dup(&self) -> Box { + Box::new(self.clone()) + } +}