tag2upload_service_manager/
bsql_queries.rs
use crate::prelude::*;
use db_support::RowId;
pub type DynToSql<'s> = &'s (dyn ToSql + Sync + 's);
pub trait IsFragment: Sync {
fn bsql_extend_text(&self, s: &mut String);
fn bsql_extend_params<'v, 'p: 'v>(&'p self, p: &mut Vec<DynToSql<'v>>);
fn bsql_note_locs(&self, la: &mut CodeLocationAccumulator);
}
#[derive(Clone, Copy)]
pub struct BoundSql<'s>(
pub &'s [&'s (dyn IsFragment)],
pub CodeLocation,
);
impl<'s> IsFragment for BoundSql<'s> {
fn bsql_extend_text(&self, s: &mut String) {
for i in self.0 {
i.bsql_extend_text(s);
}
}
fn bsql_extend_params<'v, 'p: 'v>(&'p self, p: &mut Vec<DynToSql<'v>>) {
for i in self.0 {
i.bsql_extend_params(p);
}
}
fn bsql_note_locs(&self, la: &mut CodeLocationAccumulator) {
let BoundSql(children, self_) = self;
la.push(*self_);
for c in *children {
c.bsql_note_locs(la);
}
}
}
impl<'s> BoundSql<'s> {
pub fn mk_sql_text(&self) -> String {
let mut s = String::new();
self.bsql_extend_text(&mut s);
s
}
pub fn mk_params(&self) -> Vec<&(dyn ToSql)> {
let mut p = vec![];
self.bsql_extend_params(&mut p);
let p = p.into_iter().map(|i| i as _).collect();
p
}
pub fn mk_params_for_exec(
&self,
#[allow(unused)]
sql_text: &str,
) -> Vec<&(dyn ToSql)> {
#[cfg(test)]
test::bsql_note_used(self, sql_text);
self.mk_params()
}
}
#[derive(Clone, Copy)]
pub struct Text<'s>(pub &'s str);
impl IsFragment for Text<'_> {
fn bsql_extend_text(&self, s: &mut String) { *s += self.0 }
fn bsql_extend_params<'v, 'p: 'v>(&'p self, _p: &mut Vec<DynToSql<'v>>) { }
fn bsql_note_locs(&self, _la: &mut CodeLocationAccumulator) {}
}
#[derive(Clone, Copy)]
pub struct Param<'s>(pub DynToSql<'s>);
impl<'s> IsFragment for Param<'s> {
fn bsql_extend_text(&self, s: &mut String) {
*s += " ? "
}
fn bsql_extend_params<'v, 'p: 'v>(&'p self, p: &mut Vec<DynToSql<'v>>) {
p.push(self.0)
}
fn bsql_note_locs(&self, _la: &mut CodeLocationAccumulator) {}
}
pub(crate) fn extend_texts_sep_commas<'s>(
s: &mut String,
cols: impl Iterator<Item = &'s str>,
) {
for i in cols.intersperse(",") {
*s += i
}
}
#[derive(Clone, Copy)]
pub struct ParamList<'s, P: ToSql + Sync>(pub &'s [P]);
impl<P: ToSql + Sync> IsFragment for ParamList<'_, P>
{
fn bsql_extend_text(&self, s: &mut String) {
*s += " ";
extend_texts_sep_commas(s, self.0.iter().map(|_| "?"));
*s += " ";
}
fn bsql_extend_params<'v, 'p: 'v>(&'p self, p: &mut Vec<DynToSql<'v>>) {
for i in self.0 {
p.push(i)
}
}
fn bsql_note_locs(&self, _la: &mut CodeLocationAccumulator) {}
}
pub trait AsFragment {
type F<'s>: IsFragment where Self: 's;
fn as_fragment(&self) -> Self::F<'_>;
}
impl<'s> AsFragment for BoundSql<'s> {
type F<'u> = Self where Self: 'u;
fn as_fragment(&self) -> Self::F<'_> { *self }
}
impl<P> AsFragment for P where P: ToSql + Sync {
type F<'s> = Param<'s> where P: 's;
fn as_fragment(&self) -> Self::F<'_> { Param(self) }
}
#[macro_export]
macro_rules! bsql {
{@ [
$str:literal
$($rest:tt)* ] $($out:tt)* } => {bsql!(@ [ $($rest)* ] $($out)*
&$crate::bsql_queries::Text($str) ,
)};
{@ [
( $param:expr )
$($rest:tt)* ] $($out:tt)* } => {bsql!(@ [ $($rest)* ] $($out)*
&$crate::bsql_queries::AsFragment::as_fragment(&$param) ,
)};
{@ [
$param:ident
$($rest:tt)* ] $($out:tt)* } => {bsql!(@ [ $($rest)* ] $($out)*
&$crate::bsql_queries::AsFragment::as_fragment(&$param) ,
)};
{@ [
+~( $row:expr )
$($rest:tt)* ] $($out:tt)* } => {bsql!(@ [ $($rest)* ] $($out)*
&$crate::bsql_rows::AsBSqlRowParams {
row: &$row,
include_row_id: std::result::Result::Err($crate::bsql_rows::SkipRowId),
} ,
)};
{@ [
+*( $param:expr )
$($rest:tt)* ] $($out:tt)* } => {sql!(@ [ $($rest)* ] $($out)*
&$crate::sql_queries::SqlRowParams {
row: &$row,
include_rowid: std::result::Result::Ok(()),
} ,
)};
{@ [
[ $($param:expr),* $(,)? ]
$($rest:tt)* ] $($out:tt)* } => {bsql!(@ [ $($rest)* ] $($out)*
&$crate::bsql_queries::ParamList(&[$(&$param,)*]) ,
)};
{@ [ ] $($out:tt)* } =>
{ $crate::bsql_queries::BoundSql(
&[ $($out)* ],
$crate::code_location!(),
) };
{@ [ $wrong:tt $($rest:tt)* ] $($out:tt)* } =>
{ compile_error!($wrong) };
{$( $input:tt )* } =>
{bsql!(@ [ $( $input )* ] )};
}
#[ext(RusqliteConnectionExt)]
impl rusqlite::Connection {
pub fn with_transaction<R, E>(
&mut self,
behaviour: rusqlite::TransactionBehavior,
mut f: impl FnMut(&mut rusqlite::Transaction) -> Result<R, E>,
) -> Result<Result<R, E>, rusqlite::Error> {
let mut t = self.transaction_with_behavior(behaviour)?;
let r = f(&mut t);
if r.is_ok() {
t.commit()?;
}
Ok(r)
}
}
#[ext(RusqliteTransactionExt)]
impl rusqlite::Transaction<'_> {
pub fn bsql_exec_concurrent(&self, bsql: BoundSql) -> Result<usize, IE> {
let sql_text = bsql.mk_sql_text();
self.execute(&sql_text, &*bsql.mk_params_for_exec(&sql_text))
.with_context(|| format!("{:?}", &sql_text))
.into_internal("bsql_exec* failed")
}
pub fn bsql_exec(&mut self, bsql: BoundSql) -> Result<usize, IE> {
self.bsql_exec_concurrent(bsql)
}
pub fn bsql_query_rows_concurrent<R>(
&self, bsql: BoundSql,
f: impl FnOnce(rusqlite::Rows<'_>) -> R,
) -> Result<R, IE> {
let sql_text = bsql.mk_sql_text();
(|| {
let mut stmt = self.prepare(&sql_text)
.with_context(|| format!("{:?}", &sql_text))
.into_internal("prepare")?;
let rows = stmt.query(&*bsql.mk_params_for_exec(&sql_text))
.context("query")?;
Ok::<_, AE>(f(rows))
})()
.into_internal(format_args!("db query: {sql_text:}"))
}
pub fn bsql_insert(&mut self, bsql: BoundSql) -> Result<RowId, IE> {
let sql_text = bsql.mk_sql_text();
self.execute(&sql_text, &*bsql.mk_params_for_exec(&sql_text))
.with_context(|| format!("{:?}", &sql_text))
.into_internal("bsql_insert failed")?;
let rowid = self.last_insert_rowid();
if rowid == 0 {
return Err(internal!("inserted rowid is 0: {:?}",
bsql.mk_sql_text()));
}
Ok(rowid)
}
pub fn bsql_exec_1_affected(&mut self, bsql: BoundSql) -> Result<(), IE> {
let n_affected = self.bsql_exec(bsql)?;
if n_affected != 1 {
return Err(internal!(
"db query affected {n_affected}: {:?}",
bsql.mk_sql_text(),
));
}
Ok(())
}
pub fn bsql_query_01<R>(&mut self, bsql: BoundSql) -> Result<Option<R>, IE>
where R: FromSqlRow
{
let sql_text = bsql.mk_sql_text();
self.query_row(
&sql_text,
&*bsql.mk_params_for_exec(&sql_text),
|row| Ok(R::from_sql_row(row)),
)
.optional()
.into_internal(format_args!("db query failed: {sql_text:?}"))?
.transpose()
.into_internal(format_args!("db query bad data: {sql_text:?}"))
}
pub fn bsql_query_1<R>(&mut self, bsql: BoundSql) -> Result<R, IE>
where R: FromSqlRow
{
self.bsql_query_01(bsql)?.ok_or_else(
|| internal!("db query no data: {}", bsql.mk_sql_text())
)
}
pub fn bsql_query_n_call<R, RE>(
&mut self,
bsql: BoundSql,
mut per_row: impl FnMut(R) -> Result<(), RE>
) -> Result<Result<(), RE>, IE>
where R: FromSqlRow
{
let sql_text = bsql.mk_sql_text();
(|| {
let mut stmt = self.prepare(&sql_text).context("prepare")?;
let mut rows = stmt.query(&*bsql.mk_params_for_exec(&sql_text))
.context("query")?;
while let Some(row) = rows.next().context("next row")? {
let row = R::from_sql_row(row).context("convert row")?;
match per_row(row) {
Ok(()) => {},
Err(e) => return Ok(Err(e)),
}
}
Ok::<_, AE>(Ok(()))
})()
.into_internal(format_args!("db query: {sql_text:?}"))
}
pub fn bsql_query_n_map<R, V, RE>(
&mut self,
bsql: BoundSql,
mut map: impl FnMut(R) -> Result<V, RE>,
) -> Result<Result<Vec<V>, RE>, IE>
where R: FromSqlRow
{
let mut out = vec![];
self.bsql_query_n_call(bsql, |r| {
let r = map(r)?;
out.push(r);
Ok::<_, RE>(())
}).map(
move |y| y.map(move |()| out)
)
}
pub fn bsql_query_n_vec<R>(
&mut self,
bsql: BoundSql,
) -> Result<Vec<R>, IE>
where R: FromSqlRow
{
Ok(self.bsql_query_n_map(bsql, |r| Ok::<_, Void>(r))?
.void_unwrap())
}
}