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
#[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 {

View file

@ -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<Self::Item> {
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<dyn MpsOp>).into()
}) {
.init_with_params(&self.params)
{
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::{
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<MpsContext>,
rows: Option<Vec<Result<MpsItem, RuntimeError>>>,
rows: Option<Vec<Result<MpsItem, RuntimeMsg>>>,
current: usize,
}
impl SqlStatement {
fn get_item(&mut self, increment: bool) -> Option<MpsIteratorItem> {
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<dyn MpsOp>).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<dyn MpsOp>).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<dyn MpsOp>).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);

View file

@ -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<MpsContext>,
rows: Option<Vec<Result<MpsItem, RuntimeError>>>,
rows: Option<Vec<Result<MpsItem, RuntimeMsg>>>,
current: usize,
}
impl SimpleSqlStatement {
fn get_item(&mut self, increment: bool) -> Option<MpsIteratorItem> {
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<dyn MpsOp>).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<dyn MpsOp>).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<dyn MpsOp>).into()
}),
QueryMode::Album => ctx.database.album_like(&self.query, &mut move || {
(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()
}),
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);

View file

@ -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<Vec<Result<MpsItem, RuntimeError>>, RuntimeError>;
pub type QueryResult = Result<Vec<Result<MpsItem, RuntimeMsg>>, 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<String, String>,
op: &mut QueryOp,
) -> Result<(), RuntimeError>;
fn init_with_params(&mut self, params: &HashMap<String, String>) -> 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<String, String>,
op: &mut QueryOp,
) -> Result<(), RuntimeError> {
fn init_with_params(&mut self, params: &HashMap<String, String>) -> 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(())
}
}