2020-06-02 08:16:23 +00:00
|
|
|
//! DB operations and schema migrations
|
|
|
|
|
2020-09-17 23:02:14 +00:00
|
|
|
pub(in crate::persistence) mod character;
|
|
|
|
pub mod character_loader;
|
|
|
|
pub mod character_updater;
|
2021-04-13 22:05:47 +00:00
|
|
|
mod diesel_to_rusqlite;
|
|
|
|
pub mod error;
|
2020-09-17 23:02:14 +00:00
|
|
|
mod json_models;
|
2020-05-09 15:41:25 +00:00
|
|
|
mod models;
|
|
|
|
|
2020-09-17 23:02:14 +00:00
|
|
|
use common::comp;
|
2021-04-13 22:05:47 +00:00
|
|
|
use refinery::Report;
|
|
|
|
use rusqlite::{Connection, OpenFlags};
|
|
|
|
use std::{
|
2021-04-14 06:15:51 +00:00
|
|
|
fs,
|
2021-04-13 22:05:47 +00:00
|
|
|
path::PathBuf,
|
|
|
|
sync::{Arc, RwLock},
|
|
|
|
time::Duration,
|
|
|
|
};
|
2020-10-06 03:42:09 +00:00
|
|
|
use tracing::info;
|
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
|
2020-11-03 00:12:49 +00:00
|
|
|
pub type PersistedComponents = (
|
|
|
|
comp::Body,
|
|
|
|
comp::Stats,
|
2021-04-14 15:35:34 +00:00
|
|
|
comp::SkillSet,
|
2020-11-03 00:12:49 +00:00
|
|
|
comp::Inventory,
|
|
|
|
Option<comp::Waypoint>,
|
|
|
|
);
|
2020-09-17 23:02:14 +00:00
|
|
|
|
2021-04-13 22:05:47 +00:00
|
|
|
// See: https://docs.rs/refinery/0.5.0/refinery/macro.embed_migrations.html
|
2020-05-09 15:41:25 +00:00
|
|
|
// This macro is called at build-time, and produces the necessary migration info
|
2021-04-13 22:05:47 +00:00
|
|
|
// for the `run_migrations` call below.
|
|
|
|
mod embedded {
|
|
|
|
use refinery::embed_migrations;
|
|
|
|
embed_migrations!("./src/migrations");
|
|
|
|
}
|
2020-07-28 09:26:22 +00:00
|
|
|
|
2021-04-13 22:05:47 +00:00
|
|
|
/// A database connection blessed by Veloren.
|
|
|
|
pub(crate) struct VelorenConnection {
|
|
|
|
connection: Connection,
|
|
|
|
sql_log_mode: SqlLogMode,
|
2020-07-28 09:26:22 +00:00
|
|
|
}
|
|
|
|
|
2021-04-13 22:05:47 +00:00
|
|
|
impl VelorenConnection {
|
|
|
|
fn new(connection: Connection) -> Self {
|
|
|
|
Self {
|
|
|
|
connection,
|
|
|
|
sql_log_mode: SqlLogMode::Disabled,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Updates the SQLite log mode if DatabaseSetting.sql_log_mode has changed
|
|
|
|
pub fn update_log_mode(&mut self, database_settings: &Arc<RwLock<DatabaseSettings>>) {
|
|
|
|
let settings = database_settings
|
|
|
|
.read()
|
|
|
|
.expect("DatabaseSettings RwLock was poisoned");
|
|
|
|
if self.sql_log_mode == (*settings).sql_log_mode {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
set_log_mode(&mut self.connection, (*settings).sql_log_mode);
|
|
|
|
self.sql_log_mode = (*settings).sql_log_mode;
|
|
|
|
|
|
|
|
info!(
|
|
|
|
"SQL log mode for connection changed to {:?}",
|
|
|
|
settings.sql_log_mode
|
|
|
|
);
|
|
|
|
}
|
2020-05-09 15:41:25 +00:00
|
|
|
}
|
|
|
|
|
2021-04-13 22:05:47 +00:00
|
|
|
fn set_log_mode(connection: &mut Connection, sql_log_mode: SqlLogMode) {
|
|
|
|
// Rusqlite's trace and profile logging are mutually exclusive and cannot be
|
|
|
|
// used together
|
|
|
|
match sql_log_mode {
|
|
|
|
SqlLogMode::Trace => {
|
|
|
|
connection.trace(Some(rusqlite_trace_callback));
|
|
|
|
},
|
|
|
|
SqlLogMode::Profile => {
|
|
|
|
connection.profile(Some(rusqlite_profile_callback));
|
|
|
|
},
|
|
|
|
SqlLogMode::Disabled => {
|
|
|
|
connection.trace(None);
|
|
|
|
connection.profile(None);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
2020-09-17 23:02:14 +00:00
|
|
|
|
2021-04-13 22:05:47 +00:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct DatabaseSettings {
|
|
|
|
pub db_dir: PathBuf,
|
|
|
|
pub sql_log_mode: SqlLogMode,
|
|
|
|
}
|
2020-09-17 23:02:14 +00:00
|
|
|
|
2021-04-13 22:05:47 +00:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
|
|
pub enum SqlLogMode {
|
|
|
|
/// Logging is disabled
|
|
|
|
Disabled,
|
|
|
|
/// Records timings for each SQL statement
|
|
|
|
Profile,
|
|
|
|
/// Prints all executed SQL statements
|
|
|
|
Trace,
|
2020-09-17 23:02:14 +00:00
|
|
|
}
|
|
|
|
|
2021-04-13 22:05:47 +00:00
|
|
|
/// Runs any pending database migrations. This is executed during server startup
|
|
|
|
pub fn run_migrations(settings: &DatabaseSettings) {
|
|
|
|
let mut conn = establish_connection(settings);
|
2020-09-17 23:02:14 +00:00
|
|
|
|
2021-04-13 22:05:47 +00:00
|
|
|
diesel_to_rusqlite::migrate_from_diesel(&mut conn)
|
|
|
|
.expect("One-time migration from Diesel to Refinery failed");
|
|
|
|
|
|
|
|
// If migrations fail to run, the server cannot start since the database will
|
|
|
|
// not be in the required state.
|
|
|
|
let report: Report = embedded::migrations::runner()
|
|
|
|
.set_abort_divergent(false)
|
|
|
|
.run(&mut conn.connection)
|
|
|
|
.expect("Database migrations failed, server startup aborted");
|
|
|
|
|
|
|
|
let applied_migrations = report.applied_migrations().len();
|
|
|
|
info!("Applied {} database migrations", applied_migrations);
|
|
|
|
}
|
|
|
|
|
|
|
|
// These callbacks use info logging because they are never enabled by default,
|
|
|
|
// only when explicitly turned on via CLI arguments or interactive CLI commands.
|
|
|
|
// Setting them to anything other than info would remove the ability to get SQL
|
|
|
|
// logging from a running server that wasn't started at higher than info.
|
|
|
|
fn rusqlite_trace_callback(log_message: &str) {
|
|
|
|
info!("{}", log_message);
|
2020-09-17 23:02:14 +00:00
|
|
|
}
|
2021-04-13 22:05:47 +00:00
|
|
|
fn rusqlite_profile_callback(log_message: &str, dur: Duration) {
|
|
|
|
info!("{} Duration: {:?}", log_message, dur);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn establish_connection(settings: &DatabaseSettings) -> VelorenConnection {
|
2021-04-14 06:15:51 +00:00
|
|
|
fs::create_dir_all(&settings.db_dir).expect(&*format!(
|
|
|
|
"Failed to create saves directory: {:?}",
|
|
|
|
&settings.db_dir
|
|
|
|
));
|
2021-04-13 22:05:47 +00:00
|
|
|
let connection = Connection::open_with_flags(
|
|
|
|
&settings.db_dir.join("db.sqlite"),
|
|
|
|
OpenFlags::SQLITE_OPEN_PRIVATE_CACHE | OpenFlags::default(),
|
|
|
|
)
|
|
|
|
.unwrap_or_else(|err| {
|
|
|
|
panic!(
|
|
|
|
"Error connecting to {}, Error: {:?}",
|
|
|
|
settings.db_dir.join("db.sqlite").display(),
|
|
|
|
err
|
|
|
|
)
|
|
|
|
});
|
2020-09-17 23:02:14 +00:00
|
|
|
|
2021-04-13 22:05:47 +00:00
|
|
|
let mut veloren_connection = VelorenConnection::new(connection);
|
2020-06-06 08:26:48 +00:00
|
|
|
|
2021-04-13 22:05:47 +00:00
|
|
|
let connection = &mut veloren_connection.connection;
|
|
|
|
|
|
|
|
set_log_mode(connection, settings.sql_log_mode);
|
|
|
|
veloren_connection.sql_log_mode = settings.sql_log_mode;
|
|
|
|
|
|
|
|
rusqlite::vtab::array::load_module(&connection).expect("Failed to load sqlite array module");
|
|
|
|
|
|
|
|
connection.set_prepared_statement_cache_capacity(100);
|
2020-06-06 08:26:48 +00:00
|
|
|
|
|
|
|
// 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
|
2021-04-13 22:05:47 +00:00
|
|
|
.pragma_update(None, "foreign_keys", &"ON")
|
|
|
|
.expect("Failed to set foreign_keys PRAGMA");
|
|
|
|
connection
|
|
|
|
.pragma_update(None, "journal_mode", &"WAL")
|
|
|
|
.expect("Failed to set journal_mode PRAGMA");
|
|
|
|
connection
|
|
|
|
.pragma_update(None, "busy_timeout", &"250")
|
|
|
|
.expect("Failed to set busy_timeout PRAGMA");
|
2020-06-06 08:26:48 +00:00
|
|
|
|
2021-04-13 22:05:47 +00:00
|
|
|
veloren_connection
|
2020-05-09 15:41:25 +00:00
|
|
|
}
|