1use crate::database::DatabaseInner;
4use crate::error::{Error, Result};
5use crate::row::FromRow;
6use rusqlite::types::FromSql;
7use rusqlite::{Connection, Params};
8use std::sync::Arc;
9
10mod on_conn {
12 use super::*;
13
14 pub(super) fn execute<P: Params>(conn: &Connection, sql: &str, params: P) -> Result<u64> {
16 let n = conn.execute(sql, params)?;
17 Ok(n as u64)
18 }
19
20 pub(super) fn insert<P: Params>(conn: &Connection, sql: &str, params: P) -> Result<i64> {
22 if conn.execute(sql, params)? == 0 {
23 return Err(Error::NotFound);
24 }
25 Ok(conn.last_insert_rowid())
26 }
27
28 pub(super) fn query_all<T: FromRow, P: Params>(
30 conn: &Connection,
31 sql: &str,
32 params: P,
33 ) -> Result<Vec<T>> {
34 let mut stmt = conn.prepare(sql)?;
35 let rows = stmt.query_map(params, |r| T::from_row(r))?;
36 let mut out = Vec::new();
37 for row in rows {
38 out.push(row?);
39 }
40 Ok(out)
41 }
42
43 pub(super) fn query_optional<T: FromRow, P: Params>(
45 conn: &Connection,
46 sql: &str,
47 params: P,
48 ) -> Result<Option<T>> {
49 let mut stmt = conn.prepare(sql)?;
50 let mut rows = stmt.query(params)?;
51 match rows.next()? {
52 Some(row) => Ok(Some(T::from_row(row)?)),
53 None => Ok(None),
54 }
55 }
56
57 pub(super) fn query_scalar<T: FromSql, P: Params>(
59 conn: &Connection,
60 sql: &str,
61 params: P,
62 ) -> Result<T> {
63 let mut stmt = conn.prepare(sql)?;
64 let mut rows = stmt.query(params)?;
65 match rows.next()? {
66 Some(row) => Ok(row.get(0)?),
67 None => Err(Error::NotFound),
68 }
69 }
70}
71
72pub trait SqlContext {
78 fn ctx_execute<P: Params>(&self, sql: &str, params: P) -> Result<u64>;
80 fn ctx_insert<P: Params>(&self, sql: &str, params: P) -> Result<i64>;
82 fn ctx_query_all<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Vec<T>>;
84 fn ctx_query_optional<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Option<T>>;
86 fn ctx_query_one<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<T> {
88 self.ctx_query_optional(sql, params)?.ok_or(Error::NotFound)
89 }
90 fn ctx_query_scalar<T: FromSql, P: Params>(&self, sql: &str, params: P) -> Result<T>;
92
93 fn ctx_transaction<R>(&self, f: impl FnOnce(&Tx<'_>) -> Result<R>) -> Result<R>;
96}
97
98impl<C: SqlContext> SqlContext for &C {
100 fn ctx_execute<P: Params>(&self, sql: &str, params: P) -> Result<u64> {
101 (**self).ctx_execute(sql, params)
102 }
103 fn ctx_insert<P: Params>(&self, sql: &str, params: P) -> Result<i64> {
104 (**self).ctx_insert(sql, params)
105 }
106 fn ctx_query_all<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Vec<T>> {
107 (**self).ctx_query_all(sql, params)
108 }
109 fn ctx_query_optional<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Option<T>> {
110 (**self).ctx_query_optional(sql, params)
111 }
112 fn ctx_query_scalar<T: FromSql, P: Params>(&self, sql: &str, params: P) -> Result<T> {
113 (**self).ctx_query_scalar(sql, params)
114 }
115 fn ctx_transaction<R>(&self, f: impl FnOnce(&Tx<'_>) -> Result<R>) -> Result<R> {
116 (**self).ctx_transaction(f)
117 }
118}
119
120#[derive(Clone, Copy)]
129pub struct SyncHandle<'db> {
130 pub(crate) inner: &'db Arc<DatabaseInner>,
131}
132
133impl<'db> SyncHandle<'db> {
134 pub fn execute<P: Params>(&self, sql: &str, params: P) -> Result<u64> {
136 self.ctx_execute(sql, params)
137 }
138
139 pub fn query_one<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<T> {
141 self.ctx_query_one(sql, params)
142 }
143
144 pub fn query_optional<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Option<T>> {
146 self.ctx_query_optional(sql, params)
147 }
148
149 pub fn query_scalar<T: FromSql, P: Params>(&self, sql: &str, params: P) -> Result<T> {
151 self.ctx_query_scalar(sql, params)
152 }
153
154 pub fn query_all<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Vec<T>> {
156 self.ctx_query_all(sql, params)
157 }
158
159 pub fn transaction<R>(&self, f: impl FnOnce(&mut Tx<'_>) -> Result<R>) -> Result<R> {
161 let mut tx = self.begin()?;
162 match f(&mut tx) {
163 Ok(v) => {
164 tx.commit()?;
165 Ok(v)
166 }
167 Err(e) => {
168 let _ = tx.rollback();
171 Err(e)
172 }
173 }
174 }
175
176 pub fn begin(&self) -> Result<Tx<'db>> {
178 let guard = self.inner.pool.connections.acquire()?;
179 guard.conn().execute_batch("BEGIN IMMEDIATE")?;
182 log::debug!("transaction begin");
183 Ok(Tx {
184 inner: self.inner,
185 guard,
186 open: true,
187 sp_depth: std::cell::Cell::new(0),
188 #[cfg(feature = "live")]
189 pending: std::sync::Mutex::new(vec![TxPending::default()]),
190 })
191 }
192
193 pub fn with_connection<R>(&self, f: impl FnOnce(&Connection) -> Result<R>) -> Result<R> {
210 let guard = self.inner.pool.connections.acquire()?;
211 #[cfg(feature = "live")]
212 let _hook_capture = self.inner.begin_hook_capture();
213 let out = f(guard.conn());
214 #[cfg(feature = "live")]
215 {
216 let capture = self.inner.take_hook_capture();
217 if !capture.changes.is_empty() {
218 self.inner.tracker.invalidate_changes(capture.changes);
219 } else if !capture.tables.is_empty() {
220 self.inner.tracker.invalidate(Some(capture.tables));
221 }
222 }
223 out
224 }
225
226 #[cfg(feature = "live")]
231 pub fn rearm_hooks(&self) -> Result<()> {
232 self.inner.pool.connections.for_each_connection(|conn| {
233 crate::database::install_preupdate_hook(conn, Arc::clone(&self.inner.hook_columns))
234 })
235 }
236}
237
238#[cfg(feature = "live")]
242pub(crate) mod watch_impl {
243 use super::*;
244 use crate::live::{InvalidationFilter, LiveQuery, OwnedParams, extract_tables};
245 use rusqlite::ToSql;
246 use std::collections::HashSet;
247
248 fn make<T: Clone + Send + 'static>(
250 inner: &Arc<DatabaseInner>,
251 sql: String,
252 params: Result<OwnedParams>,
253 tables: Option<HashSet<String>>,
254 run: impl Fn(&Connection, &str, &OwnedParams) -> Result<T> + Send + Sync + 'static,
255 ) -> LiveQuery<T> {
256 let tracker = Arc::clone(&inner.tracker);
257 match params {
258 Ok(p) => LiveQuery::new(tracker, sql, p, tables, run),
259 Err(e) => {
260 let msg = e.to_string();
262 LiveQuery::new(
263 tracker,
264 sql,
265 OwnedParams::None,
266 Some(HashSet::new()),
267 move |_, _, _| Err(Error::Config(msg.clone())),
268 )
269 }
270 }
271 }
272
273 pub(crate) fn watch_all<T: FromRow + Clone + Send + 'static>(
275 inner: &Arc<DatabaseInner>,
276 sql: &str,
277 params: Result<OwnedParams>,
278 tables: Option<HashSet<String>>,
279 ) -> LiveQuery<Vec<T>> {
280 let tables = tables.or_else(|| extract_tables(sql));
281 make(
282 inner,
283 sql.to_string(),
284 params,
285 tables,
286 crate::live::query_all_owned::<T>,
287 )
288 }
289
290 pub(crate) fn watch_optional<T: FromRow + Clone + Send + 'static>(
292 inner: &Arc<DatabaseInner>,
293 sql: &str,
294 params: Result<OwnedParams>,
295 tables: Option<HashSet<String>>,
296 ) -> LiveQuery<Option<T>> {
297 let tables = tables.or_else(|| extract_tables(sql));
298 make(
299 inner,
300 sql.to_string(),
301 params,
302 tables,
303 crate::live::query_optional_owned::<T>,
304 )
305 }
306
307 pub(crate) fn watch_scalar<T: FromSql + Clone + Send + 'static>(
309 inner: &Arc<DatabaseInner>,
310 sql: &str,
311 params: Result<OwnedParams>,
312 tables: Option<HashSet<String>>,
313 ) -> LiveQuery<T> {
314 let tables = tables.or_else(|| extract_tables(sql));
315 make(
316 inner,
317 sql.to_string(),
318 params,
319 tables,
320 crate::live::query_scalar_owned::<T>,
321 )
322 }
323
324 pub(crate) fn watch_scalar_filtered<T: FromSql + Clone + Send + 'static>(
325 inner: &Arc<DatabaseInner>,
326 sql: &str,
327 params: Result<OwnedParams>,
328 filter: InvalidationFilter,
329 ) -> LiveQuery<T> {
330 let tables = Some(HashSet::from([filter.table_name().to_string()]));
331 let tracker = Arc::clone(&inner.tracker);
332 match params {
333 Ok(p) => LiveQuery::new_filtered(
334 tracker,
335 sql.to_string(),
336 p,
337 tables,
338 Some(filter),
339 crate::live::query_scalar_owned::<T>,
340 ),
341 Err(e) => LiveQuery::new(
342 tracker,
343 sql.to_string(),
344 OwnedParams::None,
345 Some(HashSet::new()),
346 move |_, _, _| Err(Error::Config(e.to_string())),
347 ),
348 }
349 }
350
351 pub(crate) fn tables_from_hint(hint: &[&str]) -> Option<HashSet<String>> {
353 if hint.is_empty() {
354 None
355 } else {
356 Some(hint.iter().map(|s| s.to_string()).collect())
357 }
358 }
359
360 pub(crate) fn params_from_dyn(params: &[&dyn ToSql]) -> Result<OwnedParams> {
362 OwnedParams::from_dyn(params)
363 }
364
365 pub(crate) fn params_named(
367 pairs: Result<Vec<(String, rusqlite::types::Value)>>,
368 ) -> Result<OwnedParams> {
369 pairs.map(OwnedParams::Named)
370 }
371}
372
373#[cfg(feature = "live")]
374impl SyncHandle<'_> {
375 pub fn watch_all<T: FromRow + Clone + Send + 'static>(
377 &self,
378 sql: &str,
379 params: &[&dyn rusqlite::ToSql],
380 ) -> crate::live::LiveQuery<Vec<T>> {
381 watch_impl::watch_all(self.inner, sql, watch_impl::params_from_dyn(params), None)
382 }
383
384 pub fn watch_optional<T: FromRow + Clone + Send + 'static>(
386 &self,
387 sql: &str,
388 params: &[&dyn rusqlite::ToSql],
389 ) -> crate::live::LiveQuery<Option<T>> {
390 watch_impl::watch_optional(self.inner, sql, watch_impl::params_from_dyn(params), None)
391 }
392
393 pub fn watch_scalar<T: FromSql + Clone + Send + 'static>(
395 &self,
396 sql: &str,
397 params: &[&dyn rusqlite::ToSql],
398 ) -> crate::live::LiveQuery<T> {
399 watch_impl::watch_scalar(self.inner, sql, watch_impl::params_from_dyn(params), None)
400 }
401
402 pub fn watch_scalar_filtered<T: FromSql + Clone + Send + 'static>(
403 &self,
404 sql: &str,
405 params: &[&dyn rusqlite::ToSql],
406 filter: crate::live::InvalidationFilter,
407 ) -> crate::live::LiveQuery<T> {
408 watch_impl::watch_scalar_filtered(
409 self.inner,
410 sql,
411 watch_impl::params_from_dyn(params),
412 filter,
413 )
414 }
415}
416
417#[cfg(feature = "live")]
419#[doc(hidden)]
420impl DatabaseInner {
421 pub fn __watch_all_dyn<T: FromRow + Clone + Send + 'static>(
422 self: &Arc<Self>,
423 sql: &str,
424 params: &[&dyn rusqlite::ToSql],
425 ) -> crate::live::LiveQuery<Vec<T>> {
426 watch_impl::watch_all(self, sql, watch_impl::params_from_dyn(params), None)
427 }
428 pub fn __watch_optional_dyn<T: FromRow + Clone + Send + 'static>(
429 self: &Arc<Self>,
430 sql: &str,
431 params: &[&dyn rusqlite::ToSql],
432 ) -> crate::live::LiveQuery<Option<T>> {
433 watch_impl::watch_optional(self, sql, watch_impl::params_from_dyn(params), None)
434 }
435 pub fn __watch_scalar_dyn<T: FromSql + Clone + Send + 'static>(
436 self: &Arc<Self>,
437 sql: &str,
438 params: &[&dyn rusqlite::ToSql],
439 ) -> crate::live::LiveQuery<T> {
440 watch_impl::watch_scalar(self, sql, watch_impl::params_from_dyn(params), None)
441 }
442 pub fn __watch_all_named<T: FromRow + Clone + Send + 'static>(
443 self: &Arc<Self>,
444 sql: &'static str,
445 params: Result<Vec<(String, rusqlite::types::Value)>>,
446 tables: &[&str],
447 ) -> crate::live::LiveQuery<Vec<T>> {
448 watch_impl::watch_all(
449 self,
450 sql,
451 watch_impl::params_named(params),
452 watch_impl::tables_from_hint(tables),
453 )
454 }
455 pub fn __watch_optional_named<T: FromRow + Clone + Send + 'static>(
456 self: &Arc<Self>,
457 sql: &'static str,
458 params: Result<Vec<(String, rusqlite::types::Value)>>,
459 tables: &[&str],
460 ) -> crate::live::LiveQuery<Option<T>> {
461 watch_impl::watch_optional(
462 self,
463 sql,
464 watch_impl::params_named(params),
465 watch_impl::tables_from_hint(tables),
466 )
467 }
468 pub fn __watch_scalar_named<T: FromSql + Clone + Send + 'static>(
469 self: &Arc<Self>,
470 sql: &'static str,
471 params: Result<Vec<(String, rusqlite::types::Value)>>,
472 tables: &[&str],
473 ) -> crate::live::LiveQuery<T> {
474 watch_impl::watch_scalar(
475 self,
476 sql,
477 watch_impl::params_named(params),
478 watch_impl::tables_from_hint(tables),
479 )
480 }
481}
482
483#[cfg(feature = "live")]
486pub trait WatchContext {
487 #[doc(hidden)]
489 fn ctx_watch_all_named<T: FromRow + Clone + Send + 'static>(
490 &self,
491 sql: &'static str,
492 params: Result<Vec<(String, rusqlite::types::Value)>>,
493 tables: &[&str],
494 ) -> crate::live::LiveQuery<Vec<T>>;
495 #[doc(hidden)]
496 fn ctx_watch_optional_named<T: FromRow + Clone + Send + 'static>(
497 &self,
498 sql: &'static str,
499 params: Result<Vec<(String, rusqlite::types::Value)>>,
500 tables: &[&str],
501 ) -> crate::live::LiveQuery<Option<T>>;
502 #[doc(hidden)]
503 fn ctx_watch_scalar_named<T: FromSql + Clone + Send + 'static>(
504 &self,
505 sql: &'static str,
506 params: Result<Vec<(String, rusqlite::types::Value)>>,
507 tables: &[&str],
508 ) -> crate::live::LiveQuery<T>;
509}
510
511#[cfg(feature = "live")]
512impl<C: WatchContext> WatchContext for &C {
513 fn ctx_watch_all_named<T: FromRow + Clone + Send + 'static>(
514 &self,
515 sql: &'static str,
516 params: Result<Vec<(String, rusqlite::types::Value)>>,
517 tables: &[&str],
518 ) -> crate::live::LiveQuery<Vec<T>> {
519 (**self).ctx_watch_all_named(sql, params, tables)
520 }
521 fn ctx_watch_optional_named<T: FromRow + Clone + Send + 'static>(
522 &self,
523 sql: &'static str,
524 params: Result<Vec<(String, rusqlite::types::Value)>>,
525 tables: &[&str],
526 ) -> crate::live::LiveQuery<Option<T>> {
527 (**self).ctx_watch_optional_named(sql, params, tables)
528 }
529 fn ctx_watch_scalar_named<T: FromSql + Clone + Send + 'static>(
530 &self,
531 sql: &'static str,
532 params: Result<Vec<(String, rusqlite::types::Value)>>,
533 tables: &[&str],
534 ) -> crate::live::LiveQuery<T> {
535 (**self).ctx_watch_scalar_named(sql, params, tables)
536 }
537}
538
539#[cfg(feature = "live")]
540impl WatchContext for SyncHandle<'_> {
541 fn ctx_watch_all_named<T: FromRow + Clone + Send + 'static>(
542 &self,
543 sql: &'static str,
544 params: Result<Vec<(String, rusqlite::types::Value)>>,
545 tables: &[&str],
546 ) -> crate::live::LiveQuery<Vec<T>> {
547 watch_impl::watch_all(
548 self.inner,
549 sql,
550 watch_impl::params_named(params),
551 watch_impl::tables_from_hint(tables),
552 )
553 }
554 fn ctx_watch_optional_named<T: FromRow + Clone + Send + 'static>(
555 &self,
556 sql: &'static str,
557 params: Result<Vec<(String, rusqlite::types::Value)>>,
558 tables: &[&str],
559 ) -> crate::live::LiveQuery<Option<T>> {
560 watch_impl::watch_optional(
561 self.inner,
562 sql,
563 watch_impl::params_named(params),
564 watch_impl::tables_from_hint(tables),
565 )
566 }
567 fn ctx_watch_scalar_named<T: FromSql + Clone + Send + 'static>(
568 &self,
569 sql: &'static str,
570 params: Result<Vec<(String, rusqlite::types::Value)>>,
571 tables: &[&str],
572 ) -> crate::live::LiveQuery<T> {
573 watch_impl::watch_scalar(
574 self.inner,
575 sql,
576 watch_impl::params_named(params),
577 watch_impl::tables_from_hint(tables),
578 )
579 }
580}
581
582#[cfg(feature = "live")]
583impl WatchContext for Tx<'_> {
584 fn ctx_watch_all_named<T: FromRow + Clone + Send + 'static>(
586 &self,
587 sql: &'static str,
588 _params: Result<Vec<(String, rusqlite::types::Value)>>,
589 _tables: &[&str],
590 ) -> crate::live::LiveQuery<Vec<T>> {
591 watch_impl::watch_all(
592 self.inner,
593 sql,
594 Err(Error::Config(
595 "트랜잭션 컨텍스트에서는 라이브 쿼리를 만들 수 없습니다".into(),
596 )),
597 Some(std::collections::HashSet::new()),
598 )
599 }
600 fn ctx_watch_optional_named<T: FromRow + Clone + Send + 'static>(
601 &self,
602 sql: &'static str,
603 _params: Result<Vec<(String, rusqlite::types::Value)>>,
604 _tables: &[&str],
605 ) -> crate::live::LiveQuery<Option<T>> {
606 watch_impl::watch_optional(
607 self.inner,
608 sql,
609 Err(Error::Config(
610 "트랜잭션 컨텍스트에서는 라이브 쿼리를 만들 수 없습니다".into(),
611 )),
612 Some(std::collections::HashSet::new()),
613 )
614 }
615 fn ctx_watch_scalar_named<T: FromSql + Clone + Send + 'static>(
616 &self,
617 sql: &'static str,
618 _params: Result<Vec<(String, rusqlite::types::Value)>>,
619 _tables: &[&str],
620 ) -> crate::live::LiveQuery<T> {
621 watch_impl::watch_scalar(
622 self.inner,
623 sql,
624 Err(Error::Config(
625 "트랜잭션 컨텍스트에서는 라이브 쿼리를 만들 수 없습니다".into(),
626 )),
627 Some(std::collections::HashSet::new()),
628 )
629 }
630}
631
632impl SqlContext for SyncHandle<'_> {
633 fn ctx_execute<P: Params>(&self, sql: &str, params: P) -> Result<u64> {
634 let guard = self.inner.pool.connections.acquire()?;
635 #[cfg(feature = "live")]
636 let _hook_capture = self.inner.begin_hook_capture();
637 let out = self
638 .inner
639 .log_query(sql, || on_conn::execute(guard.conn(), sql, params));
640 #[cfg(feature = "live")]
643 self.inner.emit_after_write(sql);
644 out
645 }
646 fn ctx_insert<P: Params>(&self, sql: &str, params: P) -> Result<i64> {
647 let guard = self.inner.pool.connections.acquire()?;
648 #[cfg(feature = "live")]
649 let _hook_capture = self.inner.begin_hook_capture();
650 let out = self
651 .inner
652 .log_query(sql, || on_conn::insert(guard.conn(), sql, params));
653 #[cfg(feature = "live")]
655 self.inner.emit_after_write(sql);
656 out
657 }
658 fn ctx_query_all<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Vec<T>> {
659 let guard = self.inner.pool.connections.acquire()?;
660 #[cfg(feature = "live")]
661 let _hook_capture = self.inner.begin_hook_capture();
662 let out = self
663 .inner
664 .log_query(sql, || on_conn::query_all(guard.conn(), sql, params));
665 #[cfg(feature = "live")]
666 self.inner.emit_after_write(sql);
667 out
668 }
669 fn ctx_query_optional<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Option<T>> {
670 let guard = self.inner.pool.connections.acquire()?;
671 #[cfg(feature = "live")]
672 let _hook_capture = self.inner.begin_hook_capture();
673 let out = self
674 .inner
675 .log_query(sql, || on_conn::query_optional(guard.conn(), sql, params));
676 #[cfg(feature = "live")]
677 self.inner.emit_after_write(sql);
678 out
679 }
680 fn ctx_query_scalar<T: FromSql, P: Params>(&self, sql: &str, params: P) -> Result<T> {
681 let guard = self.inner.pool.connections.acquire()?;
682 #[cfg(feature = "live")]
683 let _hook_capture = self.inner.begin_hook_capture();
684 let out = self
685 .inner
686 .log_query(sql, || on_conn::query_scalar(guard.conn(), sql, params));
687 #[cfg(feature = "live")]
688 self.inner.emit_after_write(sql);
689 out
690 }
691 fn ctx_transaction<R>(&self, f: impl FnOnce(&Tx<'_>) -> Result<R>) -> Result<R> {
693 let tx = self.begin()?;
694 match f(&tx) {
695 Ok(v) => {
696 tx.commit()?;
697 Ok(v)
698 }
699 Err(e) => {
700 let _ = tx.rollback();
702 Err(e)
703 }
704 }
705 }
706}
707
708pub struct Tx<'db> {
719 inner: &'db Arc<DatabaseInner>,
720 guard: crate::pool::ConnectionGuard<'db>,
721 open: bool,
722 sp_depth: std::cell::Cell<u32>,
724 #[cfg(feature = "live")]
727 pending: std::sync::Mutex<Vec<TxPending>>,
728}
729
730#[cfg(feature = "live")]
732#[derive(Default)]
733struct TxPending {
734 all: bool,
736 tables: std::collections::HashSet<String>,
737 changes: Vec<crate::live::TableChange>,
738}
739
740#[cfg(feature = "live")]
741impl Tx<'_> {
742 fn collect_write(&self, sql: &str) {
745 let capture = self.inner.take_hook_capture();
746 let mut levels = self
747 .pending
748 .lock()
749 .unwrap_or_else(std::sync::PoisonError::into_inner);
750 let Some(p) = levels.last_mut() else { return };
752 p.tables.extend(capture.tables);
753 p.changes.extend(capture.changes);
754 match crate::live::extract_write_tables(sql) {
755 crate::live::WriteTables::ReadOnly => {}
756 crate::live::WriteTables::Tables(t) => p.tables.extend(t),
757 crate::live::WriteTables::Unknown => p.all = true,
758 }
759 }
760
761 fn collect_write_all(&self) {
764 let capture = self.inner.take_hook_capture();
765 let mut levels = self
766 .pending
767 .lock()
768 .unwrap_or_else(std::sync::PoisonError::into_inner);
769 let Some(p) = levels.last_mut() else { return };
770 p.tables.extend(capture.tables);
771 p.changes.extend(capture.changes);
772 p.all = true;
773 }
774
775 fn emit_pending(&self) {
777 let levels = std::mem::take(
778 &mut *self
779 .pending
780 .lock()
781 .unwrap_or_else(std::sync::PoisonError::into_inner),
782 );
783 let mut all = false;
784 let mut tables = std::collections::HashSet::new();
785 let mut changes = Vec::new();
786 for l in levels {
787 all |= l.all;
788 tables.extend(l.tables);
789 changes.extend(l.changes);
790 }
791 if all {
792 self.inner.tracker.invalidate(None);
793 } else {
794 let changed_tables: std::collections::HashSet<String> = changes
795 .iter()
796 .map(|change: &crate::live::TableChange| change.table.clone())
797 .collect();
798 if !changes.is_empty() {
799 self.inner.tracker.invalidate_changes(changes);
800 }
801 tables.retain(|table| !changed_tables.contains(table));
802 if !tables.is_empty() {
803 self.inner.tracker.invalidate(Some(tables));
804 }
805 }
806 }
807}
808
809impl Tx<'_> {
810 pub(crate) fn raw_conn(&self) -> &Connection {
812 self.guard.conn()
813 }
814
815 pub fn commit(mut self) -> Result<()> {
817 self.guard.conn().execute_batch("COMMIT")?;
818 log::debug!("transaction commit");
819 self.open = false;
820 #[cfg(feature = "live")]
821 self.emit_pending();
822 Ok(())
823 }
824
825 pub fn rollback(mut self) -> Result<()> {
827 self.guard.conn().execute_batch("ROLLBACK")?;
828 log::debug!("transaction rollback");
829 self.open = false;
830 Ok(())
831 }
832
833 pub fn execute<P: Params>(&self, sql: &str, params: P) -> Result<u64> {
835 self.ctx_execute(sql, params)
836 }
837
838 pub fn query_one<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<T> {
840 self.ctx_query_one(sql, params)
841 }
842
843 pub fn query_optional<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Option<T>> {
845 self.ctx_query_optional(sql, params)
846 }
847
848 pub fn query_scalar<T: FromSql, P: Params>(&self, sql: &str, params: P) -> Result<T> {
850 self.ctx_query_scalar(sql, params)
851 }
852
853 pub fn query_all<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Vec<T>> {
855 self.ctx_query_all(sql, params)
856 }
857
858 pub fn execute_batch(&self, sql: &str) -> Result<()> {
865 #[cfg(feature = "live")]
866 let _hook_capture = self.inner.begin_hook_capture();
867 let r = self.raw_conn().execute_batch(sql);
868 #[cfg(feature = "live")]
869 match &r {
870 Ok(()) => self.collect_write(sql),
872 Err(_) => self.collect_write_all(),
875 }
876 r?;
877 Ok(())
878 }
879
880 pub fn savepoint<R>(&self, f: impl FnOnce(&Tx<'_>) -> Result<R>) -> Result<R> {
884 let depth = self.sp_depth.get();
885 let name = format!("roomrs_sp_{depth}");
886 self.guard
887 .conn()
888 .execute_batch(&format!("SAVEPOINT {name}"))?;
889 self.sp_depth.set(depth + 1);
890 #[cfg(feature = "live")]
892 self.pending
893 .lock()
894 .unwrap_or_else(std::sync::PoisonError::into_inner)
895 .push(TxPending::default());
896
897 let out = f(self);
898
899 self.sp_depth.set(depth);
900 #[cfg(feature = "live")]
902 let level = self
903 .pending
904 .lock()
905 .unwrap_or_else(std::sync::PoisonError::into_inner)
906 .pop()
907 .unwrap_or_default();
908 match out {
909 Ok(v) => {
910 #[cfg(feature = "live")]
912 {
913 let mut levels = self
914 .pending
915 .lock()
916 .unwrap_or_else(std::sync::PoisonError::into_inner);
917 if let Some(parent) = levels.last_mut() {
918 parent.all |= level.all;
919 parent.tables.extend(level.tables);
920 parent.changes.extend(level.changes);
921 }
922 }
923 self.guard
924 .conn()
925 .execute_batch(&format!("RELEASE {name}"))?;
926 Ok(v)
927 }
928 Err(e) => {
929 if let Err(re) = self
932 .guard
933 .conn()
934 .execute_batch(&format!("ROLLBACK TO {name}; RELEASE {name}"))
935 {
936 log::error!("savepoint rollback failed: {re}");
937 }
938 Err(e)
939 }
940 }
941 }
942}
943
944impl SqlContext for Tx<'_> {
945 fn ctx_execute<P: Params>(&self, sql: &str, params: P) -> Result<u64> {
946 #[cfg(feature = "live")]
947 let _hook_capture = self.inner.begin_hook_capture();
948 let out = self
949 .inner
950 .log_query(sql, || on_conn::execute(self.guard.conn(), sql, params));
951 #[cfg(feature = "live")]
954 self.collect_write(sql);
955 out
956 }
957 fn ctx_insert<P: Params>(&self, sql: &str, params: P) -> Result<i64> {
958 #[cfg(feature = "live")]
959 let _hook_capture = self.inner.begin_hook_capture();
960 let out = self
961 .inner
962 .log_query(sql, || on_conn::insert(self.guard.conn(), sql, params));
963 #[cfg(feature = "live")]
965 self.collect_write(sql);
966 out
967 }
968 fn ctx_query_all<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Vec<T>> {
969 #[cfg(feature = "live")]
973 let _hook_capture = self.inner.begin_hook_capture();
974 let out = self
975 .inner
976 .log_query(sql, || on_conn::query_all(self.guard.conn(), sql, params));
977 #[cfg(feature = "live")]
978 self.collect_write(sql);
979 out
980 }
981 fn ctx_query_optional<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Option<T>> {
982 #[cfg(feature = "live")]
985 let _hook_capture = self.inner.begin_hook_capture();
986 let out = self.inner.log_query(sql, || {
987 on_conn::query_optional(self.guard.conn(), sql, params)
988 });
989 #[cfg(feature = "live")]
990 self.collect_write(sql);
991 out
992 }
993 fn ctx_query_scalar<T: FromSql, P: Params>(&self, sql: &str, params: P) -> Result<T> {
994 #[cfg(feature = "live")]
997 let _hook_capture = self.inner.begin_hook_capture();
998 let out = self.inner.log_query(sql, || {
999 on_conn::query_scalar(self.guard.conn(), sql, params)
1000 });
1001 #[cfg(feature = "live")]
1002 self.collect_write(sql);
1003 out
1004 }
1005 fn ctx_transaction<R>(&self, f: impl FnOnce(&Tx<'_>) -> Result<R>) -> Result<R> {
1007 self.savepoint(f)
1008 }
1009}
1010
1011impl Drop for Tx<'_> {
1012 fn drop(&mut self) {
1014 if self.open {
1015 log::debug!("transaction rollback (uncommitted drop)");
1016 let _ = self.guard.conn().execute_batch("ROLLBACK");
1017 }
1018 }
1019}