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}