rust_query/transaction.rs
1use std::{
2 cell::RefCell, convert::Infallible, marker::PhantomData, rc::Rc, sync::atomic::AtomicI64,
3};
4
5use rusqlite::ErrorCode;
6use self_cell::{MutBorrow, self_cell};
7
8use crate::{
9 IntoExpr, IntoSelect, Table, TableRow,
10 error::FromConflict,
11 lower::{
12 self, emit,
13 list_writer::{Alias, ListWriter},
14 ord_rc::OrdRc,
15 },
16 migrate::{Schema, check_schema, schema_version, user_version},
17 migration::Config,
18 mutable::Mutable,
19 pool::Pool,
20 private::{IntoJoinable, Reader},
21 query::{OwnedRows, Query, track_stmt},
22 rows::Rows,
23 value::{DbTyp, OptTable},
24};
25
26/// [Database] is a proof that the database has been configured.
27///
28/// Creating a [Database] requires going through the steps to migrate an existing database to
29/// the required schema, or creating a new database from scratch (See also [crate::migration::Config]).
30/// Please see [Database::migrator] to get started.
31///
32/// Having done the setup to create a compatible database is sadly not a guarantee that the
33/// database will stay compatible for the lifetime of the [Database] struct.
34/// That is why [Database] also stores the `schema_version`. This allows detecting non-malicious
35/// modifications to the schema and gives us the ability to panic when this is detected.
36/// Such non-malicious modification of the schema can happen for example if another [Database]
37/// instance is created with additional migrations (e.g. by another newer instance of your program).
38pub struct Database<S> {
39 pub(crate) pool: Pool,
40 pub(crate) schema_version: AtomicI64,
41 pub(crate) schema: PhantomData<S>,
42 pub(crate) mut_lock: parking_lot::FairMutex<()>,
43}
44
45impl<S: Schema> Database<S> {
46 /// This is a quick way to open a database if you don't care about migration.
47 ///
48 /// Note that this will panic if the schema version doesn't match or when the schema
49 /// itself doesn't match the expected schema.
50 pub fn new(config: Config) -> Self {
51 let Some(m) = Self::migrator(config) else {
52 panic!("schema version {}, but got an older version", S::VERSION)
53 };
54 let Some(m) = m.finish() else {
55 panic!("schema version {}, but got a new version", S::VERSION)
56 };
57 m
58 }
59}
60
61use rusqlite::Connection;
62type RTransaction<'x> = Option<rusqlite::Transaction<'x>>;
63
64self_cell!(
65 pub struct OwnedTransaction {
66 owner: MutBorrow<Connection>,
67
68 #[covariant]
69 dependent: RTransaction,
70 }
71);
72
73/// SAFETY:
74/// `RTransaction: !Send` because it borrows from `Connection` and `Connection: !Sync`.
75/// `OwnedTransaction` can be `Send` because we know that `dependent` is the only
76/// borrow of `owner` and `OwnedTransaction: !Sync` so `dependent` can not be borrowed
77/// from multiple threads.
78unsafe impl Send for OwnedTransaction {}
79assert_not_impl_any! {OwnedTransaction: Sync}
80
81thread_local! {
82 pub(crate) static TXN: RefCell<Option<TransactionWithRows>> = const { RefCell::new(None) };
83}
84
85impl OwnedTransaction {
86 pub(crate) fn get(&self) -> &rusqlite::Transaction<'_> {
87 self.borrow_dependent().as_ref().unwrap()
88 }
89
90 pub(crate) fn with(
91 mut self,
92 f: impl FnOnce(rusqlite::Transaction<'_>),
93 ) -> rusqlite::Connection {
94 self.with_dependent_mut(|_, b| f(b.take().unwrap()));
95 self.into_owner().into_inner()
96 }
97}
98
99type OwnedRowsVec<'x> = slab::Slab<OwnedRows<'x>>;
100self_cell!(
101 pub struct TransactionWithRows {
102 owner: OwnedTransaction,
103
104 #[not_covariant]
105 dependent: OwnedRowsVec,
106 }
107);
108
109impl TransactionWithRows {
110 pub(crate) fn new_empty(txn: OwnedTransaction) -> Self {
111 Self::new(txn, |_| slab::Slab::new())
112 }
113
114 pub(crate) fn get(&self) -> &rusqlite::Transaction<'_> {
115 self.borrow_owner().get()
116 }
117}
118
119impl<S: Send + Sync + Schema> Database<S> {
120 #[doc = include_str!("database/transaction.md")]
121 pub fn transaction<R: Send>(&self, f: impl Send + FnOnce(&'static Transaction<S>) -> R) -> R {
122 let res = std::thread::scope(|scope| scope.spawn(|| self.transaction_local(f)).join());
123 match res {
124 Ok(val) => val,
125 Err(payload) => std::panic::resume_unwind(payload),
126 }
127 }
128
129 /// Same as [Self::transaction], but can only be used on a new thread.
130 pub(crate) fn transaction_local<R>(&self, f: impl FnOnce(&'static Transaction<S>) -> R) -> R {
131 let conn = self.pool.pop();
132
133 let owned = OwnedTransaction::new(MutBorrow::new(conn), |conn| {
134 Some(conn.borrow_mut().transaction().unwrap())
135 });
136
137 let res = f(Transaction::new_checked(owned, &self.schema_version));
138
139 let owned = TXN.take().unwrap().into_owner();
140 self.pool.push(owned.into_owner().into_inner());
141
142 res
143 }
144
145 #[doc = include_str!("database/transaction_mut.md")]
146 pub fn transaction_mut<O: Send, E: Send>(
147 &self,
148 f: impl Send + FnOnce(&'static mut Transaction<S>) -> Result<O, E>,
149 ) -> Result<O, E> {
150 let join_res =
151 std::thread::scope(|scope| scope.spawn(|| self.transaction_mut_local(f)).join());
152
153 match join_res {
154 Ok(val) => val,
155 Err(payload) => std::panic::resume_unwind(payload),
156 }
157 }
158
159 pub(crate) fn transaction_mut_local<O, E>(
160 &self,
161 f: impl FnOnce(&'static mut Transaction<S>) -> Result<O, E>,
162 ) -> Result<O, E> {
163 // Acquire the lock before creating the connection.
164 // Technically we can acquire the lock later, but we don't want to waste
165 // file descriptors on transactions that need to wait anyway.
166 let guard = self.mut_lock.lock();
167
168 let conn = self.pool.pop();
169
170 let owned = OwnedTransaction::new(MutBorrow::new(conn), |conn| {
171 let txn = conn
172 .borrow_mut()
173 .transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)
174 .unwrap();
175 Some(txn)
176 });
177 // if this panics then the transaction is rolled back and the guard is dropped.
178 let res = f(Transaction::new_checked(owned, &self.schema_version));
179
180 // Drop the guard before commiting to let sqlite go to the next transaction
181 // more quickly while guaranteeing that the database will unlock soon.
182 drop(guard);
183
184 let owned = TXN.take().unwrap().into_owner();
185
186 let conn = if res.is_ok() {
187 owned.with(|x| x.commit().unwrap())
188 } else {
189 owned.with(|x| x.rollback().unwrap())
190 };
191 self.pool.push(conn);
192
193 res
194 }
195
196 #[doc = include_str!("database/transaction_mut_ok.md")]
197 pub fn transaction_mut_ok<R: Send>(
198 &self,
199 f: impl Send + FnOnce(&'static mut Transaction<S>) -> R,
200 ) -> R {
201 self.transaction_mut(|txn| Ok::<R, Infallible>(f(txn)))
202 .unwrap()
203 }
204
205 /// Create a new [rusqlite::Connection] to the database.
206 ///
207 /// You can do (almost) anything you want with this connection as it is almost completely isolated from all other
208 /// [rust_query] connections. The only thing you should not do here is changing the schema.
209 /// Schema changes are detected with the `schema_version` pragma and will result in a panic when creating a new
210 /// [rust_query] transaction.
211 ///
212 /// The `foreign_keys` pragma is always enabled here, even if [crate::migration::ForeignKeys::SQLite] is not used.
213 ///
214 /// Note that many systems have a limit on the number of file descriptors that can
215 /// exist in a single process. On my machine the soft limit is (1024) by default.
216 /// If this limit is reached, it may cause a panic in this method.
217 pub fn rusqlite_connection(&self) -> rusqlite::Connection {
218 let conn = self.pool.pop();
219 conn.pragma_update(None, "foreign_keys", "ON").unwrap();
220 conn
221 }
222}
223
224/// [Transaction] can be used to query and update the database.
225///
226/// From the perspective of a [Transaction] each other [Transaction] is fully applied or not at all.
227/// Futhermore, the effects of [Transaction]s have a global order.
228/// So if we have mutations `A` and then `B`, it is impossible for a [Transaction] to see the effect of `B` without seeing the effect of `A`.
229pub struct Transaction<S> {
230 pub(crate) _p2: PhantomData<S>,
231 pub(crate) _local: PhantomData<*const ()>,
232}
233
234impl<S> Transaction<S> {
235 pub(crate) fn new() -> Self {
236 Self {
237 _p2: PhantomData,
238 _local: PhantomData,
239 }
240 }
241
242 pub(crate) fn copy(&self) -> Self {
243 Self::new()
244 }
245
246 pub(crate) fn new_ref() -> &'static mut Self {
247 // no memory is leaked because Self is zero sized
248 Box::leak(Box::new(Self::new()))
249 }
250}
251
252impl<S: Schema> Transaction<S> {
253 /// This will check the schema version and panic if it is not as expected
254 pub(crate) fn new_checked(txn: OwnedTransaction, expected: &AtomicI64) -> &'static mut Self {
255 let schema_version = schema_version(txn.get());
256 // If the schema version is not the expected version then we
257 // check if the changes are acceptable.
258 if schema_version != expected.load(std::sync::atomic::Ordering::Relaxed) {
259 if user_version(txn.get()).unwrap() != S::VERSION {
260 panic!("The database user_version changed unexpectedly")
261 }
262
263 TXN.set(Some(TransactionWithRows::new_empty(txn)));
264 check_schema::<S>(Self::new_ref(), false);
265 expected.store(schema_version, std::sync::atomic::Ordering::Relaxed);
266 } else {
267 TXN.set(Some(TransactionWithRows::new_empty(txn)));
268 }
269
270 const {
271 assert!(size_of::<Self>() == 0);
272 }
273 Self::new_ref()
274 }
275}
276
277impl<S> Transaction<S> {
278 /// Execute a query with multiple results.
279 ///
280 /// ```
281 /// # use rust_query::{private::doctest::*};
282 /// # get_txn(|txn| {
283 /// let user_names = txn.query(|rows| {
284 /// let user = rows.join(User);
285 /// rows.into_vec(&user.name)
286 /// });
287 /// assert_eq!(user_names, vec!["Alice".to_owned()]);
288 /// # });
289 /// ```
290 pub fn query<'t, R>(&'t self, f: impl FnOnce(&mut Query<'t, '_, S>) -> R) -> R {
291 // Execution already happens in a [Transaction].
292 // and thus any [TransactionMut] that it might be borrowed
293 // from is borrowed immutably, which means the rows can not change.
294
295 let q = Rows {
296 phantom: PhantomData,
297 ast: Default::default(),
298 _p: PhantomData,
299 };
300 f(&mut Query {
301 q,
302 phantom: PhantomData,
303 })
304 }
305
306 /// Retrieve a single result from the database.
307 ///
308 /// ```
309 /// # use rust_query::{private::doctest::*, IntoExpr};
310 /// # rust_query::private::doctest::get_txn(|txn| {
311 /// let res = txn.query_one("test".into_expr());
312 /// assert_eq!(res, "test");
313 /// # });
314 /// ```
315 ///
316 /// Instead of using [Self::query_one] in a loop, it is better to
317 /// call [Self::query] and return all results at once.
318 pub fn query_one<O: 'static>(&self, val: impl IntoSelect<'static, S, Out = O>) -> O {
319 let mut query = self.query(|e| e.into_iter(val.into_select()));
320 let res = query.next().unwrap();
321 debug_assert!(query.next().is_none(), "query should return one row");
322 res
323 }
324
325 /// Retrieve a [crate::Lazy] value from the database.
326 ///
327 /// This is very similar to [Self::query_one], except that it retrieves
328 /// [crate::Lazy] instead of [TableRow]. As such it only works with
329 /// table valued [rust_query::Expr].
330 ///
331 /// ```
332 /// # #[rust_query::migration::schema(M)]
333 /// # pub mod vN {
334 /// # pub struct Author {
335 /// # pub name: String,
336 /// # }
337 /// # pub struct Page {
338 /// # pub content: String,
339 /// # pub title: String,
340 /// # pub author: rust_query::TableRow<Author>,
341 /// # }
342 /// # }
343 /// # use v0::*;
344 /// # rust_query::Database::new(rust_query::migration::Config::open_in_memory()).transaction_mut_ok(|txn| {
345 /// let cat = txn.insert_ok(Author {
346 /// name: "Cat".to_owned()
347 /// });
348 /// let blog_post = txn.insert_ok(Page {
349 /// content: "Hello world!".to_owned(),
350 /// title: "Hi".to_owned(),
351 /// author: cat,
352 /// });
353 /// let lazy_post = txn.lazy(blog_post);
354 ///
355 /// println!("{}:", lazy_post.title);
356 /// println!("{}", lazy_post.content);
357 /// println!("written by: {}", lazy_post.author.name);
358 /// # });
359 /// ```
360 pub fn lazy<'t, T: OptTable<Schema = S>>(
361 &'t self,
362 val: impl IntoExpr<'static, S, Typ = T>,
363 ) -> T::Lazy<'t> {
364 T::out_to_lazy(self.query_one(val.into_expr()))
365 }
366
367 /// This retrieves an iterator of [crate::Lazy] values.
368 ///
369 /// Refer to [Rows::join] for the kind of the parameter that is supported here.
370 /// Refer to [Transaction::lazy] for the single row version.
371 pub fn lazy_iter<'t, T: Table<Schema = S>>(
372 &'t self,
373 val: impl IntoJoinable<'static, S, Typ = TableRow<T>>,
374 ) -> LazyIter<'t, T> {
375 let val = val.into_joinable();
376 self.query(|rows| {
377 let table = rows.join(val);
378 LazyIter {
379 txn: self,
380 iter: rows.into_iter(table),
381 }
382 })
383 }
384
385 /// Retrieves a [Mutable] row from the database.
386 ///
387 /// The [Transaction] is borrowed mutably until the [Mutable] is dropped.
388 /// It is recommended to keep the lifetime of [Mutable] short, to prevent borrow checker errors.
389 /// See below for some good examples of how to use [Transaction::mutable].
390 ///
391 /// ```
392 /// # #[rust_query::migration::schema(M)]
393 /// # pub mod vN {
394 /// # pub struct Player {
395 /// # #[unique]
396 /// # pub number: i64,
397 /// # #[unique]
398 /// # pub name: String,
399 /// # pub score: i64,
400 /// # }
401 /// # }
402 /// # use v0::*;
403 /// # rust_query::Database::new(rust_query::migration::Config::open_in_memory()).transaction_mut_ok(|txn| {
404 /// # txn.insert(Player {number: 5, name: "Floris".to_owned(), score: 0});
405 /// if let Some(mut player) = txn.mutable(Player.number(1)) {
406 /// player.score += 100;
407 /// }
408 ///
409 /// let floris = txn.query_one(Player.name("Floris")).unwrap();
410 /// txn.mutable(floris).score += 50;
411 /// # });
412 /// ```
413 pub fn mutable<'t, T: OptTable<Schema = S>>(
414 &'t mut self,
415 val: impl IntoExpr<'static, S, Typ = T>,
416 ) -> T::Mutable<'t> {
417 let x = self.query_one(T::select_opt_mutable(val.into_expr()));
418 T::into_mutable(x)
419 }
420
421 /// Retrieve multiple [Mutable] rows from the database.
422 ///
423 /// Refer to [Rows::join] for the kind of the parameter that is supported here.
424 /// This may be useful when you need mutable access to multiple rows (potentially at the same time).
425 ///
426 /// ```
427 /// # #[rust_query::migration::schema(M)]
428 /// # pub mod vN {
429 /// # pub struct User { pub age: i64 }
430 /// # }
431 /// # use v0::*;
432 /// # rust_query::Database::new(rust_query::migration::Config::open_in_memory()).transaction_mut_ok(|txn| {
433 /// # txn.insert_ok(User {age: 30});
434 /// for mut user in txn.mutable_vec(User) {
435 /// user.age += 1;
436 /// }
437 /// # });
438 /// ```
439 pub fn mutable_vec<'t, T: Table<Schema = S>>(
440 &'t mut self,
441 val: impl IntoJoinable<'static, S, Typ = TableRow<T>>,
442 ) -> Vec<Mutable<'t, T>> {
443 let val = val.into_joinable();
444 self.query(|rows| {
445 let val = rows.join(val);
446 rows.into_vec((T::into_select(val.clone()), val))
447 .into_iter()
448 .map(TableRow::<T>::into_mutable)
449 .collect()
450 })
451 }
452}
453
454pub struct LazyIter<'t, T: Table> {
455 txn: &'t Transaction<T::Schema>,
456 iter: crate::query::Iter<'t, TableRow<T>>,
457}
458
459impl<'t, T: Table> Iterator for LazyIter<'t, T> {
460 type Item = crate::Lazy<'t, T>;
461
462 fn next(&mut self) -> Option<Self::Item> {
463 self.iter.next().map(|x| self.txn.lazy(x))
464 }
465}
466
467impl<S: 'static> Transaction<S> {
468 /// Try inserting a value into the database.
469 ///
470 /// Returns [Ok] with a reference to the new inserted value or an [Err] with conflict information.
471 /// The type of conflict information depends on the number of unique constraints on the table:
472 /// - 0 unique constraints => [Infallible]
473 /// - 1 unique constraint => [TableRow] reference to the conflicting table row.
474 /// - 2+ unique constraints => [crate::Conflict].
475 ///
476 /// ```
477 /// # use rust_query::{private::doctest::*, IntoExpr};
478 /// # rust_query::private::doctest::get_txn(|mut txn| {
479 /// let res = txn.insert(User {
480 /// name: "Bob".to_owned(),
481 /// });
482 /// assert!(res.is_ok());
483 /// let res = txn.insert(User {
484 /// name: "Bob".to_owned(),
485 /// });
486 /// assert!(res.is_err(), "there is a unique constraint on the name");
487 /// # });
488 /// ```
489 pub fn insert<T: Table<Schema = S>>(&mut self, val: T) -> Result<TableRow<T>, T::Conflict> {
490 try_insert_private(lower::JoinableTable::Table(T::NAME), None, val)
491 }
492
493 /// This is a convenience function to make using [Transaction::insert]
494 /// easier for tables without unique constraints.
495 ///
496 /// The new row is added to the table and the row reference is returned.
497 pub fn insert_ok<T: Table<Schema = S, Conflict = Infallible>>(
498 &mut self,
499 val: T,
500 ) -> TableRow<T> {
501 let Ok(row) = self.insert(val);
502 row
503 }
504
505 /// This is a convenience function to make using [Transaction::insert]
506 /// easier for tables with exactly one unique constraints.
507 ///
508 /// The new row is inserted and the reference to the row is returned OR
509 /// an existing row is found which conflicts with the new row and a reference
510 /// to the conflicting row is returned.
511 ///
512 /// ```
513 /// # use rust_query::{private::doctest::*, IntoExpr};
514 /// # rust_query::private::doctest::get_txn(|mut txn| {
515 /// let bob = txn.insert(User {
516 /// name: "Bob".to_owned(),
517 /// }).unwrap();
518 /// let bob2 = txn.find_or_insert(User {
519 /// name: "Bob".to_owned(), // this will conflict with the existing row.
520 /// });
521 /// assert_eq!(bob, bob2);
522 /// # });
523 /// ```
524 pub fn find_or_insert<T: Table<Schema = S, Conflict = TableRow<T>>>(
525 &mut self,
526 val: T,
527 ) -> TableRow<T> {
528 match self.insert(val) {
529 Ok(row) => row,
530 Err(row) => row,
531 }
532 }
533
534 pub(crate) fn update<T: Table<Schema = S>>(
535 &mut self,
536 row: TableRow<T>,
537 val: T::Mutable,
538 ) -> Result<(), T::Conflict> {
539 let val = T::mutable_into_insert(val);
540 let mut reader = Reader::default();
541 T::read(&val, &mut reader);
542
543 let mut stmt = emit::Stmt::default();
544 stmt.write("UPDATE ");
545 lower::JoinableTable::Table(T::NAME).emit(&mut stmt);
546
547 stmt.write(" SET ");
548 let mut list = ListWriter::new(&mut stmt, ", ");
549 for (key, val) in &reader.builder {
550 list.item()
551 .write(format_args!("{} = ", Alias(key)))
552 .write_param(&val);
553 }
554 list.default(format_args!("{1} = {0}.{1}", Alias(T::NAME), Alias(T::ID)));
555
556 stmt.write(format_args!(
557 " WHERE {}.{} = ",
558 Alias(T::NAME),
559 Alias(T::ID)
560 ));
561 stmt.write_param(&OrdRc(Rc::new(row.inner.idx.into())));
562
563 let res = TXN.with_borrow(|txn| {
564 let txn = txn.as_ref().unwrap().get();
565
566 let mut cached = txn.prepare_cached(&stmt.sql).unwrap();
567 cached.execute(rusqlite::params_from_iter(stmt.params))
568 });
569
570 match res {
571 Ok(1) => Ok(()),
572 Ok(n) => panic!("unexpected number of updates: {n}"),
573 Err(rusqlite::Error::SqliteFailure(kind, Some(msg)))
574 if kind.code == ErrorCode::ConstraintViolation =>
575 {
576 // `msg` looks like "UNIQUE constraint failed: playlist_track.playlist, playlist_track.track"
577 let res = TXN.with_borrow(|txn| {
578 let txn = txn.as_ref().unwrap().get();
579 <T::Conflict as FromConflict>::from_conflict(
580 txn,
581 lower::JoinableTable::Table(T::NAME),
582 reader.builder,
583 msg,
584 )
585 });
586 Err(res)
587 }
588 Err(err) => panic!("{err:?}"),
589 }
590 }
591
592 /// Convert the [Transaction] into a [TransactionWeak] to allow deletions.
593 pub fn downgrade(&'static mut self) -> &'static mut TransactionWeak<S> {
594 // TODO: clean this up
595 Box::leak(Box::new(TransactionWeak { inner: PhantomData }))
596 }
597}
598
599/// This is the weak version of [Transaction].
600///
601/// The reason that it is called `weak` is because [TransactionWeak] can not guarantee
602/// that [TableRow]s prove the existence of their particular row.
603///
604/// [TransactionWeak] is useful because it allowes deleting rows.
605pub struct TransactionWeak<S> {
606 inner: PhantomData<Transaction<S>>,
607}
608
609impl<S: Schema> TransactionWeak<S> {
610 /// Try to delete a row from the database.
611 ///
612 /// This will return an [Err] if there is a row that references the row that is being deleted.
613 /// When this method returns [Ok] it will contain a [bool] that is either
614 /// - `true` if the row was just deleted.
615 /// - `false` if the row was deleted previously in this transaction.
616 pub fn delete<T: Table<Schema = S>>(&mut self, val: TableRow<T>) -> Result<bool, T::Referer> {
617 let schema = crate::schema::from_macro::Schema::new::<S>();
618
619 // This is a manual check that foreign key constraints are not violated.
620 // We do this manually because we don't want to enabled foreign key constraints for the whole
621 // transaction (and is not possible to enable for part of a transaction).
622 let mut checks = vec![];
623 for (&table_name, table) in &schema.tables {
624 for col in table.columns.iter().filter_map(|(col_name, col)| {
625 let col = &col.def;
626 col.fk
627 .as_ref()
628 .is_some_and(|(t, c)| t == T::NAME && c == T::ID)
629 .then_some(col_name)
630 }) {
631 let mut stmt = emit::Stmt::default();
632 stmt.write("SELECT ");
633 stmt.write_param(&OrdRc(Rc::new(val.inner.idx.into())));
634 stmt.write(format_args!(
635 " IN (SELECT {0}.{1} FROM {0})",
636 Alias(table_name),
637 Alias(col)
638 ));
639 checks.push(stmt);
640 }
641 }
642
643 let mut stmt = emit::Stmt::default();
644 stmt.write(format_args!(
645 "DELETE FROM {0} WHERE {0}.{1} = ",
646 Alias(T::NAME),
647 Alias(T::ID)
648 ));
649 stmt.write_param(&OrdRc::new(val.inner.idx));
650
651 TXN.with_borrow(|txn| {
652 let txn = txn.as_ref().unwrap().get();
653
654 for stmt in checks {
655 let mut cached = txn.prepare_cached(&stmt.sql).unwrap();
656 match cached.query_one(rusqlite::params_from_iter(stmt.params), |r| r.get(0)) {
657 Ok(true) => return Err(T::get_referer_unchecked()),
658 Ok(false) => {}
659 Err(err) => panic!("{err:?}"),
660 }
661 }
662
663 let mut cached = txn.prepare_cached(&stmt.sql).unwrap();
664 match cached.execute(rusqlite::params_from_iter(stmt.params)) {
665 Ok(0) => Ok(false),
666 Ok(1) => Ok(true),
667 Ok(n) => {
668 panic!("unexpected number of deletes {n}")
669 }
670 Err(err) => panic!("{err:?}"),
671 }
672 })
673 }
674
675 /// Delete a row from the database.
676 ///
677 /// This is the infallible version of [TransactionWeak::delete].
678 ///
679 /// To be able to use this method you have to mark the table as `#[no_reference]` in the schema.
680 pub fn delete_ok<T: Table<Referer = Infallible, Schema = S>>(
681 &mut self,
682 val: TableRow<T>,
683 ) -> bool {
684 let Ok(res) = self.delete(val);
685 res
686 }
687
688 /// This allows you to do (almost) anything you want with the internal [rusqlite::Transaction].
689 ///
690 /// Note that there are some things that you should not do with the transaction, such as:
691 /// - Changes to the schema, these will result in a panic as described in [Database].
692 /// - Making changes that violate foreign-key constraints (see below).
693 ///
694 /// Sadly it is not possible to enable (or disable) the `foreign_keys` pragma during a transaction.
695 /// This means that whether this pragma is enabled depends on which [crate::migration::ForeignKeys]
696 /// option is used and can not be changed.
697 pub fn rusqlite_transaction<R>(&mut self, f: impl FnOnce(&rusqlite::Transaction) -> R) -> R {
698 TXN.with_borrow(|txn| f(txn.as_ref().unwrap().get()))
699 }
700}
701
702pub fn try_insert_private<T: Table>(
703 table: lower::JoinableTable,
704 idx: Option<i64>,
705 val: T,
706) -> Result<TableRow<T>, T::Conflict> {
707 let mut reader = Reader::default();
708 T::read(&val, &mut reader);
709 if let Some(idx) = idx {
710 reader.col::<i64>(T::ID, idx);
711 }
712
713 let mut stmt = emit::Stmt::default();
714 stmt.write("INSERT INTO ");
715 table.emit(&mut stmt);
716
717 if reader.builder.is_empty() {
718 // values always has at least one column, so we leave it out when there are no columns
719 stmt.write(" DEFAULT VALUES");
720 } else {
721 let (col_names, col_exprs): (Vec<_>, Vec<_>) = reader.builder.clone().into_iter().collect();
722
723 stmt.write(" (");
724 let mut list = ListWriter::new(&mut stmt, ", ");
725 for col in col_names {
726 list.item().write(Alias(col));
727 }
728 stmt.write(") VALUES (");
729 let mut list = ListWriter::new(&mut stmt, ", ");
730 for val in col_exprs {
731 list.item().write_param(&val);
732 }
733 stmt.write(")");
734 }
735 stmt.write(" RETURNING ").write(T::ID);
736
737 let res = TXN.with_borrow(|txn| {
738 let txn = txn.as_ref().unwrap().get();
739 track_stmt(txn, &stmt.sql, &stmt.params);
740
741 let mut statement = txn.prepare_cached(&stmt.sql).unwrap();
742 let mut res = statement
743 .query_map(rusqlite::params_from_iter(stmt.params), |row| {
744 Ok(TableRow::<T>::from_sql(row.get_ref(T::ID)?)?)
745 })
746 .unwrap();
747
748 res.next().unwrap()
749 });
750
751 match res {
752 Ok(id) => {
753 if let Some(idx) = idx {
754 assert_eq!(idx, id.inner.idx);
755 }
756 Ok(id)
757 }
758 Err(rusqlite::Error::SqliteFailure(kind, Some(msg)))
759 if kind.code == ErrorCode::ConstraintViolation =>
760 {
761 // val looks like "UNIQUE constraint failed: playlist_track.playlist, playlist_track.track"
762 let res = TXN.with_borrow(|txn| {
763 let txn = txn.as_ref().unwrap().get();
764 <T::Conflict as FromConflict>::from_conflict(txn, table, reader.builder, msg)
765 });
766 Err(res)
767 }
768 Err(err) => panic!("{err:?}"),
769 }
770}