Rewrite SQL system to allow for transpiling SQL, and implement PoC
This commit is contained in:
parent
c70520b15d
commit
b3d76df033
7 changed files with 139 additions and 13 deletions
|
@ -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")]
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Token>) -> Result<[i8; 2], SyntaxError> {
|
||||
let token1 = tokens.pop_front().unwrap();
|
||||
match token1 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<String, String>) -> 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<String, String>) -> Result<(), RuntimeMsg> {
|
5
interpreter/src/processing/sql/mod.rs
Normal file
5
interpreter/src/processing/sql/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod executor;
|
||||
mod simple_emit;
|
||||
|
||||
pub use executor::*;
|
||||
pub use simple_emit::SimpleSqlQuery;
|
114
interpreter/src/processing/sql/simple_emit.rs
Normal file
114
interpreter/src/processing/sql/simple_emit.rs
Normal file
|
@ -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<Context>,
|
||||
file_iter: Option<FileIter>,
|
||||
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<Self::Item> {
|
||||
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<usize>) {
|
||||
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<dyn Op> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue