rust_query/
lib.rs

1#![allow(private_bounds, private_interfaces)]
2#![doc = include_str!("../README.md")]
3
4extern crate self as rust_query;
5
6#[macro_use]
7extern crate static_assertions;
8
9mod alias;
10mod ast;
11mod async_db;
12mod db;
13mod joinable;
14mod lazy;
15mod migrate;
16mod mutable;
17#[cfg(feature = "mutants")]
18mod mutants;
19mod mymap;
20mod pool;
21mod query;
22mod rows;
23mod schema;
24mod select;
25mod transaction;
26mod value;
27mod writable;
28
29use alias::JoinableTable;
30use private::Reader;
31use schema::from_macro::TypBuilder;
32use std::ops::Deref;
33use value::MyTyp;
34
35pub use async_db::DatabaseAsync;
36pub use db::TableRow;
37pub use lazy::Lazy;
38pub use mutable::Mutable;
39pub use select::{IntoSelect, Select};
40pub use transaction::{Database, Transaction, TransactionWeak};
41#[expect(deprecated)]
42pub use value::UnixEpoch;
43pub use value::aggregate::aggregate;
44pub use value::trivial::FromExpr;
45pub use value::{Expr, IntoExpr, optional::optional};
46pub use writable::Update;
47
48/// Derive [derive@Select] to create a new `*Select` struct.
49///
50/// This `*Select` struct will implement the [IntoSelect] trait and can be used
51/// with [args::Query::into_iter], [Transaction::query_one] etc.
52///
53/// Usage can also be nested.
54///
55/// ```
56/// #[rust_query::migration::schema(Schema)]
57/// pub mod vN {
58///     pub struct Thing {
59///         pub details: Details,
60///         pub beta: f64,
61///         pub seconds: i64,
62///     }
63///     pub struct Details {
64///         pub name: String,
65///     }
66/// }
67/// use v0::*;
68/// use rust_query::{Table, Select, Transaction};
69///
70/// #[derive(Select)]
71/// struct MyData {
72///     seconds: i64,
73///     is_it_real: bool,
74///     name: String,
75///     other: OtherData
76/// }
77///
78/// #[derive(Select)]
79/// struct OtherData {
80///     alpha: f64,
81///     beta: f64,
82/// }
83///
84/// fn do_query(db: &Transaction<Schema>) -> Vec<MyData> {
85///     db.query(|rows| {
86///         let thing = rows.join(Thing);
87///
88///         rows.into_vec(MyDataSelect {
89///             seconds: &thing.seconds,
90///             is_it_real: thing.seconds.lt(100),
91///             name: &thing.details.name,
92///             other: OtherDataSelect {
93///                 alpha: thing.beta.add(2.0),
94///                 beta: &thing.beta,
95///             },
96///         })
97///     })
98/// }
99/// # fn main() {}
100/// ```
101pub use rust_query_macros::Select;
102
103/// Use in combination with `#[rust_query(From = Thing)]` to specify which tables
104/// this struct should implement [trait@FromExpr] for.
105///
106/// The implementation of [trait@FromExpr] will initialize every field from the column with
107/// the corresponding name. It is also possible to change the type of each field
108/// as long as the new field type implements [trait@FromExpr].
109///
110/// ```
111/// # use rust_query::migration::schema;
112/// # use rust_query::{TableRow, FromExpr};
113/// #[schema(Example)]
114/// pub mod vN {
115///     pub struct User {
116///         pub name: String,
117///         pub score: i64,
118///         pub best_game: Option<Game>,
119///     }
120///     pub struct Game;
121/// }
122///
123/// #[derive(FromExpr)]
124/// #[rust_query(From = v0::User)]
125/// struct MyUserFields {
126///     name: String,
127///     best_game: Option<TableRow<v0::Game>>,
128/// }
129/// # fn main() {}
130/// ```
131pub use rust_query_macros::FromExpr;
132
133/// Types that are used as closure arguments.
134///
135/// You generally don't need to import these types.
136pub mod args {
137    pub use crate::query::{OrderBy, Query};
138    pub use crate::rows::Rows;
139    pub use crate::value::aggregate::Aggregate;
140    pub use crate::value::optional::Optional;
141}
142
143/// Types to declare schemas and migrations.
144///
145/// A good starting point is too look at [crate::migration::schema].
146pub mod migration {
147    pub use crate::migrate::{
148        Migrator,
149        config::{Config, ForeignKeys, Synchronous},
150        migration::{Migrated, TransactionMigrate},
151    };
152    #[cfg(feature = "dev")]
153    pub use crate::schema::dev::hash_schema;
154
155    /// Use this macro to define your schema.
156    ///
157    /// The macro must be applied to a module named `vN`. This is because the module
158    /// is a template that can used to generate multiple modules called `v0`, `v1` etc.
159    /// By default, only one module named `v0` is generated.
160    ///
161    /// ```
162    /// #[rust_query::migration::schema(SchemaName)]
163    /// pub mod vN {
164    ///     pub struct TableName {
165    ///         pub column_name: i64,
166    ///     }
167    /// }
168    /// use v0::TableName; // the actual module name is `v0`
169    /// # fn main() {}
170    /// ```
171    ///
172    /// Note that the schema module, table structs and column fields all must be `pub`.
173    /// The `id` column field is currently reserved for internal use and can not be used.
174    ///
175    /// Supported data types are:
176    /// - `i64` (sqlite `integer`)
177    /// - `f64` (sqlite `real`)
178    /// - `String` (sqlite `text`)
179    /// - `Vec<u8>` (sqlite `blob`)
180    /// - `bool` (sqlite `integer` with `CHECK "col" IN (0, 1)`)
181    /// - Any table in the same schema (sqlite `integer` with foreign key constraint)
182    /// - `Option<T>` where `T` is not an `Option` (sqlite nullable)
183    ///
184    /// ## Unique constraints
185    ///
186    /// To define a unique constraint on a column, you need to add an attribute to the table or field.
187    ///
188    /// For example:
189    /// ```
190    /// #[rust_query::migration::schema(SchemaName)]
191    /// pub mod vN {
192    ///     #[unique(username, movie)] // <-- here
193    ///     pub struct Rating {
194    ///         pub movie: String,
195    ///         pub username: String,
196    ///         #[unique] // <-- or here
197    ///         pub uuid: String,
198    ///     }
199    /// }
200    /// ```
201    /// This will create a single schema version with a single table called `rating` and three columns.
202    /// The table will also have two unique contraints (one on the `uuid` column and one on the combination of `username` and `movie`).
203    /// Note that optional types are not allowed in unique constraints.
204    ///
205    /// ## Indexes
206    /// Indexes are very similar to unique constraints, but they don't require the columns to be unique.
207    /// These are useful to prevent sqlite from having to scan a whole table.
208    /// To incentivise creating indices you also get some extra sugar to use the index!
209    ///
210    /// ```
211    /// #[rust_query::migration::schema(SchemaName)]
212    /// pub mod vN {
213    ///     pub struct Topic {
214    ///         #[unique]
215    ///         pub title: String,
216    ///         #[index]
217    ///         pub category: String,
218    ///     }
219    /// }
220    /// fn test(txn: &rust_query::Transaction<v0::SchemaName>) {
221    ///     let _ = txn.lazy_iter(v0::Topic.category("sports"));
222    ///     let _ = txn.lazy(v0::Topic.title("star wars"));
223    /// }
224    /// ```
225    ///
226    /// The `TableName.column_name(value)` syntax is only allowed if `TableName` has an index or
227    /// unique constraint that starts with `column_name`.
228    ///
229    /// Adding and removing indexes and changing the order of columns in indexes and unique constraints
230    /// is considered backwards compatible and thus does not require a new schema version.
231    ///
232    /// # Multiple schema versions
233    ///
234    /// At some point you might want to change something substantial in your schema.
235    /// It would be really sad if you had to throw away all the old data in your database.
236    /// That is why [rust_query] allows us to define multiple schema versions and how to transition between them.
237    ///
238    /// ## Adding tables
239    /// One of the simplest things to do is adding a new table.
240    ///
241    /// ```
242    /// #[rust_query::migration::schema(SchemaName)]
243    /// #[version(0..=1)]
244    /// pub mod vN {
245    ///     pub struct User {
246    ///         #[unique]
247    ///         pub username: String,
248    ///     }
249    ///     #[version(1..)] // <-- note that `Game`` has a version range
250    ///     pub struct Game {
251    ///         #[unique]
252    ///         pub name: String,
253    ///         pub size: i64,
254    ///     }
255    /// }
256    ///
257    /// // These are just examples of tables you can use
258    /// use v0::SchemaName as _;
259    /// use v1::SchemaName as _;
260    /// use v0::User as _;
261    /// use v1::User as _;
262    /// // `v0::Game` does not exist
263    /// use v1::Game as _;
264    /// # fn main() {}
265    ///
266    /// fn migrate() -> rust_query::Database<v1::SchemaName> {
267    ///     rust_query::Database::migrator(rust_query::migration::Config::open("test.db"))
268    ///         .expect("database version is before supported versions")
269    ///         .migrate(|_txn| v0::migrate::SchemaName {})
270    ///         .finish()
271    ///         .expect("database version is after supported versions")
272    /// }
273    /// ```
274    /// The migration itself is not very interesting because new tables are automatically created
275    /// without any data. To have some initial data, take a look at the `#[from]` attribute down below or use
276    /// [crate::migration::Migrator::fixup].
277    ///
278    /// ## Changing columns
279    /// Changing columns is very similar to adding and removing structs.
280    /// ```
281    /// use rust_query::migration::{schema, Config};
282    /// use rust_query::{Database, Lazy};
283    /// #[schema(Schema)]
284    /// #[version(0..=1)]
285    /// pub mod vN {
286    ///     pub struct User {
287    ///         #[unique]
288    ///         pub username: String,
289    ///         #[version(1..)] // <-- here
290    ///         pub score: i64,
291    ///     }
292    /// }
293    /// pub fn migrate() -> Database<v1::Schema> {
294    ///     Database::migrator(Config::open_in_memory()) // we use an in memory database for this test
295    ///         .expect("database version is before supported versions")
296    ///         .migrate(|txn| v0::migrate::Schema {
297    ///             // In this case it is required to provide a value for each row that already exists.
298    ///             // This is done with the `v0::migrate::User` struct:
299    ///             user: txn.migrate_ok(|old: Lazy<v0::User>| v0::migrate::User {
300    ///                 score: old.username.len() as i64 // use the username length as the new score
301    ///             }),
302    ///         })
303    ///         .finish()
304    ///         .expect("database version is after supported versions")
305    /// }
306    /// # fn main() {}
307    /// ```
308    ///
309    /// ## `#[from(TableName)]` Attribute
310    /// You can use this attribute when renaming or splitting a table.
311    /// This will make it clear that data in the table should have the
312    /// same row ids as the `from` table.
313    ///
314    /// For example:
315    ///
316    /// ```
317    /// # use rust_query::migration::{schema, Config};
318    /// # use rust_query::{Database, Lazy};
319    /// # fn main() {}
320    /// #[schema(Schema)]
321    /// #[version(0..=1)]
322    /// pub mod vN {
323    ///     #[version(..1)]
324    ///     pub struct User {
325    ///         pub name: String,
326    ///     }
327    ///     #[version(1..)]
328    ///     #[from(User)]
329    ///     pub struct Author {
330    ///         pub name: String,
331    ///     }
332    ///     pub struct Book {
333    ///         pub author: Author,
334    ///     }
335    /// }
336    /// pub fn migrate() -> Database<v1::Schema> {
337    ///     Database::migrator(Config::open_in_memory()) // we use an in memory database for this test
338    ///         .expect("database version is before supported versions")
339    ///         .migrate(|txn| v0::migrate::Schema {
340    ///             author: txn.migrate_ok(|old: Lazy<v0::User>| v0::migrate::Author {
341    ///                 name: old.name.clone(),
342    ///             }),
343    ///         })
344    ///         .finish()
345    ///         .expect("database version is after supported versions")
346    /// }
347    /// ```
348    /// In this example the `Book` table exists in both `v0` and `v1`,
349    /// however `User` only exists in `v0` and `Author` only exist in `v1`.
350    /// Note that the `pub author: Author` field only specifies the latest version
351    /// of the table, it will use the `#[from]` attribute to find previous versions.
352    ///
353    /// ## `#[no_reference]` Attribute
354    /// You can put this attribute on your table definitions and it will make it impossible
355    /// to have foreign key references to such table.
356    /// This makes it possible to use `TransactionWeak::delete_ok`.
357    pub use rust_query_macros::schema;
358}
359
360/// These items are only exposed for use by the proc macros.
361/// Direct use is unsupported.
362#[doc(hidden)]
363pub mod private {
364    use std::marker::PhantomData;
365
366    pub use crate::joinable::{IntoJoinable, Joinable};
367    pub use crate::migrate::{
368        Schema, SchemaMigration, TableTypBuilder,
369        migration::{Migration, SchemaBuilder},
370    };
371    pub use crate::query::get_plan;
372    pub use crate::schema::from_macro::TypBuilder;
373    pub use crate::value::{
374        DynTypedExpr, MyTyp, Typed, ValueBuilder, adhoc_expr, new_column, unique_from_joinable,
375    };
376    pub use crate::writable::{Reader, TableInsert};
377
378    pub struct Lazy<'t>(PhantomData<&'t ()>);
379    pub struct Ignore;
380    pub struct Custom<T>(PhantomData<T>);
381    pub struct AsUpdate;
382    pub struct AsExpr<'t>(PhantomData<&'t ()>);
383
384    pub trait Apply {
385        type Out<T: MyTyp, S>;
386    }
387
388    impl<'t> Apply for Lazy<'t> {
389        type Out<T: MyTyp, S> = T::Lazy<'t>;
390    }
391
392    impl Apply for Ignore {
393        type Out<T: MyTyp, S> = ();
394    }
395
396    impl<X> Apply for Custom<X> {
397        type Out<T: MyTyp, S> = X;
398    }
399
400    impl Apply for AsUpdate {
401        type Out<T: MyTyp, S> = crate::Update<S, T>;
402    }
403
404    impl<'t> Apply for AsExpr<'t> {
405        type Out<T: MyTyp, S> = crate::Expr<'t, S, T>;
406    }
407
408    pub trait UpdateOrUnit<S, T>: Default {}
409    impl<S, T: MyTyp> UpdateOrUnit<S, T> for crate::Update<S, T> {}
410    impl<S, T> UpdateOrUnit<S, T> for () {}
411
412    pub mod doctest {
413        use crate::{Database, Transaction, migrate::config::Config, migration};
414
415        #[migration::schema(Empty)]
416        pub mod vN {
417            pub struct User {
418                #[unique]
419                pub name: String,
420            }
421        }
422        pub use v0::*;
423
424        #[cfg_attr(test, mutants::skip)] // this function is only used in doc tests
425        pub fn get_txn(f: impl Send + FnOnce(&'static mut Transaction<Empty>)) {
426            let db = Database::new(Config::open_in_memory());
427            db.transaction_mut_ok(|txn| {
428                txn.insert(User { name: "Alice" }).unwrap();
429                f(txn)
430            })
431        }
432    }
433}
434
435/// This trait is implemented for all table types as generated by the [crate::migration::schema] macro.
436///
437/// **You can not implement this trait yourself!**
438pub trait Table: Sized + 'static {
439    #[doc(hidden)]
440    type Ext2<'t>;
441
442    #[doc(hidden)]
443    fn covariant_ext<'x, 't>(val: &'x Self::Ext2<'static>) -> &'x Self::Ext2<'t>;
444
445    #[doc(hidden)]
446    fn build_ext2<'t>(val: &Expr<'t, Self::Schema, Self>) -> Self::Ext2<'t>;
447
448    /// The schema that this table is a part of.
449    type Schema;
450
451    #[doc(hidden)]
452    /// The table that this table can be migrated from.
453    type MigrateFrom: MyTyp;
454
455    /// The type of conflict that can result from inserting a row in this table.
456    /// This is the same type that is used for row updates too.
457    type Conflict;
458
459    /// The type of updates used by [Transaction::update_ok].
460    type UpdateOk;
461    /// The type of updates used by [Transaction::update].
462    type Update;
463    /// The type of error when a delete fails due to a foreign key constraint.
464    type Referer;
465
466    #[doc(hidden)]
467    type Mutable: Deref;
468    #[doc(hidden)]
469    type Lazy<'t>;
470    #[doc(hidden)]
471    type Insert;
472
473    #[doc(hidden)]
474    fn read(val: &Self::Insert, f: &mut Reader<Self::Schema>);
475
476    #[doc(hidden)]
477    fn get_conflict_unchecked(
478        txn: &Transaction<Self::Schema>,
479        val: &Self::Insert,
480    ) -> Self::Conflict;
481
482    #[doc(hidden)]
483    fn select_mutable(val: Expr<'_, Self::Schema, Self>)
484    -> Select<'_, Self::Schema, Self::Mutable>;
485
486    #[doc(hidden)]
487    fn mutable_into_update(val: Self::Mutable) -> Self::Update;
488
489    #[doc(hidden)]
490    fn mutable_as_unique(val: &mut Self::Mutable) -> &mut <Self::Mutable as Deref>::Target;
491
492    #[doc(hidden)]
493    fn update_into_try_update(val: Self::UpdateOk) -> Self::Update;
494
495    #[doc(hidden)]
496    fn apply_try_update(val: Self::Update, old: Expr<'static, Self::Schema, Self>) -> Self::Insert;
497
498    #[doc(hidden)]
499    fn get_referer_unchecked() -> Self::Referer;
500
501    #[doc(hidden)]
502    fn get_lazy<'t>(txn: &'t Transaction<Self::Schema>, row: TableRow<Self>) -> Self::Lazy<'t>;
503
504    #[doc(hidden)]
505    fn typs(f: &mut TypBuilder<Self::Schema>);
506
507    #[doc(hidden)]
508    const SPAN: (usize, usize);
509
510    #[doc(hidden)]
511    const ID: &'static str;
512    #[doc(hidden)]
513    const NAME: &'static str;
514}
515
516trait CustomJoin: Table {
517    fn name(&self) -> JoinableTable;
518}
519
520#[test]
521fn compile_tests() {
522    let t = trybuild::TestCases::new();
523    t.compile_fail("tests/compile/*.rs");
524}