veloren_server/persistence/
mod.rs1pub(in crate::persistence) mod character;
8pub mod character_loader;
9pub mod character_updater;
10mod diesel_to_rusqlite;
11pub mod error;
12mod json_models;
13mod models;
14
15use crate::persistence::character_updater::PetPersistenceData;
16use common::comp;
17use refinery::Report;
18use rusqlite::{
19 Connection, OpenFlags,
20 trace::{TraceEvent, TraceEventCodes},
21};
22use std::{
23 fs,
24 ops::Deref,
25 path::PathBuf,
26 sync::{Arc, RwLock},
27};
28use tracing::info;
29
30#[derive(Debug)]
32pub struct PersistedComponents {
33 pub body: comp::Body,
34 pub hardcore: Option<comp::Hardcore>,
35 pub stats: comp::Stats,
36 pub skill_set: comp::SkillSet,
37 pub inventory: comp::Inventory,
38 pub waypoint: Option<comp::Waypoint>,
39 pub pets: Vec<PetPersistenceData>,
40 pub active_abilities: comp::ActiveAbilities,
41 pub map_marker: Option<comp::MapMarker>,
42}
43
44pub type EditableComponents = (comp::Body,);
45
46mod embedded {
50 use refinery::embed_migrations;
51 embed_migrations!("./src/migrations");
52}
53
54pub(crate) struct VelorenConnection {
56 connection: Connection,
57 sql_log_mode: SqlLogMode,
58}
59
60impl VelorenConnection {
61 fn new(connection: Connection) -> Self {
62 Self {
63 connection,
64 sql_log_mode: SqlLogMode::Disabled,
65 }
66 }
67
68 pub fn update_log_mode(&mut self, database_settings: &Arc<RwLock<DatabaseSettings>>) {
70 let settings = database_settings
71 .read()
72 .expect("DatabaseSettings RwLock was poisoned");
73 if self.sql_log_mode == settings.sql_log_mode {
74 return;
75 }
76
77 set_log_mode(&mut self.connection, settings.sql_log_mode);
78 self.sql_log_mode = settings.sql_log_mode;
79
80 info!(
81 "SQL log mode for connection changed to {:?}",
82 settings.sql_log_mode
83 );
84 }
85}
86
87impl Deref for VelorenConnection {
88 type Target = Connection;
89
90 fn deref(&self) -> &Connection { &self.connection }
91}
92
93fn set_log_mode(connection: &mut Connection, sql_log_mode: SqlLogMode) {
94 match sql_log_mode {
95 SqlLogMode::Trace => {
96 connection.trace_v2(
97 TraceEventCodes::SQLITE_TRACE_STMT,
98 Some(rusqlite_trace_callback),
99 );
100 },
101 SqlLogMode::Profile => {
102 connection.trace_v2(
103 TraceEventCodes::SQLITE_TRACE_PROFILE,
104 Some(rusqlite_trace_callback),
105 );
106 },
107 SqlLogMode::Disabled => {
108 connection.trace_v2(TraceEventCodes::empty(), None);
109 },
110 };
111}
112
113#[derive(Clone)]
114pub struct DatabaseSettings {
115 pub db_dir: PathBuf,
116 pub sql_log_mode: SqlLogMode,
117}
118
119#[derive(Clone, Copy, PartialEq, Eq)]
120pub enum ConnectionMode {
121 ReadOnly,
122 ReadWrite,
123}
124
125#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
126pub enum SqlLogMode {
127 #[default]
129 Disabled,
130 Profile,
132 Trace,
134}
135
136impl SqlLogMode {
137 pub fn variants() -> [&'static str; 3] { ["disabled", "profile", "trace"] }
138}
139
140impl core::str::FromStr for SqlLogMode {
141 type Err = &'static str;
142
143 fn from_str(s: &str) -> Result<Self, Self::Err> {
144 match s {
145 "disabled" => Ok(Self::Disabled),
146 "profile" => Ok(Self::Profile),
147 "trace" => Ok(Self::Trace),
148 _ => Err("Could not parse SqlLogMode"),
149 }
150 }
151}
152
153#[expect(clippy::to_string_trait_impl)]
154impl ToString for SqlLogMode {
155 fn to_string(&self) -> String {
156 match self {
157 SqlLogMode::Disabled => "disabled",
158 SqlLogMode::Profile => "profile",
159 SqlLogMode::Trace => "trace",
160 }
161 .into()
162 }
163}
164
165pub fn run_migrations(settings: &DatabaseSettings) {
167 let mut conn = establish_connection(settings, ConnectionMode::ReadWrite);
168
169 diesel_to_rusqlite::migrate_from_diesel(&mut conn)
170 .expect("One-time migration from Diesel to Refinery failed");
171
172 let report: Report = embedded::migrations::runner()
175 .set_abort_divergent(false)
176 .run(&mut conn.connection)
177 .expect("Database migrations failed, server startup aborted");
178
179 let applied_migrations = report.applied_migrations().len();
180 info!("Applied {} database migrations", applied_migrations);
181}
182
183pub fn vacuum_database(settings: &DatabaseSettings) {
186 let conn = establish_connection(settings, ConnectionMode::ReadWrite);
187
188 conn.execute("VACUUM main", [])
189 .expect("Database vacuuming failed, server startup aborted");
190
191 info!("Database vacuumed");
192}
193
194fn rusqlite_trace_callback(event: TraceEvent<'_>) {
199 match event {
200 TraceEvent::Stmt(_, msg) => info!("{}", msg),
201 TraceEvent::Profile(stmt, dur) => info!("{} Duration: {:?}", stmt.sql(), dur),
202 _ => (),
203 }
204}
205
206pub(crate) fn establish_connection(
207 settings: &DatabaseSettings,
208 connection_mode: ConnectionMode,
209) -> VelorenConnection {
210 fs::create_dir_all(&settings.db_dir)
211 .unwrap_or_else(|_| panic!("Failed to create saves directory: {:?}", settings.db_dir));
212
213 let open_flags = OpenFlags::SQLITE_OPEN_PRIVATE_CACHE
214 | OpenFlags::SQLITE_OPEN_NO_MUTEX
215 | match connection_mode {
216 ConnectionMode::ReadWrite => {
217 OpenFlags::SQLITE_OPEN_CREATE | OpenFlags::SQLITE_OPEN_READ_WRITE
218 },
219 ConnectionMode::ReadOnly => OpenFlags::SQLITE_OPEN_READ_ONLY,
220 };
221
222 let connection = Connection::open_with_flags(settings.db_dir.join("db.sqlite"), open_flags)
223 .unwrap_or_else(|err| {
224 panic!(
225 "Error connecting to {}, Error: {:?}",
226 settings.db_dir.join("db.sqlite").display(),
227 err
228 )
229 });
230
231 let mut veloren_connection = VelorenConnection::new(connection);
232
233 let connection = &mut veloren_connection.connection;
234
235 set_log_mode(connection, settings.sql_log_mode);
236 veloren_connection.sql_log_mode = settings.sql_log_mode;
237
238 rusqlite::vtab::array::load_module(connection).expect("Failed to load sqlite array module");
239
240 connection.set_prepared_statement_cache_capacity(100);
241
242 connection
245 .pragma_update(None, "foreign_keys", "ON")
246 .expect("Failed to set foreign_keys PRAGMA");
247 connection
248 .pragma_update(None, "journal_mode", "WAL")
249 .expect("Failed to set journal_mode PRAGMA");
250 connection
251 .pragma_update(None, "busy_timeout", "250")
252 .expect("Failed to set busy_timeout PRAGMA");
253
254 veloren_connection
255}