Remove OpGetter from sql context functionality

This commit is contained in:
NGnius (Graham) 2022-01-31 16:25:05 -05:00
parent 70950e0bc7
commit 9ea25c27de
5 changed files with 74 additions and 137 deletions

View file

@ -63,7 +63,7 @@ pub trait MpsLanguageError: Display + Debug {
} }
// RuntimeError builder components // RuntimeError builder components
#[derive(Debug, Clone)]
pub struct RuntimeMsg(pub String); pub struct RuntimeMsg(pub String);
impl RuntimeMsg { impl RuntimeMsg {
@ -76,6 +76,7 @@ impl RuntimeMsg {
} }
} }
#[derive(Debug, Clone)]
pub struct RuntimeOp(pub PseudoOp); pub struct RuntimeOp(pub PseudoOp);
impl RuntimeOp { impl RuntimeOp {

View file

@ -9,8 +9,8 @@ use crate::MpsContext;
use crate::lang::repeated_tokens; use crate::lang::repeated_tokens;
use crate::lang::utility::{assert_token, assert_token_raw}; use crate::lang::utility::{assert_token, assert_token_raw};
use crate::lang::MpsLanguageDictionary; use crate::lang::MpsLanguageDictionary;
use crate::lang::SyntaxError;
use crate::lang::{MpsFunctionFactory, MpsFunctionStatementFactory, MpsIteratorItem, MpsOp}; use crate::lang::{MpsFunctionFactory, MpsFunctionStatementFactory, MpsIteratorItem, MpsOp};
use crate::lang::{PseudoOp, RuntimeOp, SyntaxError};
#[derive(Debug)] #[derive(Debug)]
pub struct SqlInitStatement { pub struct SqlInitStatement {
@ -41,18 +41,16 @@ impl Iterator for SqlInitStatement {
type Item = MpsIteratorItem; type Item = MpsIteratorItem;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let pseudo_clone = self.clone();
// execute // execute
match self match self
.context .context
.as_mut() .as_mut()
.unwrap() .unwrap()
.database .database
.init_with_params(&self.params, &mut move || { .init_with_params(&self.params)
(Box::new(pseudo_clone.clone()) as Box<dyn MpsOp>).into() {
}) {
Ok(_) => None, Ok(_) => None,
Err(e) => Some(Err(e)), Err(e) => Some(Err(e.with(RuntimeOp(PseudoOp::from_printable(self))))),
} }
} }

View file

@ -5,8 +5,9 @@ use std::iter::Iterator;
use crate::lang::utility::assert_token; use crate::lang::utility::assert_token;
use crate::lang::{ use crate::lang::{
MpsFunctionFactory, MpsFunctionStatementFactory, MpsIteratorItem, MpsLanguageDictionary, MpsOp, MpsFunctionFactory, MpsFunctionStatementFactory, MpsIteratorItem, MpsLanguageDictionary, MpsOp,
PseudoOp,
}; };
use crate::lang::{RuntimeError, SyntaxError}; use crate::lang::{RuntimeError, RuntimeMsg, RuntimeOp, SyntaxError};
use crate::tokens::MpsToken; use crate::tokens::MpsToken;
use crate::MpsContext; use crate::MpsContext;
use crate::MpsItem; use crate::MpsItem;
@ -16,12 +17,13 @@ use crate::MpsItem;
pub struct SqlStatement { pub struct SqlStatement {
query: String, query: String,
context: Option<MpsContext>, context: Option<MpsContext>,
rows: Option<Vec<Result<MpsItem, RuntimeError>>>, rows: Option<Vec<Result<MpsItem, RuntimeMsg>>>,
current: usize, current: usize,
} }
impl SqlStatement { impl SqlStatement {
fn get_item(&mut self, increment: bool) -> Option<MpsIteratorItem> { fn get_item(&mut self, increment: bool) -> Option<MpsIteratorItem> {
let fake = PseudoOp::from_printable(self);
if let Some(rows) = &self.rows { if let Some(rows) = &self.rows {
if increment { if increment {
if self.current == rows.len() { if self.current == rows.len() {
@ -33,19 +35,15 @@ impl SqlStatement {
None None
} else { } else {
//Some(rows[self.current].clone()) //Some(rows[self.current].clone())
match &rows[self.current] { match rows[self.current].clone() {
Ok(item) => Some(Ok(item.clone())), Ok(item) => Some(Ok(item)),
Err(e) => Some(Err(RuntimeError { Err(e) => Some(Err(e.with(RuntimeOp(fake)))),
line: e.line,
op: (Box::new(self.clone()) as Box<dyn MpsOp>).into(),
msg: e.msg.clone(),
})),
} }
} }
} else { } else {
Some(Err(RuntimeError { Some(Err(RuntimeError {
line: 0, line: 0,
op: (Box::new(self.clone()) as Box<dyn MpsOp>).into(), op: fake,
msg: "Context error: rows is None".to_string(), msg: "Context error: rows is None".to_string(),
})) }))
} }
@ -91,15 +89,13 @@ impl Iterator for SqlStatement {
// query has executed, return another result // query has executed, return another result
self.get_item(true) self.get_item(true)
} else { } else {
let self_clone = self.clone(); let fake = PseudoOp::from_printable(self);
let ctx = self.context.as_mut().unwrap(); let ctx = self.context.as_mut().unwrap();
// query has not been executed yet // query has not been executed yet
match ctx.database.raw(&self.query, &mut move || { match ctx.database.raw(&self.query) {
(Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()
}) {
Err(e) => { Err(e) => {
self.rows = Some(Vec::with_capacity(0)); self.rows = Some(Vec::with_capacity(0));
Some(Err(e)) Some(Err(e.with(RuntimeOp(fake))))
} }
Ok(rows) => { Ok(rows) => {
self.rows = Some(rows); self.rows = Some(rows);

View file

@ -5,8 +5,9 @@ use std::iter::Iterator;
use crate::lang::utility::assert_token; use crate::lang::utility::assert_token;
use crate::lang::{ use crate::lang::{
MpsFunctionFactory, MpsFunctionStatementFactory, MpsIteratorItem, MpsLanguageDictionary, MpsOp, MpsFunctionFactory, MpsFunctionStatementFactory, MpsIteratorItem, MpsLanguageDictionary, MpsOp,
PseudoOp,
}; };
use crate::lang::{RuntimeError, SyntaxError}; use crate::lang::{RuntimeError, RuntimeMsg, RuntimeOp, SyntaxError};
use crate::tokens::MpsToken; use crate::tokens::MpsToken;
use crate::MpsContext; use crate::MpsContext;
use crate::MpsItem; use crate::MpsItem;
@ -65,12 +66,13 @@ pub struct SimpleSqlStatement {
query: String, query: String,
mode: QueryMode, mode: QueryMode,
context: Option<MpsContext>, context: Option<MpsContext>,
rows: Option<Vec<Result<MpsItem, RuntimeError>>>, rows: Option<Vec<Result<MpsItem, RuntimeMsg>>>,
current: usize, current: usize,
} }
impl SimpleSqlStatement { impl SimpleSqlStatement {
fn get_item(&mut self, increment: bool) -> Option<MpsIteratorItem> { fn get_item(&mut self, increment: bool) -> Option<MpsIteratorItem> {
let fake = PseudoOp::from_printable(self);
if let Some(rows) = &self.rows { if let Some(rows) = &self.rows {
if increment { if increment {
if self.current == rows.len() { if self.current == rows.len() {
@ -82,19 +84,15 @@ impl SimpleSqlStatement {
None None
} else { } else {
//Some(rows[self.current].clone()) //Some(rows[self.current].clone())
match &rows[self.current] { match rows[self.current].clone() {
Ok(item) => Some(Ok(item.clone())), Ok(item) => Some(Ok(item)),
Err(e) => Some(Err(RuntimeError { Err(e) => Some(Err(e.with(RuntimeOp(fake)))),
line: e.line,
op: (Box::new(self.clone()) as Box<dyn MpsOp>).into(),
msg: e.msg.clone(),
})),
} }
} }
} else { } else {
Some(Err(RuntimeError { Some(Err(RuntimeError {
line: 0, line: 0,
op: (Box::new(self.clone()) as Box<dyn MpsOp>).into(), op: fake,
msg: "Context error: rows is None".to_string(), msg: "Context error: rows is None".to_string(),
})) }))
} }
@ -141,27 +139,19 @@ impl Iterator for SimpleSqlStatement {
// query has executed, return another result // query has executed, return another result
self.get_item(true) self.get_item(true)
} else { } else {
let self_clone = self.clone(); let fake = PseudoOp::from_printable(self);
let ctx = self.context.as_mut().unwrap(); let ctx = self.context.as_mut().unwrap();
// query has not been executed yet // query has not been executed yet
let query_result = match self.mode { let query_result = match self.mode {
QueryMode::Artist => ctx.database.artist_like(&self.query, &mut move || { QueryMode::Artist => ctx.database.artist_like(&self.query),
(Box::new(self_clone.clone()) as Box<dyn MpsOp>).into() QueryMode::Album => ctx.database.album_like(&self.query),
}), QueryMode::Song => ctx.database.song_like(&self.query),
QueryMode::Album => ctx.database.album_like(&self.query, &mut move || { QueryMode::Genre => ctx.database.genre_like(&self.query),
(Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()
}),
QueryMode::Song => ctx.database.song_like(&self.query, &mut move || {
(Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()
}),
QueryMode::Genre => ctx.database.genre_like(&self.query, &mut move || {
(Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()
}),
}; };
match query_result { match query_result {
Err(e) => { Err(e) => {
self.rows = Some(Vec::with_capacity(0)); self.rows = Some(Vec::with_capacity(0));
Some(Err(e)) Some(Err(e.with(RuntimeOp(fake))))
} }
Ok(rows) => { Ok(rows) => {
self.rows = Some(rows); self.rows = Some(rows);

View file

@ -2,39 +2,33 @@ use core::fmt::Debug;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use crate::lang::db::*; use crate::lang::db::*;
use crate::lang::RuntimeError; use crate::lang::RuntimeMsg;
use crate::MpsItem; use crate::MpsItem;
use super::OpGetter as QueryOp; pub type QueryResult = Result<Vec<Result<MpsItem, RuntimeMsg>>, RuntimeMsg>;
pub type QueryResult = Result<Vec<Result<MpsItem, RuntimeError>>, RuntimeError>;
/// SQL querying functionality, loosely de-coupled from any specific SQL dialect (excluding raw call) /// SQL querying functionality, loosely de-coupled from any specific SQL dialect (excluding raw call)
pub trait MpsDatabaseQuerier: Debug { pub trait MpsDatabaseQuerier: Debug {
/// raw SQL call, assumed (but not guaranteed) to retrieved music items /// 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` /// 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` /// 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` /// 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` /// 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: /// connect to the SQL database with (optional) settings such as:
/// `"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
fn init_with_params( fn init_with_params(&mut self, params: &HashMap<String, String>) -> Result<(), RuntimeMsg>;
&mut self,
params: &HashMap<String, String>,
op: &mut QueryOp,
) -> Result<(), RuntimeError>;
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
@ -44,124 +38,87 @@ pub struct MpsSQLiteExecutor {
impl MpsSQLiteExecutor { impl MpsSQLiteExecutor {
#[inline] #[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() { if self.sqlite_connection.is_none() {
// connection needs to be created // connection needs to be created
match generate_default_db() { match generate_default_db() {
Ok(conn) => { Ok(conn) => {
self.sqlite_connection = Some(conn); self.sqlite_connection = Some(conn);
} }
Err(e) => { Err(e) => return Err(RuntimeMsg(format!("SQL connection error: {}", e))),
return Err(RuntimeError {
line: 0,
op: op(),
msg: format!("SQL connection error: {}", e),
})
}
} }
} }
Ok(()) Ok(())
} }
fn music_query_single_param( fn music_query_single_param(&mut self, query: &str, param: &str) -> QueryResult {
&mut self, self.gen_db_maybe()?;
query: &str,
param: &str,
op: &mut QueryOp,
) -> QueryResult {
self.gen_db_maybe(op)?;
let conn = self.sqlite_connection.as_mut().unwrap(); let conn = self.sqlite_connection.as_mut().unwrap();
match perform_single_param_query(conn, query, param) { match perform_single_param_query(conn, query, param) {
Ok(items) => Ok(items Ok(items) => Ok(items
.into_iter() .into_iter()
.map(|item| { .map(|item| item.map_err(|e| RuntimeMsg(format!("SQL item mapping error: {}", e))))
item.map_err(|e| RuntimeError {
line: 0,
op: op(),
msg: format!("SQL item mapping error: {}", e),
})
})
.collect()), .collect()),
Err(e) => Err(RuntimeError { Err(e) => Err(RuntimeMsg(e)),
line: 0,
op: op(),
msg: e,
}),
} }
} }
} }
impl MpsDatabaseQuerier for MpsSQLiteExecutor { impl MpsDatabaseQuerier for MpsSQLiteExecutor {
fn raw(&mut self, query: &str, op: &mut QueryOp) -> QueryResult { fn raw(&mut self, query: &str) -> QueryResult {
self.gen_db_maybe(op)?; self.gen_db_maybe()?;
let conn = self.sqlite_connection.as_mut().unwrap(); let conn = self.sqlite_connection.as_mut().unwrap();
// execute query // execute query
match perform_query(conn, query) { match perform_query(conn, query) {
Ok(items) => Ok(items Ok(items) => Ok(items
.into_iter() .into_iter()
.map(|item| { .map(|item| item.map_err(|e| RuntimeMsg(format!("SQL item mapping error: {}", e))))
item.map_err(|e| RuntimeError {
line: 0,
op: op(),
msg: format!("SQL item mapping error: {}", e),
})
})
.collect()), .collect()),
Err(e) => Err(RuntimeError { Err(e) => Err(RuntimeMsg(e)),
line: 0,
op: op(),
msg: 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 param = &format!("%{}%", query);
let query_stmt = "SELECT songs.* FROM songs let query_stmt = "SELECT songs.* FROM songs
JOIN artists ON songs.artist = artists.artist_id JOIN artists ON songs.artist = artists.artist_id
JOIN metadata ON songs.metadata = metadata.meta_id JOIN metadata ON songs.metadata = metadata.meta_id
WHERE artists.name like ? ORDER BY songs.album, metadata.track"; 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 param = &format!("%{}%", query);
let query_stmt = "SELECT songs.* FROM songs let query_stmt = "SELECT songs.* FROM songs
JOIN albums ON songs.album = artists.album_id JOIN albums ON songs.album = artists.album_id
JOIN metadata ON songs.metadata = metadata.meta_id JOIN metadata ON songs.metadata = metadata.meta_id
WHERE albums.title like ? ORDER BY songs.album, metadata.track"; 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 param = &format!("%{}%", query);
let query_stmt = "SELECT songs.* FROM songs let query_stmt = "SELECT songs.* FROM songs
JOIN metadata ON songs.metadata = metadata.meta_id JOIN metadata ON songs.metadata = metadata.meta_id
WHERE songs.title like ? ORDER BY songs.album, metadata.track"; 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 param = &format!("%{}%", query);
let query_stmt = "SELECT songs.* FROM songs let query_stmt = "SELECT songs.* FROM songs
JOIN genres ON songs.genre = genres.genre_id JOIN genres ON songs.genre = genres.genre_id
JOIN metadata ON songs.metadata = metadata.meta_id JOIN metadata ON songs.metadata = metadata.meta_id
WHERE genres.title like ? ORDER BY songs.album, metadata.track"; 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( fn init_with_params(&mut self, params: &HashMap<String, String>) -> Result<(), RuntimeMsg> {
&mut self,
params: &HashMap<String, String>,
op: &mut QueryOp,
) -> Result<(), RuntimeError> {
// must be executed before connection is created // must be executed before connection is created
if self.sqlite_connection.is_some() { if self.sqlite_connection.is_some() {
Err(RuntimeError { Err(RuntimeMsg(
line: 0, "Cannot init SQLite connection: Already connected".to_string(),
op: op(), ))
msg: "Cannot init SQLite connection: Already connected".into(),
})
} else { } else {
// process params // process params
// init connection // init connection
@ -183,14 +140,10 @@ impl MpsDatabaseQuerier for MpsSQLiteExecutor {
settings.auto_generate = match val as &str { settings.auto_generate = match val as &str {
"true" => Ok(true), "true" => Ok(true),
"false" => Ok(false), "false" => Ok(false),
x => Err(RuntimeError { x => Err(RuntimeMsg(format!(
line: 0, "Unrecognised right hand side of param \"{}\" = \"{}\"",
op: op(), key, x
msg: format!( ))),
"Unrecognised right hand side of param \"{}\" = \"{}\"",
key, x
),
}),
}?; }?;
} }
_ => {} _ => {}
@ -211,17 +164,16 @@ impl MpsDatabaseQuerier for MpsSQLiteExecutor {
concat_keys += &format!("{}, ", key); concat_keys += &format!("{}, ", key);
} }
} }
return Err(RuntimeError { return Err(RuntimeMsg(format!(
line: 0, "Unrecognised sql init parameter(s): {}",
op: op(), concat_keys
msg: format!("Unrecognised sql init parameter(s): {}", concat_keys), )));
});
} }
self.sqlite_connection = Some(settings.try_into().map_err(|e| RuntimeError { self.sqlite_connection = Some(
line: 0, settings
op: op(), .try_into()
msg: format!("SQL connection error: {}", e), .map_err(|e| RuntimeMsg(format!("SQL connection error: {}", e)))?,
})?); );
Ok(()) Ok(())
} }
} }