diff --git a/mps-interpreter/src/lang/error.rs b/mps-interpreter/src/lang/error.rs index 0906dee..09d78a1 100644 --- a/mps-interpreter/src/lang/error.rs +++ b/mps-interpreter/src/lang/error.rs @@ -63,7 +63,7 @@ pub trait MpsLanguageError: Display + Debug { } // RuntimeError builder components - +#[derive(Debug, Clone)] pub struct RuntimeMsg(pub String); impl RuntimeMsg { @@ -76,6 +76,7 @@ impl RuntimeMsg { } } +#[derive(Debug, Clone)] pub struct RuntimeOp(pub PseudoOp); impl RuntimeOp { diff --git a/mps-interpreter/src/lang/vocabulary/sql_init.rs b/mps-interpreter/src/lang/vocabulary/sql_init.rs index 1412004..9e7723b 100644 --- a/mps-interpreter/src/lang/vocabulary/sql_init.rs +++ b/mps-interpreter/src/lang/vocabulary/sql_init.rs @@ -9,8 +9,8 @@ use crate::MpsContext; use crate::lang::repeated_tokens; use crate::lang::utility::{assert_token, assert_token_raw}; use crate::lang::MpsLanguageDictionary; -use crate::lang::SyntaxError; use crate::lang::{MpsFunctionFactory, MpsFunctionStatementFactory, MpsIteratorItem, MpsOp}; +use crate::lang::{PseudoOp, RuntimeOp, SyntaxError}; #[derive(Debug)] pub struct SqlInitStatement { @@ -41,18 +41,16 @@ impl Iterator for SqlInitStatement { type Item = MpsIteratorItem; fn next(&mut self) -> Option { - let pseudo_clone = self.clone(); // execute match self .context .as_mut() .unwrap() .database - .init_with_params(&self.params, &mut move || { - (Box::new(pseudo_clone.clone()) as Box).into() - }) { + .init_with_params(&self.params) + { Ok(_) => None, - Err(e) => Some(Err(e)), + Err(e) => Some(Err(e.with(RuntimeOp(PseudoOp::from_printable(self))))), } } diff --git a/mps-interpreter/src/lang/vocabulary/sql_query.rs b/mps-interpreter/src/lang/vocabulary/sql_query.rs index 33bb085..70d6ffb 100644 --- a/mps-interpreter/src/lang/vocabulary/sql_query.rs +++ b/mps-interpreter/src/lang/vocabulary/sql_query.rs @@ -5,8 +5,9 @@ use std::iter::Iterator; use crate::lang::utility::assert_token; use crate::lang::{ MpsFunctionFactory, MpsFunctionStatementFactory, MpsIteratorItem, MpsLanguageDictionary, MpsOp, + PseudoOp, }; -use crate::lang::{RuntimeError, SyntaxError}; +use crate::lang::{RuntimeError, RuntimeMsg, RuntimeOp, SyntaxError}; use crate::tokens::MpsToken; use crate::MpsContext; use crate::MpsItem; @@ -16,12 +17,13 @@ use crate::MpsItem; pub struct SqlStatement { query: String, context: Option, - rows: Option>>, + rows: Option>>, current: usize, } impl SqlStatement { fn get_item(&mut self, increment: bool) -> Option { + let fake = PseudoOp::from_printable(self); if let Some(rows) = &self.rows { if increment { if self.current == rows.len() { @@ -33,19 +35,15 @@ impl SqlStatement { None } else { //Some(rows[self.current].clone()) - match &rows[self.current] { - Ok(item) => Some(Ok(item.clone())), - Err(e) => Some(Err(RuntimeError { - line: e.line, - op: (Box::new(self.clone()) as Box).into(), - msg: e.msg.clone(), - })), + match rows[self.current].clone() { + Ok(item) => Some(Ok(item)), + Err(e) => Some(Err(e.with(RuntimeOp(fake)))), } } } else { Some(Err(RuntimeError { line: 0, - op: (Box::new(self.clone()) as Box).into(), + op: fake, msg: "Context error: rows is None".to_string(), })) } @@ -91,15 +89,13 @@ impl Iterator for SqlStatement { // query has executed, return another result self.get_item(true) } else { - let self_clone = self.clone(); + let fake = PseudoOp::from_printable(self); let ctx = self.context.as_mut().unwrap(); // query has not been executed yet - match ctx.database.raw(&self.query, &mut move || { - (Box::new(self_clone.clone()) as Box).into() - }) { + match ctx.database.raw(&self.query) { Err(e) => { self.rows = Some(Vec::with_capacity(0)); - Some(Err(e)) + Some(Err(e.with(RuntimeOp(fake)))) } Ok(rows) => { self.rows = Some(rows); diff --git a/mps-interpreter/src/lang/vocabulary/sql_simple_query.rs b/mps-interpreter/src/lang/vocabulary/sql_simple_query.rs index a0a4072..2c0cbb4 100644 --- a/mps-interpreter/src/lang/vocabulary/sql_simple_query.rs +++ b/mps-interpreter/src/lang/vocabulary/sql_simple_query.rs @@ -5,8 +5,9 @@ use std::iter::Iterator; use crate::lang::utility::assert_token; use crate::lang::{ MpsFunctionFactory, MpsFunctionStatementFactory, MpsIteratorItem, MpsLanguageDictionary, MpsOp, + PseudoOp, }; -use crate::lang::{RuntimeError, SyntaxError}; +use crate::lang::{RuntimeError, RuntimeMsg, RuntimeOp, SyntaxError}; use crate::tokens::MpsToken; use crate::MpsContext; use crate::MpsItem; @@ -65,12 +66,13 @@ pub struct SimpleSqlStatement { query: String, mode: QueryMode, context: Option, - rows: Option>>, + rows: Option>>, current: usize, } impl SimpleSqlStatement { fn get_item(&mut self, increment: bool) -> Option { + let fake = PseudoOp::from_printable(self); if let Some(rows) = &self.rows { if increment { if self.current == rows.len() { @@ -82,19 +84,15 @@ impl SimpleSqlStatement { None } else { //Some(rows[self.current].clone()) - match &rows[self.current] { - Ok(item) => Some(Ok(item.clone())), - Err(e) => Some(Err(RuntimeError { - line: e.line, - op: (Box::new(self.clone()) as Box).into(), - msg: e.msg.clone(), - })), + match rows[self.current].clone() { + Ok(item) => Some(Ok(item)), + Err(e) => Some(Err(e.with(RuntimeOp(fake)))), } } } else { Some(Err(RuntimeError { line: 0, - op: (Box::new(self.clone()) as Box).into(), + op: fake, msg: "Context error: rows is None".to_string(), })) } @@ -141,27 +139,19 @@ impl Iterator for SimpleSqlStatement { // query has executed, return another result self.get_item(true) } else { - let self_clone = self.clone(); + let fake = PseudoOp::from_printable(self); let ctx = self.context.as_mut().unwrap(); // query has not been executed yet let query_result = match self.mode { - QueryMode::Artist => ctx.database.artist_like(&self.query, &mut move || { - (Box::new(self_clone.clone()) as Box).into() - }), - QueryMode::Album => ctx.database.album_like(&self.query, &mut move || { - (Box::new(self_clone.clone()) as Box).into() - }), - QueryMode::Song => ctx.database.song_like(&self.query, &mut move || { - (Box::new(self_clone.clone()) as Box).into() - }), - QueryMode::Genre => ctx.database.genre_like(&self.query, &mut move || { - (Box::new(self_clone.clone()) as Box).into() - }), + QueryMode::Artist => ctx.database.artist_like(&self.query), + QueryMode::Album => ctx.database.album_like(&self.query), + QueryMode::Song => ctx.database.song_like(&self.query), + QueryMode::Genre => ctx.database.genre_like(&self.query), }; match query_result { Err(e) => { self.rows = Some(Vec::with_capacity(0)); - Some(Err(e)) + Some(Err(e.with(RuntimeOp(fake)))) } Ok(rows) => { self.rows = Some(rows); diff --git a/mps-interpreter/src/processing/sql.rs b/mps-interpreter/src/processing/sql.rs index 8997c9d..9e15e45 100644 --- a/mps-interpreter/src/processing/sql.rs +++ b/mps-interpreter/src/processing/sql.rs @@ -2,39 +2,33 @@ use core::fmt::Debug; use std::collections::{HashMap, HashSet}; use crate::lang::db::*; -use crate::lang::RuntimeError; +use crate::lang::RuntimeMsg; use crate::MpsItem; -use super::OpGetter as QueryOp; - -pub type QueryResult = Result>, RuntimeError>; +pub type QueryResult = Result>, RuntimeMsg>; /// SQL querying functionality, loosely de-coupled from any specific SQL dialect (excluding raw call) pub trait MpsDatabaseQuerier: Debug { /// raw SQL call, assumed (but not guaranteed) to retrieved music items - fn raw(&mut self, query: &str, op: &mut QueryOp) -> QueryResult; + fn raw(&mut self, query: &str) -> QueryResult; /// get music, searching by artist name like `query` - fn artist_like(&mut self, query: &str, op: &mut QueryOp) -> QueryResult; + fn artist_like(&mut self, query: &str) -> QueryResult; /// get music, searching by album title like `query` - fn album_like(&mut self, query: &str, op: &mut QueryOp) -> QueryResult; + fn album_like(&mut self, query: &str) -> QueryResult; /// get music, searching by song title like `query` - fn song_like(&mut self, query: &str, op: &mut QueryOp) -> QueryResult; + fn song_like(&mut self, query: &str) -> QueryResult; /// get music, searching by genre title like `query` - fn genre_like(&mut self, query: &str, op: &mut QueryOp) -> QueryResult; + fn genre_like(&mut self, query: &str) -> QueryResult; /// connect to the SQL database with (optional) settings such as: /// `"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 - fn init_with_params( - &mut self, - params: &HashMap, - op: &mut QueryOp, - ) -> Result<(), RuntimeError>; + fn init_with_params(&mut self, params: &HashMap) -> Result<(), RuntimeMsg>; } #[derive(Default, Debug)] @@ -44,124 +38,87 @@ pub struct MpsSQLiteExecutor { impl MpsSQLiteExecutor { #[inline] - fn gen_db_maybe(&mut self, op: &mut QueryOp) -> Result<(), RuntimeError> { + fn gen_db_maybe(&mut self) -> Result<(), RuntimeMsg> { if self.sqlite_connection.is_none() { // connection needs to be created match generate_default_db() { Ok(conn) => { self.sqlite_connection = Some(conn); } - Err(e) => { - return Err(RuntimeError { - line: 0, - op: op(), - msg: format!("SQL connection error: {}", e), - }) - } + Err(e) => return Err(RuntimeMsg(format!("SQL connection error: {}", e))), } } Ok(()) } - fn music_query_single_param( - &mut self, - query: &str, - param: &str, - op: &mut QueryOp, - ) -> QueryResult { - self.gen_db_maybe(op)?; + fn music_query_single_param(&mut self, query: &str, param: &str) -> QueryResult { + self.gen_db_maybe()?; let conn = self.sqlite_connection.as_mut().unwrap(); match perform_single_param_query(conn, query, param) { Ok(items) => Ok(items .into_iter() - .map(|item| { - item.map_err(|e| RuntimeError { - line: 0, - op: op(), - msg: format!("SQL item mapping error: {}", e), - }) - }) + .map(|item| item.map_err(|e| RuntimeMsg(format!("SQL item mapping error: {}", e)))) .collect()), - Err(e) => Err(RuntimeError { - line: 0, - op: op(), - msg: e, - }), + Err(e) => Err(RuntimeMsg(e)), } } } impl MpsDatabaseQuerier for MpsSQLiteExecutor { - fn raw(&mut self, query: &str, op: &mut QueryOp) -> QueryResult { - self.gen_db_maybe(op)?; + fn raw(&mut self, query: &str) -> QueryResult { + self.gen_db_maybe()?; let conn = self.sqlite_connection.as_mut().unwrap(); // execute query match perform_query(conn, query) { Ok(items) => Ok(items .into_iter() - .map(|item| { - item.map_err(|e| RuntimeError { - line: 0, - op: op(), - msg: format!("SQL item mapping error: {}", e), - }) - }) + .map(|item| item.map_err(|e| RuntimeMsg(format!("SQL item mapping error: {}", e)))) .collect()), - Err(e) => Err(RuntimeError { - line: 0, - op: op(), - msg: e, - }), + Err(e) => Err(RuntimeMsg(e)), } } - fn artist_like(&mut self, query: &str, op: &mut QueryOp) -> QueryResult { + fn artist_like(&mut self, query: &str) -> QueryResult { let param = &format!("%{}%", query); let query_stmt = "SELECT songs.* FROM songs JOIN artists ON songs.artist = artists.artist_id JOIN metadata ON songs.metadata = metadata.meta_id WHERE artists.name like ? ORDER BY songs.album, metadata.track"; - self.music_query_single_param(query_stmt, param, op) + self.music_query_single_param(query_stmt, param) } - fn album_like(&mut self, query: &str, op: &mut QueryOp) -> QueryResult { + fn album_like(&mut self, query: &str) -> QueryResult { let param = &format!("%{}%", query); let query_stmt = "SELECT songs.* FROM songs JOIN albums ON songs.album = artists.album_id JOIN metadata ON songs.metadata = metadata.meta_id WHERE albums.title like ? ORDER BY songs.album, metadata.track"; - self.music_query_single_param(query_stmt, param, op) + self.music_query_single_param(query_stmt, param) } - fn song_like(&mut self, query: &str, op: &mut QueryOp) -> QueryResult { + fn song_like(&mut self, query: &str) -> QueryResult { let param = &format!("%{}%", query); let query_stmt = "SELECT songs.* FROM songs JOIN metadata ON songs.metadata = metadata.meta_id WHERE songs.title like ? ORDER BY songs.album, metadata.track"; - self.music_query_single_param(query_stmt, param, op) + self.music_query_single_param(query_stmt, param) } - fn genre_like(&mut self, query: &str, op: &mut QueryOp) -> QueryResult { + fn genre_like(&mut self, query: &str) -> QueryResult { let param = &format!("%{}%", query); let query_stmt = "SELECT songs.* FROM songs JOIN genres ON songs.genre = genres.genre_id JOIN metadata ON songs.metadata = metadata.meta_id WHERE genres.title like ? ORDER BY songs.album, metadata.track"; - self.music_query_single_param(query_stmt, param, op) + self.music_query_single_param(query_stmt, param) } - fn init_with_params( - &mut self, - params: &HashMap, - op: &mut QueryOp, - ) -> Result<(), RuntimeError> { + fn init_with_params(&mut self, params: &HashMap) -> Result<(), RuntimeMsg> { // must be executed before connection is created if self.sqlite_connection.is_some() { - Err(RuntimeError { - line: 0, - op: op(), - msg: "Cannot init SQLite connection: Already connected".into(), - }) + Err(RuntimeMsg( + "Cannot init SQLite connection: Already connected".to_string(), + )) } else { // process params // init connection @@ -183,14 +140,10 @@ impl MpsDatabaseQuerier for MpsSQLiteExecutor { settings.auto_generate = match val as &str { "true" => Ok(true), "false" => Ok(false), - x => Err(RuntimeError { - line: 0, - op: op(), - msg: format!( - "Unrecognised right hand side of param \"{}\" = \"{}\"", - key, x - ), - }), + x => Err(RuntimeMsg(format!( + "Unrecognised right hand side of param \"{}\" = \"{}\"", + key, x + ))), }?; } _ => {} @@ -211,17 +164,16 @@ impl MpsDatabaseQuerier for MpsSQLiteExecutor { concat_keys += &format!("{}, ", key); } } - return Err(RuntimeError { - line: 0, - op: op(), - msg: format!("Unrecognised sql init parameter(s): {}", concat_keys), - }); + return Err(RuntimeMsg(format!( + "Unrecognised sql init parameter(s): {}", + concat_keys + ))); } - self.sqlite_connection = Some(settings.try_into().map_err(|e| RuntimeError { - line: 0, - op: op(), - msg: format!("SQL connection error: {}", e), - })?); + self.sqlite_connection = Some( + settings + .try_into() + .map_err(|e| RuntimeMsg(format!("SQL connection error: {}", e)))?, + ); Ok(()) } }