You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-11-23 11:02:35 +03:00
97 lines
3.3 KiB
Markdown
97 lines
3.3 KiB
Markdown
# Database
|
|
|
|
Interactions with the database goes through `sqlx`.
|
|
It provides async database operations with connection pooling, migrations support and compile-time check of queries through macros.
|
|
|
|
## Compile-time check of queries
|
|
|
|
To be able to check queries, `sqlx` has to introspect the live database.
|
|
Usually it does so by having the database available at compile time, but to avoid that we're using the `offline` feature of `sqlx`, which saves the introspection informatons as a flat file in the repository.
|
|
|
|
Preparing this flat file is done through `sqlx-cli`, and should be done everytime the database schema or the queries changed.
|
|
|
|
```sh
|
|
# Install the CLI
|
|
cargo install sqlx-cli --no-default-features --features postgres
|
|
|
|
cd crates/storage/ # Must be in the mas-storage crate folder
|
|
export DATABASE_URL=postgresql:///matrix_auth
|
|
cargo sqlx prepare
|
|
```
|
|
|
|
## Migrations
|
|
|
|
Migration files live in the `migrations` folder in the `mas-core` crate.
|
|
|
|
```sh
|
|
cd crates/storage/ # Again, in the mas-storage crate folder
|
|
export DATABASE_URL=postgresql:///matrix_auth
|
|
cargo sqlx migrate run # Run pending migrations
|
|
cargo sqlx migrate revert # Revert the last migration
|
|
cargo sqlx migrate add -r [description] # Add new migration files
|
|
```
|
|
|
|
Note that migrations are embedded in the final binary and can be run from the service CLI tool.
|
|
|
|
## Writing database interactions
|
|
|
|
A typical interaction with the database look like this:
|
|
|
|
```rust
|
|
pub async fn lookup_session(
|
|
executor: impl Executor<'_, Database = Postgres>,
|
|
id: i64,
|
|
) -> anyhow::Result<SessionInfo> {
|
|
sqlx::query_as!(
|
|
SessionInfo, // Struct that will be filled with the result
|
|
r#"
|
|
SELECT
|
|
s.id,
|
|
u.id as user_id,
|
|
u.username,
|
|
s.active,
|
|
s.created_at,
|
|
a.created_at as "last_authd_at?"
|
|
FROM user_sessions s
|
|
INNER JOIN users u
|
|
ON s.user_id = u.id
|
|
LEFT JOIN user_session_authentications a
|
|
ON a.session_id = s.id
|
|
WHERE s.id = $1
|
|
ORDER BY a.created_at DESC
|
|
LIMIT 1
|
|
"#,
|
|
id, // Query parameter
|
|
)
|
|
.fetch_one(executor)
|
|
.await
|
|
// Providing some context when there is an error
|
|
.context("could not fetch session")
|
|
}
|
|
```
|
|
|
|
Note that we pass an `impl Executor` as parameter here.
|
|
This allows us to use this function from either a simple connection or from an active transaction.
|
|
|
|
The caveat here is that the `executor` can be used only once, so if an interaction needs to do multiple queries, it should probably take an `impl Acquire` to then acquire a transaction and do multiple interactions.
|
|
|
|
```rust
|
|
pub async fn login(
|
|
conn: impl Acquire<'_, Database = Postgres>,
|
|
username: &str,
|
|
password: String,
|
|
) -> Result<SessionInfo, LoginError> {
|
|
let mut txn = conn.begin().await.context("could not start transaction")?;
|
|
// First interaction
|
|
let user = lookup_user_by_username(&mut txn, username)?;
|
|
// Second interaction
|
|
let mut session = start_session(&mut txn, user).await?;
|
|
// Third interaction
|
|
session.last_authd_at =
|
|
Some(authenticate_session(&mut txn, session.id, password).await?);
|
|
// Commit the transaction once everything went fine
|
|
txn.commit().await.context("could not commit transaction")?;
|
|
Ok(session)
|
|
}
|
|
```
|