tag2upload_service_manager/
bsql_queries.rs

1
2use crate::prelude::*;
3use db_support::RowId;
4
5pub type DynToSql<'s> = &'s (dyn ToSql + Sync + 's);
6
7// We have a #[test] which checks of the generated sql syntax.
8// This is done rather ad-hoc by grepping for bsql! calls,
9// tracking executed queries, and expecting that the t_comprehensive test
10// case runs *every* query at least once.
11//
12// Doing it in a more principled way would be quite hard.
13// IsFragment and BoundSql are hard to use for this, because
14// they include *values* which our automatic test cases wouldn't
15// know how to produce.  We'd only want to test prepare.
16// (Whereas, the t_comprehensive test does test execution and semantics.)
17// We could have had a monomorphised rather than dyn IsFragment
18// which could provide text without the value,
19// but if we did that we wouldn't be able to name the type of the
20// the `BoundSql` type in function arguments, which would be annoying
21// and we certainly couldn't make an inventory entry or #[test]
22// since those can't be generated during monomorphisation.
23
24/// Should we dump sql query text, parameters, and many results?
25fn debug_to_stderr() -> bool { cfg!(test) }
26macro_rules! debug_println { { $( $x:tt )* } => {
27    if debug_to_stderr() { eprintln!( $($x)* ); }
28} }
29
30pub trait IsFragment: Sync {
31    fn bsql_extend_text(&self, s: &mut String);
32
33    // On lifetimes: we put elements with lifetime `'p`
34    // into a vector of elements of a possibly-shorter lifetime `'v`.
35    // That allows us to mix a variety of input lifetimes
36    // (using just one lifetime means we can't mix differently-lifetimed
37    // inputs because `&mut Vec`'s API would let us take elements out,
38    // so it's invariant).
39    fn bsql_extend_params<'v, 'p: 'v>(&'p self, p: &mut Vec<DynToSql<'v>>);
40
41    // `BoundSql` implements this nontrivially, and that means it catches every
42    // call to `bsql!`.  The helper types implement as a no-op.
43    fn bsql_note_locs(&self, la: &mut CodeLocationAccumulator);
44}
45
46#[derive(Clone, Copy)]
47pub struct BoundSql<'s>(
48    pub &'s [&'s (dyn IsFragment)],
49
50    pub CodeLocation,
51);
52impl<'s> IsFragment for BoundSql<'s> {
53    fn bsql_extend_text(&self, s: &mut String) {
54        for i in self.0 {
55            i.bsql_extend_text(s);
56        }
57    }
58    fn bsql_extend_params<'v, 'p: 'v>(&'p self, p: &mut Vec<DynToSql<'v>>) {
59        for i in self.0 {
60            i.bsql_extend_params(p);
61        }
62    }
63    fn bsql_note_locs(&self, la: &mut CodeLocationAccumulator) {
64        let BoundSql(children, self_) = self;
65        la.push(*self_);
66        for c in *children {
67            c.bsql_note_locs(la);
68        }
69    }
70}
71impl<'s> BoundSql<'s> {
72    pub fn mk_sql_text(&self) -> String {
73        let mut s = String::new();
74        self.bsql_extend_text(&mut s);
75        s
76    }
77    pub fn mk_params(&self) -> Vec<&(dyn ToSql)> {
78        let mut p = vec![];
79        self.bsql_extend_params(&mut p);
80        let p = p.into_iter().map(|i| i as _).collect();
81        p
82    }
83    pub fn mk_params_for_exec(
84        &self,
85        #[allow(unused)]
86        sql_text: &str,
87    ) -> Vec<&(dyn ToSql)> {
88        #[cfg(test)]
89        test::bsql_note_used(self, sql_text);
90        let p = self.mk_params();
91        if debug_to_stderr() {
92            use rusqlite::types::ToSqlOutput as O;
93            use rusqlite::types::ValueRef as VR;
94            eprintln!("bsql text: {sql_text}");
95            for e in &p {
96                eprintln!("    {}", match e.to_sql() {
97                    Ok(O::Borrowed(VR::Text(b))) =>
98                        format!("Ok(Borrowed(Text~({:?})))",
99                                     str::from_utf8(b)),
100                    other => format!("{:?}", other),
101                });
102            }
103        }
104        p
105    }
106}
107
108#[derive(Clone, Copy)]
109pub struct Text<'s>(pub &'s str);
110impl IsFragment for Text<'_> {
111    fn bsql_extend_text(&self, s: &mut String) { *s += self.0 }
112    fn bsql_extend_params<'v, 'p: 'v>(&'p self, _p: &mut Vec<DynToSql<'v>>) { }
113    fn bsql_note_locs(&self, _la: &mut CodeLocationAccumulator) {}
114}
115
116#[derive(Clone, Copy)]
117pub struct Param<'s>(pub DynToSql<'s>);
118impl<'s> IsFragment for Param<'s> {
119    fn bsql_extend_text(&self, s: &mut String) {
120        *s += " ? "
121    }
122    fn bsql_extend_params<'v, 'p: 'v>(&'p self, p: &mut Vec<DynToSql<'v>>) {
123        p.push(self.0)
124    }
125    fn bsql_note_locs(&self, _la: &mut CodeLocationAccumulator) {}
126}
127
128#[derive(Clone, Copy)]
129pub struct IterRowValue<'s, I>(pub &'s I);
130impl<'s, I, T> IsFragment for IterRowValue<'s, I>
131where &'s I: IntoIterator<Item = &'s T> + Sync,
132      T: ToSql + Sync + 's,
133{
134    fn bsql_extend_text(&self, s: &mut String) {
135        for i in chain!(
136            ["("],
137            self.0.into_iter().map(|_| "?").intersperse(","),
138            [")"],
139        ) {
140            *s += i;
141        }
142    }
143    fn bsql_extend_params<'v, 'p: 'v>(&'p self, p: &mut Vec<DynToSql<'v>>) {
144        for i in self.0 {
145            p.push(i);
146        }
147    }
148    fn bsql_note_locs(&self, _la: &mut CodeLocationAccumulator) {}
149}
150
151pub(crate) fn extend_texts_sep_commas<'s>(
152    s: &mut String,
153    cols: impl Iterator<Item = &'s str>,
154) {
155    for i in cols.intersperse(",") {
156        *s += i
157    }
158}
159
160#[derive(Clone, Copy)]
161pub struct ParamList<'s, P: ToSql + Sync>(pub &'s [P]);
162impl<P: ToSql + Sync> IsFragment for ParamList<'_, P>
163{
164    fn bsql_extend_text(&self, s: &mut String) {
165        *s += " ";
166        extend_texts_sep_commas(s, self.0.iter().map(|_| "?"));
167        *s += " ";
168    }
169    fn bsql_extend_params<'v, 'p: 'v>(&'p self, p: &mut Vec<DynToSql<'v>>) {
170        for i in self.0 {
171            p.push(i)
172        }
173    }
174    fn bsql_note_locs(&self, _la: &mut CodeLocationAccumulator) {}
175}
176
177/// Something that can be found in `(..)` in [`bsql!`](crate::bsql)
178//
179// We need a trait for this because the type conversion
180// is different for `ToSql` elements vs `BounddSql` ones.
181pub trait AsFragment {
182    type F<'s>: IsFragment where Self: 's;
183    fn as_fragment(&self) -> Self::F<'_>;
184}
185impl<'s> AsFragment for BoundSql<'s> {
186    type F<'u> = Self where Self: 'u;
187    fn as_fragment(&self) -> Self::F<'_> { *self }
188}
189impl<P> AsFragment for P where P: ToSql + Sync {
190    type F<'s> = Param<'s> where P: 's;
191    fn as_fragment(&self) -> Self::F<'_> { Param(self) }
192}
193
194/// Generates a [`BoundSql`].
195///
196/// Input contains the following pieces:
197///
198///  * **"string literal"**:
199///    Plain literal sql text.
200///
201///  * **`(`expression`)`**:
202///    If `expression` is `ToSql + Sync`,
203///    adds it as a bind parameter,
204///    and adds `" ? "` to the sql query text.
205///
206///    Or, `expression` can be [`BoundSql`].
207///    Its sql text appears here, and its bind parameters are also added.
208///
209///  * **identifier**:
210///    Abbreviated syntax for `(identifier)`.
211///
212///  * **`[` expression, expression, ... `]`**:
213///    Bind parameters comma separated in the SQL text.
214///    Every `expression` must have the same type
215///    (they're made into a slice).
216///    The expansion in the sql text is `" ?,? "`
217///    with an appropriate number of `?`.
218///    Useful with conditions like `"VALUE IN (...)"`.
219///
220///  * **`+~`(expression`)` `+*(`expression`)`**:
221///    A row for an `INSERT`.
222///    `expression` must implement `AsSqlRow`.
223///    Expands to `(c0,c1,..) VALUES (?,?,..)`
224///    with bindings for the columns `c0`, `c1`, etc., in `expression`.
225///    With `+~`, skips rowid columns (marked `#[deftly(bsql(rowid))]`);
226///    with `+*`, includes them.
227///
228///  * **`*[` iter `]`**:
229///    A sqlite row value made from an iterator.
230///    `iter` must be a reference where the reference implements
231///    `IntoIterator<Item: &ToSql + Sync>`.
232///    Expands to `(?,?,?)` (with the appropriate number of `?`).
233///    The iterator will be created and drained twice, and must
234///    yield the same number of elements each time.
235#[macro_export]
236macro_rules! bsql {
237    // Intermediate syntax for the T-munter is
238    //   @ [ UNPROCESSED INPUT ] PROCESSED OUTPUT
239    {@ [
240        $str:literal
241    $($rest:tt)* ] $($out:tt)* } => {bsql!(@ [ $($rest)* ] $($out)*
242        &$crate::bsql_queries::Text($str) ,
243    )};
244    
245    {@ [
246        ( $param:expr )
247    $($rest:tt)* ] $($out:tt)* } => {bsql!(@ [ $($rest)* ] $($out)*
248        &$crate::bsql_queries::AsFragment::as_fragment(&$param) ,
249    )};
250
251    {@ [
252        $param:ident
253    $($rest:tt)* ] $($out:tt)* } => {bsql!(@ [ $($rest)* ] $($out)*
254        &$crate::bsql_queries::AsFragment::as_fragment(&$param) ,
255    )};
256
257    {@ [
258        +~( $row:expr )
259    $($rest:tt)* ] $($out:tt)* } => {bsql!(@ [ $($rest)* ] $($out)*
260        &$crate::bsql_rows::AsBSqlRowParams {
261            row: &$row, 
262   include_row_id: std::result::Result::Err($crate::bsql_rows::SkipRowId),
263        } ,
264    )};
265
266    {@ [
267        *[ $iter:expr ]
268    $($rest:tt)* ] $($out:tt)* } => {bsql!(@ [ $($rest)* ] $($out)*
269        &bsql_queries::IterRowValue($iter) ,
270    )};
271
272    {@ [
273        +*( $param:expr )
274    $($rest:tt)* ] $($out:tt)* } => {sql!(@ [ $($rest)* ] $($out)*
275        &$crate::sql_queries::SqlRowParams {
276            row: &$row,
277            include_rowid: std::result::Result::Ok(()),
278        } ,
279    )};
280
281    {@ [
282        [ $($param:expr),* $(,)? ]
283    $($rest:tt)* ] $($out:tt)* } => {bsql!(@ [ $($rest)* ] $($out)*
284        &$crate::bsql_queries::ParamList(&[$(&$param,)*]) ,
285    )};
286    
287    {@ [ ] $($out:tt)* } =>
288    { $crate::bsql_queries::BoundSql(
289        &[ $($out)* ],
290        $crate::code_location!(),
291    ) };
292
293    {@ [ $wrong:tt $($rest:tt)* ] $($out:tt)* } =>
294    { compile_error!($wrong) };
295
296    {$( $input:tt )* } =>
297    {bsql!(@ [ $( $input )* ] )};
298}
299
300#[ext(RusqliteConnectionExt)]
301impl rusqlite::Connection {
302    pub fn with_transaction<R, E>(
303        &mut self,
304        behaviour: rusqlite::TransactionBehavior,
305        mut f: impl FnMut(&mut rusqlite::Transaction) -> Result<R, E>,
306    ) -> Result<Result<R, E>, rusqlite::Error> {
307        let mut t = self.transaction_with_behavior(behaviour)?;
308        let r = f(&mut t);
309        if r.is_ok() {
310            t.commit()?;
311        }
312        Ok(r)
313    }
314}
315
316#[ext(RusqliteTransactionExt)]
317impl rusqlite::Transaction<'_> {
318    /// Like `bsql_exec` but takes `&self`
319    ///
320    /// Caller must convince themselves the concurrency is fine.
321    /// See <https://sqlite.org/isolation.html>
322    /// "No Isolation Between Operations On The Same Database Connection".
323    pub fn bsql_exec_concurrent(&self, bsql: BoundSql) -> Result<usize, IE> {
324        let sql_text = bsql.mk_sql_text();
325        let r = self.execute(&sql_text, &*bsql.mk_params_for_exec(&sql_text))
326            .with_context(|| format!("{:?}", &sql_text))
327            .into_internal("bsql_exec* failed")?;
328        debug_println!("    bsql_exec returned {r}");
329        Ok(r)
330    }
331    pub fn bsql_exec(&mut self, bsql: BoundSql) -> Result<usize, IE> {
332        self.bsql_exec_concurrent(bsql)
333    }
334
335    /// Run a query concurrently, giving access to the `Rows`
336    ///
337    /// See `bsql_exec_concurrent`
338    pub fn bsql_query_rows_concurrent<R>(
339        &self, bsql: BoundSql,
340        f: impl FnOnce(rusqlite::Rows<'_>) -> R,
341    ) -> Result<R, IE> {
342        let sql_text = bsql.mk_sql_text();
343        (|| {
344            let mut stmt = self.prepare(&sql_text)
345                .with_context(|| format!("{:?}", &sql_text))
346                .into_internal("prepare")?;
347            let rows = stmt.query(&*bsql.mk_params_for_exec(&sql_text))
348                .context("query")?;
349
350            Ok::<_, AE>(f(rows))
351        })()
352            .into_internal(format_args!("db query: {sql_text:}"))
353    }
354
355    pub fn bsql_insert(&mut self, bsql: BoundSql) -> Result<RowId, IE> {
356        let sql_text = bsql.mk_sql_text();
357        self.execute(&sql_text, &*bsql.mk_params_for_exec(&sql_text))
358            .with_context(|| format!("{:?}", &sql_text))
359            .into_internal("bsql_insert failed")?;
360
361        let rowid = self.last_insert_rowid();
362        if rowid == 0 {
363            return Err(internal!("inserted rowid is 0: {:?}",
364                                 bsql.mk_sql_text()));
365        }
366        debug_println!("    bsql_insert rowid {rowid:?}");
367
368        Ok(rowid)
369    }
370
371    pub fn bsql_exec_1_affected(&mut self, bsql: BoundSql) -> Result<(), IE> {
372        let n_affected = self.bsql_exec(bsql)?;
373        if n_affected != 1 {
374            return Err(internal!(
375                "db query affected {n_affected}: {:?}",
376                bsql.mk_sql_text(),
377            ));
378        }
379        Ok(())
380    }
381
382    pub fn bsql_query_01<R>(&mut self, bsql: BoundSql) -> Result<Option<R>, IE>
383    where R: FromSqlRow
384    {
385        let sql_text = bsql.mk_sql_text();
386        self.query_row(
387            &sql_text,
388            &*bsql.mk_params_for_exec(&sql_text),
389            |row| {
390                debug_println!("    bsql_query_01 row {row:#?}");
391                Ok(R::from_sql_row(row))
392            },
393        )
394            .optional()
395            .into_internal(format_args!("db query failed: {sql_text:?}"))?
396            .transpose()
397            .into_internal(format_args!("db query bad data: {sql_text:?}"))
398    }
399
400    pub fn bsql_query_1<R>(&mut self, bsql: BoundSql) -> Result<R, IE>
401    where R: FromSqlRow
402    {
403        self.bsql_query_01(bsql)?.ok_or_else(
404            || internal!("db query no data: {}", bsql.mk_sql_text())
405        )
406    }
407
408    pub fn bsql_query_n_call<R, RE>(
409        &mut self,
410        bsql: BoundSql,
411        mut per_row: impl FnMut(R) -> Result<(), RE>
412    ) -> Result<Result<(), RE>, IE>
413    where R: FromSqlRow
414    {
415        let sql_text = bsql.mk_sql_text();
416        (|| {
417            let mut stmt = self.prepare(&sql_text).context("prepare")?;
418            let mut rows = stmt.query(&*bsql.mk_params_for_exec(&sql_text))
419                .context("query")?;
420
421            while let Some(row) = rows.next().context("next row")? {
422                debug_println!("    bsql_qeury_n_call row {row:#?}");
423                let row = R::from_sql_row(row).context("convert row")?;
424                match per_row(row) {
425                    Ok(()) => {},
426                    Err(e) => return Ok(Err(e)),
427                }
428            }
429            Ok::<_, AE>(Ok(()))
430        })()
431            .into_internal(format_args!("db query: {sql_text:?}"))
432    }
433
434    pub fn bsql_query_n_map<R, V, RE>(
435        &mut self,
436        bsql: BoundSql,
437        mut map: impl FnMut(R) -> Result<V, RE>,
438    ) -> Result<Result<Vec<V>, RE>, IE>
439    where R: FromSqlRow
440    {
441        let mut out = vec![];
442        self.bsql_query_n_call(bsql, |r| {
443            let r = map(r)?;
444            out.push(r);
445            Ok::<_, RE>(())
446        }).map(
447            move |y| y.map(move |()| out)
448        )
449    }
450
451    pub fn bsql_query_n_vec<R>(
452        &mut self,
453        bsql: BoundSql,
454    ) -> Result<Vec<R>, IE>
455    where R: FromSqlRow
456    {
457        Ok(self.bsql_query_n_map(bsql, |r| Ok::<_, Void>(r))?
458            .void_unwrap())
459    }
460}