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