tag2upload_service_manager/
bsql_queries.rs1
2use crate::prelude::*;
3use db_support::RowId;
4
5pub type DynToSql<'s> = &'s (dyn ToSql + Sync + 's);
6
7fn 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 fn bsql_extend_params<'v, 'p: 'v>(&'p self, p: &mut Vec<DynToSql<'v>>);
40
41 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
177pub 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#[macro_export]
236macro_rules! bsql {
237 {@ [
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 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 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}