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