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-05-02 15:08:39 +00:00
|
|
|
ops::Deref,
|
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 {
|
2021-07-11 18:41:52 +00:00
|
|
|
#![allow(clippy::nonstandard_macro_braces)] //tmp as of false positive !?
|
2021-04-13 22:05:47 +00:00
|
|
|
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-05-02 15:08:39 +00:00
|
|
|
impl Deref for VelorenConnection {
|
|
|
|
type Target = Connection;
|
|
|
|
|
|
|
|
fn deref(&self) -> &Connection { &self.connection }
|
|
|
|
}
|
|
|
|
|
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-05-02 15:08:39 +00:00
|
|
|
#[derive(Clone, Copy, PartialEq)]
|
|
|
|
pub enum ConnectionMode {
|
|
|
|
ReadOnly,
|
|
|
|
ReadWrite,
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
impl SqlLogMode {
|
|
|
|
pub fn variants() -> [&'static str; 3] { ["disabled", "profile", "trace"] }
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for SqlLogMode {
|
|
|
|
fn default() -> Self { Self::Disabled }
|
|
|
|
}
|
|
|
|
|
|
|
|
impl core::str::FromStr for SqlLogMode {
|
|
|
|
type Err = &'static str;
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
match s {
|
|
|
|
"disabled" => Ok(Self::Disabled),
|
|
|
|
"profile" => Ok(Self::Profile),
|
|
|
|
"trace" => Ok(Self::Trace),
|
|
|
|
_ => Err("Could not parse SqlLogMode"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ToString for SqlLogMode {
|
|
|
|
fn to_string(&self) -> String {
|
|
|
|
match self {
|
|
|
|
SqlLogMode::Disabled => "disabled",
|
|
|
|
SqlLogMode::Profile => "profile",
|
|
|
|
SqlLogMode::Trace => "trace",
|
|
|
|
}
|
|
|
|
.into()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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) {
|
2021-05-02 15:08:39 +00:00
|
|
|
let mut conn = establish_connection(settings, ConnectionMode::ReadWrite);
|
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);
|
|
|
|
}
|
|
|
|
|
2021-05-02 15:08:39 +00:00
|
|
|
pub(crate) fn establish_connection(
|
|
|
|
settings: &DatabaseSettings,
|
|
|
|
connection_mode: ConnectionMode,
|
|
|
|
) -> 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-05-02 15:08:39 +00:00
|
|
|
|
|
|
|
let open_flags = OpenFlags::SQLITE_OPEN_PRIVATE_CACHE
|
|
|
|
| OpenFlags::SQLITE_OPEN_NO_MUTEX
|
|
|
|
| match connection_mode {
|
|
|
|
ConnectionMode::ReadWrite => {
|
|
|
|
OpenFlags::SQLITE_OPEN_CREATE | OpenFlags::SQLITE_OPEN_READ_WRITE
|
|
|
|
},
|
|
|
|
ConnectionMode::ReadOnly => OpenFlags::SQLITE_OPEN_READ_ONLY,
|
|
|
|
};
|
|
|
|
|
|
|
|
let connection = Connection::open_with_flags(&settings.db_dir.join("db.sqlite"), open_flags)
|
|
|
|
.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;
|
|
|
|
|
2021-07-11 18:41:52 +00:00
|
|
|
rusqlite::vtab::array::load_module(connection).expect("Failed to load sqlite array module");
|
2021-04-13 22:05:47 +00:00
|
|
|
|
|
|
|
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
|
|
|
}
|