Remove OpGetter from sql context functionality
This commit is contained in:
parent
70950e0bc7
commit
9ea25c27de
5 changed files with 74 additions and 137 deletions
|
@ -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 {
|
||||
|
|
|
@ -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))))),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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!(
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue