Skip to main content

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