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}