#[schema]Expand description
Use this macro to define your schema.
The macro must be applied to a module named vN. This is because the module
is a template that can used to generate multiple modules called v0, v1 etc.
By default, only one module named v0 is generated.
#[rust_query::migration::schema(SchemaName)]
pub mod vN {
pub struct TableName {
pub column_name: i64,
}
}
use v0::TableName; // the actual module name is `v0`Note that the schema module, table structs and column fields all must be pub.
The id column field is currently reserved for internal use and can not be used.
Supported data types are:
i64(sqliteinteger)f64(sqlitereal)String(sqlitetext)Vec<u8>(sqliteblob)bool(sqliteintegerwithCHECK "col" IN (0, 1))- Any table in the same schema (sqlite
integerwith foreign key constraint) Option<T>whereTis not anOption(sqlite nullable)
§Unique constraints
To define a unique constraint on a column, you need to add an attribute to the table or field.
For example:
#[rust_query::migration::schema(SchemaName)]
pub mod vN {
#[unique(username, movie)] // <-- here
pub struct Rating {
pub movie: String,
pub username: String,
#[unique] // <-- or here
pub uuid: String,
}
}This will create a single schema version with a single table called rating and three columns.
The table will also have two unique contraints (one on the uuid column and one on the combination of username and movie).
Note that optional types are not allowed in unique constraints.
§Indexes
Indexes are very similar to unique constraints, but they don’t require the columns to be unique. These are useful to prevent sqlite from having to scan a whole table. To incentivise creating indices you also get some extra sugar to use the index!
#[rust_query::migration::schema(SchemaName)]
pub mod vN {
pub struct Topic {
#[unique]
pub title: String,
#[index]
pub category: String,
}
}
fn test(txn: &rust_query::Transaction<v0::SchemaName>) {
let _ = txn.lazy_iter(v0::Topic.category("sports"));
let _ = txn.lazy(v0::Topic.title("star wars"));
}The TableName.column_name(value) syntax is only allowed if TableName has an index or
unique constraint that starts with column_name.
Adding and removing indexes and changing the order of columns in indexes and unique constraints is considered backwards compatible and thus does not require a new schema version.
§Multiple schema versions
At some point you might want to change something substantial in your schema. It would be really sad if you had to throw away all the old data in your database. That is why rust_query allows us to define multiple schema versions and how to transition between them.
§Adding tables
One of the simplest things to do is adding a new table.
#[rust_query::migration::schema(SchemaName)]
#[version(0..=1)]
pub mod vN {
pub struct User {
#[unique]
pub username: String,
}
#[version(1..)] // <-- note that `Game`` has a version range
pub struct Game {
#[unique]
pub name: String,
pub size: i64,
}
}
// These are just examples of tables you can use
use v0::SchemaName as _;
use v1::SchemaName as _;
use v0::User as _;
use v1::User as _;
// `v0::Game` does not exist
use v1::Game as _;
fn migrate() -> rust_query::Database<v1::SchemaName> {
rust_query::Database::migrator(rust_query::migration::Config::open("test.db"))
.expect("database version is before supported versions")
.migrate(|_txn| v0::migrate::SchemaName {})
.finish()
.expect("database version is after supported versions")
}The migration itself is not very interesting because new tables are automatically created
without any data. To have some initial data, take a look at the #[from] attribute down below or use
crate::migration::Migrator::fixup.
§Changing columns
Changing columns is very similar to adding and removing structs.
use rust_query::migration::{schema, Config};
use rust_query::{Database, Lazy};
#[schema(Schema)]
#[version(0..=1)]
pub mod vN {
pub struct User {
#[unique]
pub username: String,
#[version(1..)] // <-- here
pub score: i64,
}
}
pub fn migrate() -> Database<v1::Schema> {
Database::migrator(Config::open_in_memory()) // we use an in memory database for this test
.expect("database version is before supported versions")
.migrate(|txn| v0::migrate::Schema {
// In this case it is required to provide a value for each row that already exists.
// This is done with the `v0::migrate::User` struct:
user: txn.migrate_ok(|old: Lazy<v0::User>| v0::migrate::User {
score: old.username.len() as i64 // use the username length as the new score
}),
})
.finish()
.expect("database version is after supported versions")
}§#[from(TableName)] Attribute
You can use this attribute when renaming or splitting a table.
This will make it clear that data in the table should have the
same row ids as the from table.
For example:
#[schema(Schema)]
#[version(0..=1)]
pub mod vN {
#[version(..1)]
pub struct User {
pub name: String,
}
#[version(1..)]
#[from(User)]
pub struct Author {
pub name: String,
}
pub struct Book {
pub author: Author,
}
}
pub fn migrate() -> Database<v1::Schema> {
Database::migrator(Config::open_in_memory()) // we use an in memory database for this test
.expect("database version is before supported versions")
.migrate(|txn| v0::migrate::Schema {
author: txn.migrate_ok(|old: Lazy<v0::User>| v0::migrate::Author {
name: old.name.clone(),
}),
})
.finish()
.expect("database version is after supported versions")
}In this example the Book table exists in both v0 and v1,
however User only exists in v0 and Author only exist in v1.
Note that the pub author: Author field only specifies the latest version
of the table, it will use the #[from] attribute to find previous versions.
§#[no_reference] Attribute
You can put this attribute on your table definitions and it will make it impossible
to have foreign key references to such table.
This makes it possible to use TransactionWeak::delete_ok.