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