Attribute Macro schema

Source
#[schema]
Expand description

Use this macro to define your schema.

§Supported data types:

  • i64 (sqlite integer)
  • f64 (sqlite real)
  • String (sqlite text)
  • Vec<u8> (sqlite blob)
  • Any table in the same schema (sqlite integer with foreign key constraint)
  • Option<T> where T is not an Option (sqlite nullable)

Booleans are not supported in schemas yet.

§Unique constraints

To define a unique constraint on a column, you need to add an attribute to the table or field. The attribute needs to start with unique and can have any suffix. Within a table, the different unique constraints must have different suffixes.

For example:

#[rust_query::migration::schema(Schema)]
#[version(0..=0)]
pub mod vN {
    pub struct User {
        #[unique_email]
        pub email: String,
        #[unique_username]
        pub username: String,
    }
}

This will create a single schema with a single table called user and two columns. The table will also have two unique contraints. Note that optional types are not allowed in unique constraints.

§Multiple versions

The macro must be applied to a module named vN. This is because the module is a template that is used to generate multiple modules called v0, v1 etc. Each module corresponds to a schema version and contains the types to work with that schema.

Note in the previous example that the schema version range is 0..=0 so there is only a version 0. The generated code will have a structure like this:

pub mod v0 {
    pub struct Schema;
    pub struct User{..};
    // a bunch of other stuff
}
pub struct MacroRoot;

§Adding tables

At some point you might want to add a new table.

#[rust_query::migration::schema(Schema)]
#[version(0..=1)]
pub mod vN {
    pub struct User {
        #[unique_email]
        pub email: String,
        #[unique_username]
        pub username: String,
    }
    #[version(1..)] // <-- note that `Game`` has a version range
    pub struct Game {
        pub name: String,
        pub size: i64,
    }
}

We now have two schema versions which generates two modules v0 and v1. They look something like this:

pub mod v0 {
    pub struct Schema;
    pub struct User{..};
    pub mod migrate {..}
    // a bunch of other stuff
}
pub mod v1 {
    pub struct Schema;
    pub struct User{..};
    pub struct Game{..};
    // a bunch of other stuff
}
pub struct MacroRoot;

§Changing columns

Changing columns is very similar to adding and removing structs.

use rust_query::migration::{schema, Config};
use rust_query::{LocalClient, Database};
#[schema(Schema)]
#[version(0..=1)]
pub mod vN {
    pub struct User {
        #[unique_email]
        pub email: String,
        #[unique_username]
        pub username: String,
        #[version(1..)] // <-- here
        pub score: i64,
    }
}
// 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:
pub fn migrate(client: &mut LocalClient) -> Database<v1::Schema> {
    let m = client.migrator(Config::open_in_memory()) // we use an in memory database for this test
        .expect("database version is before supported versions");
    let m = m.migrate(|txn| v0::migrate::Schema {
        user: txn.migrate_ok(|old: v0::User!(email)| v0::migrate::User {
            score: old.email.len() as i64 // use the email length as the new score
        }),
    });
    m.finish().expect("database version is after supported versions")
}

The migrate function first creates an empty database if it does not exists.

§#[from] 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,
    }
}

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.

This will work correctly and will let you migrate data from User to Author with code like this:

let m = m.migrate(|txn| v0::migrate::Schema {
    author: txn.migrate_ok(|old: v0::User!(name)| v0::migrate::Author {
        name: old.name,
    }),
});

§#[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.