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::{Connection, OpenFlags};
19use std::{
20 fs,
21 ops::Deref,
22 path::PathBuf,
23 sync::{Arc, RwLock},
24 time::Duration,
25};
26use tracing::info;
27
28pub(crate) use character::parse_waypoint;
30
31#[derive(Debug)]
33pub struct PersistedComponents {
34 pub body: comp::Body,
35 pub hardcore: Option<comp::Hardcore>,
36 pub stats: comp::Stats,
37 pub skill_set: comp::SkillSet,
38 pub inventory: comp::Inventory,
39 pub waypoint: Option<comp::Waypoint>,
40 pub pets: Vec<PetPersistenceData>,
41 pub active_abilities: comp::ActiveAbilities,
42 pub map_marker: Option<comp::MapMarker>,
43}
44
45pub type EditableComponents = (comp::Body,);
46
47mod embedded {
51 use refinery::embed_migrations;
52 embed_migrations!("./src/migrations");
53}
54
55pub(crate) struct VelorenConnection {
57 connection: Connection,
58 sql_log_mode: SqlLogMode,
59}
60
61impl VelorenConnection {
62 fn new(connection: Connection) -> Self {
63 Self {
64 connection,
65 sql_log_mode: SqlLogMode::Disabled,
66 }
67 }
68
69 pub fn update_log_mode(&mut self, database_settings: &Arc<RwLock<DatabaseSettings>>) {
71 let settings = database_settings
72 .read()
73 .expect("DatabaseSettings RwLock was poisoned");
74 if self.sql_log_mode == settings.sql_log_mode {
75 return;
76 }
77
78 set_log_mode(&mut self.connection, settings.sql_log_mode);
79 self.sql_log_mode = settings.sql_log_mode;
80
81 info!(
82 "SQL log mode for connection changed to {:?}",
83 settings.sql_log_mode
84 );
85 }
86}
87
88impl Deref for VelorenConnection {
89 type Target = Connection;
90
91 fn deref(&self) -> &Connection { &self.connection }
92}
93
94fn set_log_mode(connection: &mut Connection, sql_log_mode: SqlLogMode) {
95 match sql_log_mode {
98 SqlLogMode::Trace => {
99 connection.trace(Some(rusqlite_trace_callback));
100 },
101 SqlLogMode::Profile => {
102 connection.profile(Some(rusqlite_profile_callback));
103 },
104 SqlLogMode::Disabled => {
105 connection.trace(None);
106 connection.profile(None);
107 },
108 };
109}
110
111#[derive(Clone)]
112pub struct DatabaseSettings {
113 pub db_dir: PathBuf,
114 pub sql_log_mode: SqlLogMode,
115}
116
117#[derive(Clone, Copy, PartialEq, Eq)]
118pub enum ConnectionMode {
119 ReadOnly,
120 ReadWrite,
121}
122
123#[derive(Clone, Copy, Debug, PartialEq, Eq)]
124pub enum SqlLogMode {
125 Disabled,
127 Profile,
129 Trace,
131}
132
133impl SqlLogMode {
134 pub fn variants() -> [&'static str; 3] { ["disabled", "profile", "trace"] }
135}
136
137impl Default for SqlLogMode {
138 fn default() -> Self { Self::Disabled }
139}
140
141impl core::str::FromStr for SqlLogMode {
142 type Err = &'static str;
143
144 fn from_str(s: &str) -> Result<Self, Self::Err> {
145 match s {
146 "disabled" => Ok(Self::Disabled),
147 "profile" => Ok(Self::Profile),
148 "trace" => Ok(Self::Trace),
149 _ => Err("Could not parse SqlLogMode"),
150 }
151 }
152}
153
154#[expect(clippy::to_string_trait_impl)]
155impl ToString for SqlLogMode {
156 fn to_string(&self) -> String {
157 match self {
158 SqlLogMode::Disabled => "disabled",
159 SqlLogMode::Profile => "profile",
160 SqlLogMode::Trace => "trace",
161 }
162 .into()
163 }
164}
165
166pub fn run_migrations(settings: &DatabaseSettings) {
168 let mut conn = establish_connection(settings, ConnectionMode::ReadWrite);
169
170 diesel_to_rusqlite::migrate_from_diesel(&mut conn)
171 .expect("One-time migration from Diesel to Refinery failed");
172
173 let report: Report = embedded::migrations::runner()
176 .set_abort_divergent(false)
177 .run(&mut conn.connection)
178 .expect("Database migrations failed, server startup aborted");
179
180 let applied_migrations = report.applied_migrations().len();
181 info!("Applied {} database migrations", applied_migrations);
182}
183
184pub fn vacuum_database(settings: &DatabaseSettings) {
187 let conn = establish_connection(settings, ConnectionMode::ReadWrite);
188
189 conn.execute("VACUUM main", [])
190 .expect("Database vacuuming failed, server startup aborted");
191
192 info!("Database vacuumed");
193}
194
195fn rusqlite_trace_callback(log_message: &str) {
200 info!("{}", log_message);
201}
202fn rusqlite_profile_callback(log_message: &str, dur: Duration) {
203 info!("{} Duration: {:?}", log_message, dur);
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}