2020-06-02 08:16:23 +00:00
|
|
|
//! DB operations and schema migrations
|
|
|
|
//!
|
|
|
|
//! This code uses several [`Diesel ORM`](http://diesel.rs/) tools for DB operations:
|
|
|
|
//! - [`diesel-migrations`](https://docs.rs/diesel_migrations/1.4.0/diesel_migrations/)
|
|
|
|
//! for managing table migrations
|
|
|
|
//! - [`diesel-cli`](https://github.com/diesel-rs/diesel/tree/master/diesel_cli/)
|
|
|
|
//! for generating and testing migrations
|
|
|
|
|
2020-09-17 23:02:14 +00:00
|
|
|
pub(in crate::persistence) mod character;
|
|
|
|
pub mod character_loader;
|
|
|
|
pub mod character_updater;
|
2020-04-20 08:44:29 +00:00
|
|
|
mod error;
|
2020-09-17 23:02:14 +00:00
|
|
|
mod json_models;
|
2020-05-09 15:41:25 +00:00
|
|
|
mod models;
|
|
|
|
mod schema;
|
|
|
|
|
|
|
|
extern crate diesel;
|
|
|
|
|
2020-09-17 23:02:14 +00:00
|
|
|
use common::comp;
|
2020-06-06 08:26:48 +00:00
|
|
|
use diesel::{connection::SimpleConnection, prelude::*};
|
2020-05-09 15:41:25 +00:00
|
|
|
use diesel_migrations::embed_migrations;
|
2020-05-14 18:47:16 +00:00
|
|
|
use std::{env, fs, path::PathBuf};
|
2020-07-28 09:26:22 +00:00
|
|
|
use tracing::{info, warn};
|
2020-05-09 15:41:25 +00:00
|
|
|
|
2020-09-17 23:02:14 +00:00
|
|
|
/// A tuple of the components that are persisted to the DB for each character
|
|
|
|
pub type PersistedComponents = (comp::Body, comp::Stats, comp::Inventory, comp::Loadout);
|
|
|
|
|
2020-05-09 15:41:25 +00:00
|
|
|
// See: https://docs.rs/diesel_migrations/1.4.0/diesel_migrations/macro.embed_migrations.html
|
|
|
|
// This macro is called at build-time, and produces the necessary migration info
|
|
|
|
// for the `embedded_migrations` call below.
|
2020-07-20 00:08:19 +00:00
|
|
|
//
|
|
|
|
// NOTE: Adding a useless comment to trigger the migrations being run. Delete
|
|
|
|
// when needed.
|
2020-05-09 15:41:25 +00:00
|
|
|
embed_migrations!();
|
|
|
|
|
2020-07-28 09:26:22 +00:00
|
|
|
struct TracingOut;
|
|
|
|
|
|
|
|
impl std::io::Write for TracingOut {
|
|
|
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
|
|
info!("{}", String::from_utf8_lossy(buf));
|
|
|
|
Ok(buf.len())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn flush(&mut self) -> std::io::Result<()> { Ok(()) }
|
|
|
|
}
|
|
|
|
|
2020-06-02 08:16:23 +00:00
|
|
|
/// Runs any pending database migrations. This is executed during server startup
|
2020-05-12 23:58:15 +00:00
|
|
|
pub fn run_migrations(db_dir: &str) -> Result<(), diesel_migrations::RunMigrationsError> {
|
2020-05-14 18:47:16 +00:00
|
|
|
let db_dir = &apply_saves_dir_override(db_dir);
|
2020-05-12 23:58:15 +00:00
|
|
|
let _ = fs::create_dir(format!("{}/", db_dir));
|
2020-07-28 09:26:22 +00:00
|
|
|
|
2020-08-06 22:08:54 +00:00
|
|
|
embedded_migrations::run_with_output(
|
2020-09-17 23:02:14 +00:00
|
|
|
&establish_connection(db_dir)
|
|
|
|
.expect(
|
|
|
|
"If we cannot execute migrations, we should not be allowed to launch the server, \
|
|
|
|
so we don't populate it with bad data.",
|
|
|
|
)
|
|
|
|
.0,
|
2020-07-28 09:26:22 +00:00
|
|
|
&mut std::io::LineWriter::new(TracingOut),
|
2020-08-06 22:08:54 +00:00
|
|
|
)
|
2020-05-09 15:41:25 +00:00
|
|
|
}
|
|
|
|
|
2020-09-17 23:02:14 +00:00
|
|
|
/// A database connection blessed by Veloren.
|
|
|
|
pub struct VelorenConnection(SqliteConnection);
|
|
|
|
|
|
|
|
/// A transaction blessed by Veloren.
|
|
|
|
#[derive(Clone, Copy)]
|
|
|
|
pub struct VelorenTransaction<'a>(&'a SqliteConnection);
|
|
|
|
|
|
|
|
impl VelorenConnection {
|
|
|
|
/// Open a transaction in order to be able to run a set of queries against
|
|
|
|
/// the database. We require the use of a transaction, rather than
|
|
|
|
/// allowing direct session access, so that (1) we can control things
|
|
|
|
/// like the retry process (at a future date), and (2) to avoid
|
|
|
|
/// accidentally forgetting to open or reuse a transaction.
|
|
|
|
///
|
|
|
|
/// We could make things even more foolproof, but we restrict ourselves to
|
|
|
|
/// this for now.
|
|
|
|
pub fn transaction<T, E, F>(&mut self, f: F) -> Result<T, E>
|
|
|
|
where
|
|
|
|
F: for<'a> FnOnce(VelorenTransaction<'a>) -> Result<T, E>,
|
|
|
|
E: From<diesel::result::Error>,
|
|
|
|
{
|
|
|
|
self.0.transaction(|| f(VelorenTransaction(&self.0)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> core::ops::Deref for VelorenTransaction<'a> {
|
|
|
|
type Target = SqliteConnection;
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target { &self.0 }
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn establish_connection(db_dir: &str) -> QueryResult<VelorenConnection> {
|
2020-05-14 18:47:16 +00:00
|
|
|
let db_dir = &apply_saves_dir_override(db_dir);
|
2020-05-12 23:58:15 +00:00
|
|
|
let database_url = format!("{}/db.sqlite", db_dir);
|
2020-06-06 08:26:48 +00:00
|
|
|
|
|
|
|
let connection = SqliteConnection::establish(&database_url)
|
|
|
|
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url));
|
|
|
|
|
|
|
|
// Use Write-Ahead-Logging for improved concurrency: https://sqlite.org/wal.html
|
|
|
|
// Set a busy timeout (in ms): https://sqlite.org/c3ref/busy_timeout.html
|
2020-09-17 23:02:14 +00:00
|
|
|
connection
|
|
|
|
.batch_execute(
|
|
|
|
"
|
2020-08-06 22:08:54 +00:00
|
|
|
PRAGMA foreign_keys = ON;
|
2020-06-06 08:26:48 +00:00
|
|
|
PRAGMA journal_mode = WAL;
|
2020-09-17 23:02:14 +00:00
|
|
|
PRAGMA busy_timeout = 250;
|
2020-06-06 08:26:48 +00:00
|
|
|
",
|
2020-09-17 23:02:14 +00:00
|
|
|
)
|
|
|
|
.expect(
|
2020-08-06 22:08:54 +00:00
|
|
|
"Failed adding PRAGMA statements while establishing sqlite connection, including \
|
|
|
|
enabling foreign key constraints. We will not allow connecting to the server under \
|
2020-09-17 23:02:14 +00:00
|
|
|
these conditions.",
|
2020-06-06 08:26:48 +00:00
|
|
|
);
|
|
|
|
|
2020-09-17 23:02:14 +00:00
|
|
|
Ok(VelorenConnection(connection))
|
2020-05-09 15:41:25 +00:00
|
|
|
}
|
2020-05-14 18:47:16 +00:00
|
|
|
|
|
|
|
fn apply_saves_dir_override(db_dir: &str) -> String {
|
2020-06-21 21:47:49 +00:00
|
|
|
if let Some(saves_dir) = env::var_os("VELOREN_SAVES_DIR") {
|
|
|
|
let path = PathBuf::from(saves_dir.clone());
|
2020-05-14 18:47:16 +00:00
|
|
|
if path.exists() || path.parent().map(|x| x.exists()).unwrap_or(false) {
|
|
|
|
// Only allow paths with valid unicode characters
|
2020-06-02 08:16:23 +00:00
|
|
|
if let Some(path) = path.to_str() {
|
|
|
|
return path.to_owned();
|
2020-05-14 18:47:16 +00:00
|
|
|
}
|
|
|
|
}
|
2020-06-21 21:47:49 +00:00
|
|
|
warn!(?saves_dir, "VELOREN_SAVES_DIR points to an invalid path.");
|
2020-05-14 18:47:16 +00:00
|
|
|
}
|
|
|
|
db_dir.to_string()
|
|
|
|
}
|