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