Skip to main content

rust_query/
lib.rs

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