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")]
|
#[cfg(feature = "advanced")]
|
||||||
use super::processing::advanced::{DefaultAnalyzer, MusicAnalyzer};
|
use super::processing::advanced::{DefaultAnalyzer, MusicAnalyzer};
|
||||||
use super::processing::database::{DatabaseQuerier, SQLiteExecutor};
|
use super::processing::database::{DatabaseQuerier, SQLiteTranspileExecutor};
|
||||||
#[cfg(feature = "mpd")]
|
#[cfg(feature = "mpd")]
|
||||||
use super::processing::database::{MpdExecutor, MpdQuerier};
|
use super::processing::database::{MpdExecutor, MpdQuerier};
|
||||||
use super::processing::general::{
|
use super::processing::general::{
|
||||||
|
@ -22,7 +22,7 @@ pub struct Context {
|
||||||
impl Default for Context {
|
impl Default for Context {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
database: Box::new(SQLiteExecutor::default()),
|
database: Box::new(SQLiteTranspileExecutor::default()),
|
||||||
variables: Box::new(OpStorage::default()),
|
variables: Box::new(OpStorage::default()),
|
||||||
filesystem: Box::new(FilesystemExecutor::default()),
|
filesystem: Box::new(FilesystemExecutor::default()),
|
||||||
#[cfg(feature = "advanced")]
|
#[cfg(feature = "advanced")]
|
||||||
|
|
|
@ -22,11 +22,7 @@ pub struct FieldLikeFilter {
|
||||||
|
|
||||||
impl FieldLikeFilter {
|
impl FieldLikeFilter {
|
||||||
fn sanitise_string(s: &str) -> String {
|
fn sanitise_string(s: &str) -> String {
|
||||||
#[cfg(feature = "unidecode")]
|
super::utility::sanitise_string(s)
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,15 @@ use crate::lang::utility::assert_token_raw;
|
||||||
use crate::lang::SyntaxError;
|
use crate::lang::SyntaxError;
|
||||||
use crate::tokens::Token;
|
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> {
|
pub fn assert_comparison_operator(tokens: &mut VecDeque<Token>) -> Result<[i8; 2], SyntaxError> {
|
||||||
let token1 = tokens.pop_front().unwrap();
|
let token1 = tokens.pop_front().unwrap();
|
||||||
match token1 {
|
match token1 {
|
||||||
|
|
|
@ -11,7 +11,7 @@ mod variables;
|
||||||
pub mod database {
|
pub mod database {
|
||||||
#[cfg(feature = "mpd")]
|
#[cfg(feature = "mpd")]
|
||||||
pub use super::mpd::{MpdExecutor, MpdQuerier};
|
pub use super::mpd::{MpdExecutor, MpdQuerier};
|
||||||
pub use super::sql::{DatabaseQuerier, QueryResult, SQLiteExecutor};
|
pub use super::sql::{DatabaseQuerier, QueryResult, SQLiteExecutor, SQLiteTranspileExecutor};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod general {
|
pub mod general {
|
||||||
|
|
|
@ -30,6 +30,7 @@ pub trait DatabaseQuerier: Debug {
|
||||||
/// `"folder" = "path"` - path to root music directory
|
/// `"folder" = "path"` - path to root music directory
|
||||||
/// `"database" = "uri"` - connection URI for database (for SQLite this is just a filepath)
|
/// `"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
|
/// `"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>;
|
fn init_with_params(&mut self, params: &HashMap<String, String>) -> Result<(), RuntimeMsg>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,24 +306,25 @@ fn rows_to_item(
|
||||||
pub struct SQLiteTranspileExecutor;
|
pub struct SQLiteTranspileExecutor;
|
||||||
|
|
||||||
impl DatabaseQuerier for 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()))
|
Err(RuntimeMsg("Unimplemented".to_owned()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn artist_like(&mut self, query: &str) -> QueryResult {
|
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 {
|
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 {
|
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 {
|
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> {
|
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