1use crate::utils::clock::Instant;
6#[cfg(feature = "sqlite")]
7use anyhow::{Context, Result};
8#[cfg(feature = "sqlite")]
9use rusqlite::Connection;
10#[cfg(feature = "sqlite")]
11use std::collections::HashMap;
12#[cfg(feature = "sqlite")]
13use std::path::PathBuf;
14#[cfg(feature = "sqlite")]
15use std::sync::Mutex; use crate::state::{ClientId, ServerId};
18
19pub const MEMORY_DATABASE_PATH: &str = ":memory:";
21
22pub const MAX_DATABASE_NAME_LEN: usize = 64;
24
25pub const MAX_QUERY_ROWS: usize = 10_000;
27pub const MAX_QUERY_RESULT_BYTES: usize = 8 * 1024 * 1024;
28pub const MAX_QUERY_VALUE_BYTES: i32 = 8 * 1024 * 1024;
29pub const MAX_QUERY_SQL_BYTES: i32 = 1024 * 1024;
30pub const MAX_QUERY_VM_STEPS: usize = 10_000_000;
32pub const MAX_QUERY_DURATION: std::time::Duration = std::time::Duration::from_secs(5);
33
34#[cfg(feature = "sqlite")]
37fn quoted_identifier(name: &str) -> String {
38 format!("\"{}\"", name.replace('"', "\"\""))
39}
40
41pub fn validate_database_name(name: &str) -> anyhow::Result<()> {
51 if name.is_empty() {
52 anyhow::bail!(
53 "Invalid database name: name must not be empty. \
54 Allowed: 1-{} characters from A-Z, a-z, 0-9, '_' and '-'.",
55 MAX_DATABASE_NAME_LEN
56 );
57 }
58 if name.len() > MAX_DATABASE_NAME_LEN {
59 anyhow::bail!(
60 "Invalid database name '{}': {} bytes exceeds the {}-byte limit.",
61 name,
62 name.len(),
63 MAX_DATABASE_NAME_LEN
64 );
65 }
66 if let Some(bad) = name
67 .chars()
68 .find(|c| !(c.is_ascii_alphanumeric() || *c == '_' || *c == '-'))
69 {
70 anyhow::bail!(
71 "Invalid database name '{}': character {:?} is not allowed. \
72 A database name may contain only A-Z, a-z, 0-9, '_' and '-' \
73 (it becomes the filename './netget_db_<name>.db').",
74 name,
75 bad
76 );
77 }
78 Ok(())
79}
80
81pub fn database_file_path(name: &str) -> anyhow::Result<String> {
86 validate_database_name(name)?;
87 Ok(format!("./netget_db_{}.db", name))
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
92pub struct DatabaseId(u32);
93
94impl DatabaseId {
95 pub fn new(id: u32) -> Self {
97 Self(id)
98 }
99
100 pub fn as_u32(&self) -> u32 {
102 self.0
103 }
104
105 pub fn from_string(s: &str) -> Option<Self> {
107 let s = s.trim();
108 let id_str = s.strip_prefix("db-").unwrap_or(s);
109 id_str.parse::<u32>().ok().map(Self)
110 }
111}
112
113impl std::fmt::Display for DatabaseId {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 write!(f, "db-{}", self.0)
116 }
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
121pub enum DatabaseOwner {
122 Server(ServerId),
124 Client(ClientId),
126 Global,
128}
129
130impl std::fmt::Display for DatabaseOwner {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 match self {
133 Self::Server(id) => write!(f, "Server {}", id),
134 Self::Client(id) => write!(f, "Client {}", id),
135 Self::Global => write!(f, "Global"),
136 }
137 }
138}
139
140#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
142pub struct TableSchema {
143 pub name: String,
145 pub columns: Vec<String>,
147 pub row_count: u64,
149}
150
151#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
153pub struct DatabaseInstance {
154 pub id: DatabaseId,
156 pub name: String,
158 pub path: String,
160 pub owner: DatabaseOwner,
162 pub tables: Vec<TableSchema>,
164 #[serde(skip, default = "Instant::now")]
166 pub created_at: Instant,
167 #[serde(skip, default)]
169 pub last_query_at: Option<Instant>,
170 pub query_count: u64,
172}
173
174impl DatabaseInstance {
175 #[cfg(feature = "sqlite")]
177 pub fn new(id: DatabaseId, name: String, path: String, owner: DatabaseOwner) -> Self {
178 Self {
179 id,
180 name,
181 path,
182 owner,
183 tables: Vec::new(),
184 created_at: Instant::now(),
185 last_query_at: None,
186 query_count: 0,
187 }
188 }
189
190 pub fn is_memory(&self) -> bool {
192 self.path == ":memory:"
193 }
194
195 #[cfg(feature = "sqlite")]
197 pub fn refresh_schema(&mut self, conn: &Connection) -> Result<()> {
198 self.tables.clear();
199
200 let mut stmt = conn.prepare(
202 "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name"
203 )?;
204 let table_names: Vec<String> = stmt
205 .query_map([], |row| row.get(0))?
206 .collect::<rusqlite::Result<_>>()?;
207
208 for table_name in table_names {
210 let mut columns = Vec::new();
211
212 let quoted_name = quoted_identifier(&table_name);
214 let mut stmt = conn.prepare(&format!("PRAGMA table_info({quoted_name})"))?;
215 let rows = stmt.query_map([], |row| {
216 let name: String = row.get(1)?;
217 let type_name: String = row.get(2)?;
218 let not_null: i32 = row.get(3)?;
219 let pk: i32 = row.get(5)?;
220
221 let mut col_def = format!("{} {}", name, type_name);
222 if pk > 0 {
223 col_def.push_str(" PRIMARY KEY");
224 } else if not_null != 0 {
225 col_def.push_str(" NOT NULL");
226 }
227
228 Ok(col_def)
229 })?;
230
231 for row in rows {
232 columns.push(row?);
233 }
234
235 let row_count: u64 =
237 conn.query_row(&format!("SELECT COUNT(*) FROM {quoted_name}"), [], |row| {
238 row.get(0)
239 })?;
240
241 self.tables.push(TableSchema {
242 name: table_name,
243 columns,
244 row_count,
245 });
246 }
247
248 Ok(())
249 }
250
251 pub fn schema_summary(&self) -> String {
253 if self.tables.is_empty() {
254 return format!("{} ({}): No tables", self.name, self.id);
255 }
256
257 let mut summary = format!("{} ({}):\n", self.name, self.id);
258 for table in &self.tables {
259 summary.push_str(&format!(" - {} ({} rows)\n", table.name, table.row_count));
260 for column in &table.columns {
261 summary.push_str(&format!(" {}\n", column));
262 }
263 }
264 summary
265 }
266
267 #[cfg(feature = "sqlite")]
269 pub fn record_query(&mut self) {
270 self.query_count += 1;
271 self.last_query_at = Some(Instant::now());
272 }
273
274 #[cfg(feature = "sqlite")]
276 pub fn update_row_counts(&mut self, conn: &Connection) -> Result<()> {
277 for table in &mut self.tables {
278 table.row_count = conn.query_row(
279 &format!("SELECT COUNT(*) FROM {}", quoted_identifier(&table.name)),
280 [],
281 |row| row.get(0),
282 )?;
283 }
284 Ok(())
285 }
286}
287
288#[cfg(feature = "sqlite")]
290pub struct DatabaseConnection {
291 conn: Mutex<Connection>,
293 instance: DatabaseInstance,
295}
296
297#[cfg(feature = "sqlite")]
306fn filesystem_authorizer(ctx: rusqlite::hooks::AuthContext<'_>) -> rusqlite::hooks::Authorization {
307 use rusqlite::hooks::{AuthAction, Authorization};
308 match ctx.action {
309 AuthAction::Attach { filename } => {
310 if filename.is_empty() || filename == MEMORY_DATABASE_PATH {
311 Authorization::Allow
312 } else {
313 Authorization::Deny
314 }
315 }
316 AuthAction::Unknown { code, .. } if code == rusqlite::ffi::SQLITE_ATTACH => {
317 Authorization::Deny
318 }
319 AuthAction::Function { function_name }
320 if function_name.eq_ignore_ascii_case("load_extension") =>
321 {
322 Authorization::Deny
323 }
324 _ => Authorization::Allow,
325 }
326}
327
328#[cfg(feature = "sqlite")]
329impl DatabaseConnection {
330 pub fn new(instance: DatabaseInstance) -> Result<Self> {
332 let conn =
333 Connection::open(&instance.path).context("Failed to open database connection")?;
334
335 conn.authorizer(Some(filesystem_authorizer));
343
344 conn.set_limit(
345 rusqlite::limits::Limit::SQLITE_LIMIT_LENGTH,
346 MAX_QUERY_VALUE_BYTES,
347 )
348 .context("Failed to set SQLite value size limit")?;
349 conn.set_limit(
350 rusqlite::limits::Limit::SQLITE_LIMIT_SQL_LENGTH,
351 MAX_QUERY_SQL_BYTES,
352 )
353 .context("Failed to set SQLite SQL size limit")?;
354 Ok(Self {
355 conn: Mutex::new(conn),
356 instance,
357 })
358 }
359
360 pub fn instance(&self) -> &DatabaseInstance {
362 &self.instance
363 }
364
365 pub fn instance_mut(&mut self) -> &mut DatabaseInstance {
367 &mut self.instance
368 }
369
370 pub fn execute_query(&mut self, sql: &str) -> Result<QueryResult> {
377 let conn = self.conn.lock().unwrap_or_else(|e| e.into_inner());
378
379 self.instance.record_query();
381
382 let started = std::time::Instant::now();
383 let mut steps = 0usize;
384 conn.progress_handler(
385 1000,
386 Some(move || {
387 steps += 1000;
388 steps >= MAX_QUERY_VM_STEPS || started.elapsed() >= MAX_QUERY_DURATION
389 }),
390 );
391 let result = (|| -> Result<QueryResult> {
392 use rusqlite::fallible_iterator::FallibleIterator;
393 let mut batch = rusqlite::Batch::new(&conn, sql);
394 let mut stmt = batch.next()?.context("query contains no SQL statement")?;
395 anyhow::ensure!(
396 batch.next()?.is_none(),
397 "execute_query accepts exactly one SQL statement"
398 );
399 let readonly = stmt.readonly();
400 let result = if stmt.column_count() > 0 {
401 let column_names: Vec<String> = stmt
402 .column_names()
403 .iter()
404 .map(|name| name.to_string())
405 .collect();
406 let mut bytes: usize = column_names
407 .iter()
408 .map(|name| name.len().saturating_mul(6).saturating_add(3))
409 .sum();
410 let mut rows = Vec::new();
411 let mut query_rows = stmt.query([])?;
412 while let Some(row) = query_rows.next()? {
413 anyhow::ensure!(rows.len() < MAX_QUERY_ROWS, "query result exceeds {} rows; use LIMIT (a modifying statement may already have executed)", MAX_QUERY_ROWS);
414 let mut values = Vec::with_capacity(column_names.len());
415 bytes = bytes.saturating_add(2);
416 for i in 0..column_names.len() {
417 use rusqlite::types::ValueRef;
418 let value = row.get_ref(i)?;
419 let cost = match value {
420 ValueRef::Text(text) => text.len().saturating_mul(6).saturating_add(3),
421 ValueRef::Blob(blob) => blob.len().saturating_mul(2).saturating_add(3),
422 _ => 32,
423 };
424 bytes = bytes.saturating_add(cost);
425 anyhow::ensure!(bytes <= MAX_QUERY_RESULT_BYTES, "query result exceeds {} bytes; select fewer/smaller values (a modifying statement may already have executed)", MAX_QUERY_RESULT_BYTES);
426 values.push(match value {
427 ValueRef::Null => serde_json::Value::Null,
428 ValueRef::Integer(value) => serde_json::json!(value),
429 ValueRef::Real(value) => serde_json::json!(value),
430 ValueRef::Text(value) => serde_json::Value::String(
431 String::from_utf8_lossy(value).into_owned(),
432 ),
433 ValueRef::Blob(value) => serde_json::Value::String(hex::encode(value)),
434 });
435 }
436 rows.push(values);
437 }
438 QueryResult::Select {
439 columns: column_names,
440 rows,
441 }
442 } else {
443 QueryResult::Modified {
444 affected_rows: stmt.execute([])?,
445 }
446 };
447 drop(stmt);
448 if !readonly {
451 self.instance.refresh_schema(&conn)?;
452 }
453 Ok(result)
454 })();
455 conn.progress_handler(0, None::<fn() -> bool>);
456 result.map_err(|error| anyhow::anyhow!("SQLite query failed: {error:#}"))
457 }
458
459 pub fn refresh_schema(&mut self) -> Result<()> {
461 let conn = self.conn.lock().unwrap_or_else(|e| e.into_inner());
462 self.instance.refresh_schema(&conn)
463 }
464}
465
466#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
468#[serde(tag = "type")]
469pub enum QueryResult {
470 Select {
472 columns: Vec<String>,
474 rows: Vec<Vec<serde_json::Value>>,
476 },
477 Modified {
479 affected_rows: usize,
481 },
482}
483
484impl QueryResult {
485 pub fn format(&self) -> String {
487 match self {
488 Self::Select { columns, rows } => {
489 if rows.is_empty() {
490 return format!("No rows returned. Columns: {}", columns.join(", "));
491 }
492
493 let mut output = String::new();
494 output.push_str(&columns.join(" | "));
495 output.push('\n');
496 output.push_str(&"-".repeat(output.len()));
497 output.push('\n');
498
499 for row in rows {
500 let row_str: Vec<String> = row
501 .iter()
502 .map(|v| match v {
503 serde_json::Value::Null => "NULL".to_string(),
504 serde_json::Value::String(s) => s.clone(),
505 other => other.to_string(),
506 })
507 .collect();
508 output.push_str(&row_str.join(" | "));
509 output.push('\n');
510 }
511
512 output
513 }
514 Self::Modified { affected_rows } => {
515 format!("{} row(s) affected", affected_rows)
516 }
517 }
518 }
519
520 pub fn row_count(&self) -> usize {
522 match self {
523 Self::Select { rows, .. } => rows.len(),
524 Self::Modified { affected_rows } => *affected_rows,
525 }
526 }
527}
528
529#[cfg(feature = "sqlite")]
531pub struct DatabaseManager {
532 connections: HashMap<DatabaseId, DatabaseConnection>,
534}
535
536#[cfg(feature = "sqlite")]
537impl DatabaseManager {
538 pub fn new() -> Self {
540 Self {
541 connections: HashMap::new(),
542 }
543 }
544
545 pub fn create_database(
547 &mut self,
548 id: DatabaseId,
549 name: String,
550 path: String,
551 owner: DatabaseOwner,
552 init_sql: Option<&str>,
553 ) -> Result<()> {
554 validate_database_name(&name)?;
563
564 let instance = DatabaseInstance::new(id, name, path, owner);
566
567 let mut conn = DatabaseConnection::new(instance)?;
569
570 if let Some(sql) = init_sql {
572 let db_conn = conn.conn.lock().unwrap_or_else(|e| e.into_inner());
574 db_conn.execute_batch(sql)?;
575 drop(db_conn);
576 }
577
578 conn.refresh_schema()?;
580
581 self.connections.insert(id, conn);
583
584 Ok(())
585 }
586
587 pub fn get_instance(&self, id: DatabaseId) -> Option<&DatabaseInstance> {
589 self.connections.get(&id).map(|conn| conn.instance())
590 }
591
592 pub fn get_all_instances(&self) -> Vec<&DatabaseInstance> {
594 self.connections
595 .values()
596 .map(|conn| conn.instance())
597 .collect()
598 }
599
600 pub fn execute_query(&mut self, id: DatabaseId, sql: &str) -> Result<QueryResult> {
602 let conn = self
603 .connections
604 .get_mut(&id)
605 .context("Database not found")?;
606 conn.execute_query(sql)
607 }
608
609 pub fn delete_database(&mut self, id: DatabaseId) -> Result<()> {
611 let conn = self.connections.remove(&id).context("Database not found")?;
612
613 if !conn.instance().is_memory() {
615 let path = PathBuf::from(&conn.instance().path);
616 if path.exists() {
617 std::fs::remove_file(&path).context("Failed to delete database file")?;
618 }
619 }
620
621 Ok(())
622 }
623
624 pub fn get_databases_by_server(&self, server_id: ServerId) -> Vec<&DatabaseInstance> {
626 self.connections
627 .values()
628 .map(|conn| conn.instance())
629 .filter(|instance| instance.owner == DatabaseOwner::Server(server_id))
630 .collect()
631 }
632
633 pub fn get_databases_by_client(&self, client_id: ClientId) -> Vec<&DatabaseInstance> {
635 self.connections
636 .values()
637 .map(|conn| conn.instance())
638 .filter(|instance| instance.owner == DatabaseOwner::Client(client_id))
639 .collect()
640 }
641
642 pub fn delete_databases_by_server(&mut self, server_id: ServerId) -> Result<()> {
644 let db_ids: Vec<DatabaseId> = self
645 .connections
646 .values()
647 .filter(|conn| conn.instance().owner == DatabaseOwner::Server(server_id))
648 .map(|conn| conn.instance().id)
649 .collect();
650
651 for id in db_ids {
652 self.delete_database(id)?;
653 }
654
655 Ok(())
656 }
657
658 pub fn delete_databases_by_client(&mut self, client_id: ClientId) -> Result<()> {
660 let db_ids: Vec<DatabaseId> = self
661 .connections
662 .values()
663 .filter(|conn| conn.instance().owner == DatabaseOwner::Client(client_id))
664 .map(|conn| conn.instance().id)
665 .collect();
666
667 for id in db_ids {
668 self.delete_database(id)?;
669 }
670
671 Ok(())
672 }
673}
674
675#[cfg(not(feature = "sqlite"))]
676pub struct DatabaseManager;
677
678#[cfg(not(feature = "sqlite"))]
679impl DatabaseManager {
680 pub fn new() -> Self {
681 Self
682 }
683}