Rewrite SQL system to allow for transpiling SQL, and implement PoC

This commit is contained in:
NGnius (Graham) 2022-08-03 20:27:34 -04:00
parent c70520b15d
commit b3d76df033
7 changed files with 139 additions and 13 deletions

View file

@ -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")]

View file

@ -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)
}
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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> {

View file

@ -0,0 +1,5 @@
mod executor;
mod simple_emit;
pub use executor::*;
pub use simple_emit::SimpleSqlQuery;

View 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())
}
}