use std::{ops::RangeBounds, sync::Arc};
use genawaiter::sync::Gen;
use itertools::Itertools;
use melstructs::{Address, BlockHeight, CoinData, CoinValue, Denom, TxHash};
use rusqlite::ToSql;
use crate::{pool::Pool, repeat_fallible, BalanceTracker};
#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq)]
pub struct CoinInfo {
pub create_txhash: TxHash,
pub create_index: u8,
pub create_height: BlockHeight,
pub coin_data: CoinData,
pub spend_info: Option<CoinSpendInfo>,
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Copy)]
pub struct CoinSpendInfo {
pub spend_txhash: TxHash,
pub spend_index: usize,
pub spend_height: BlockHeight,
}
#[derive(Clone)]
pub struct CoinQuery {
pub(crate) pool: Pool,
filters: Vec<String>,
params: Vec<Arc<dyn ToSql>>,
}
unsafe impl Send for CoinQuery {}
impl CoinQuery {
pub(crate) fn new(pool: Pool) -> Self {
Self {
pool,
filters: vec![],
params: vec![],
}
}
pub fn create_txhash(self, txhash: TxHash) -> Self {
self.add_eq_filter("create_txhash", txhash.to_string())
}
pub fn create_index(self, create_index: u8) -> Self {
self.add_eq_filter("create_index", create_index)
}
pub fn create_height_range(self, range: impl RangeBounds<u64>) -> Self {
self.add_range_filter("create_height", range, |f| *f)
}
pub fn unspent(mut self) -> Self {
self.filters.push("spend_txhash is null".into());
self
}
pub fn unspent_by(mut self, height: BlockHeight) -> Self {
self.filters
.push("(spend_txhash is null or spend_height > ?)".into());
self.params.push(Arc::new(height.0));
self.create_height_range(..=height.0)
}
pub fn spend_txhash(self, txhash: TxHash) -> Self {
self.add_eq_filter("spend_txhash", txhash.to_string())
}
pub fn spend_index(self, spend_index: u8) -> Self {
self.add_eq_filter("spend_index", spend_index)
}
pub fn spend_height_range(self, range: impl RangeBounds<u64>) -> Self {
self.add_range_filter("spend_height", range, |f| *f)
}
pub fn value_range(self, range: impl RangeBounds<CoinValue>) -> Self {
self.add_range_filter("value", range, |f| f.0.to_be_bytes())
}
pub fn denom(self, denom: Denom) -> Self {
self.add_eq_filter("denom", denom.to_bytes().to_vec())
}
pub fn covhash(self, covhash: Address) -> Self {
self.add_eq_filter("covhash", covhash.to_string())
}
pub fn additional_data(self, additional_data: &[u8]) -> Self {
self.add_eq_filter("additional_data", additional_data.to_vec())
}
fn add_eq_filter<T: ToSql + 'static>(mut self, field: &str, val: T) -> Self {
self.filters.push(format!("{} == ?", field));
self.params.push(Arc::new(val));
self
}
fn add_range_filter<T, U: ToSql + 'static>(
mut self,
field: &str,
range: impl RangeBounds<T>,
f: impl Fn(&T) -> U,
) -> Self {
match range.start_bound() {
std::ops::Bound::Included(v) => {
self.filters.push(format!("{} >= ?", field));
self.params.push(Arc::new(f(v)));
}
std::ops::Bound::Excluded(v) => {
self.filters.push(format!("{} > ?", field));
self.params.push(Arc::new(f(v)));
}
std::ops::Bound::Unbounded => {}
}
match range.end_bound() {
std::ops::Bound::Included(v) => {
self.filters.push(format!("{} <= ?", field));
self.params.push(Arc::new(f(v)));
}
std::ops::Bound::Excluded(v) => {
self.filters.push(format!("{} < ?", field));
self.params.push(Arc::new(f(v)));
}
std::ops::Bound::Unbounded => {}
}
self
}
pub fn balance_tracker(self) -> BalanceTracker {
BalanceTracker::new(self)
}
pub fn iter(&self) -> impl Iterator<Item = CoinInfo> + '_ {
let gen = Gen::new(|co| async move {
let query = format!(
"select * from coins where {}",
self.filters.iter().join(" and ")
);
log::debug!("iter query: {:?}", query);
let conn = self.pool.get_conn();
let mut stmt = repeat_fallible(|| conn.prepare_cached(&query));
let params: Vec<&dyn ToSql> = self.params.iter().map(|f| f.as_ref()).collect_vec();
let i = stmt
.query_map(¶ms[..], |row| {
let create_txhash: String = row.get(0)?;
let create_txhash = TxHash(create_txhash.parse().unwrap());
let create_index: u8 = row.get(1)?;
let create_height: BlockHeight = BlockHeight(row.get(2)?);
let spend_txhash: Option<String> = row.get(3)?;
let spend_txhash: Option<TxHash> =
spend_txhash.map(|x| TxHash(x.parse().unwrap()));
let spend_index: Option<usize> = row.get(4)?;
let spend_height: Option<u64> = row.get(5)?;
let spend_height: Option<BlockHeight> = spend_height.map(|h| h.into());
let value: CoinValue = u128::from_be_bytes(row.get(6)?).into();
let denom: Vec<u8> = row.get(7)?;
let denom: Denom = Denom::from_bytes(&denom).unwrap();
let covhash: String = row.get(8)?;
let covhash: Address = covhash.parse().unwrap();
let additional_data: Vec<u8> = row.get(9)?;
Ok(CoinInfo {
create_txhash,
create_index,
create_height,
coin_data: CoinData {
covhash,
value,
denom,
additional_data: additional_data.into(),
},
spend_info: spend_txhash.map(|spend_txhash| CoinSpendInfo {
spend_txhash,
spend_index: spend_index.unwrap(),
spend_height: spend_height.unwrap(),
}),
})
})
.unwrap();
for elem in i {
co.yield_(elem.unwrap()).await;
}
});
gen.into_iter()
}
}