Initial commit: Rust YpDaemon

This commit is contained in:
Torsten Schulz (local)
2025-11-21 23:05:34 +01:00
commit d0ec363f09
21 changed files with 8067 additions and 0 deletions

207
src/db/connection.rs Normal file
View File

@@ -0,0 +1,207 @@
use postgres::{Client, NoTls};
use postgres::Error as PgError;
use std::collections::HashMap;
use std::fmt;
use std::sync::{Arc, Condvar, Mutex};
use std::time::Duration;
pub type Row = HashMap<String, String>;
pub type Rows = Vec<Row>;
#[derive(Debug)]
pub struct DbError {
message: String,
}
impl DbError {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
}
impl fmt::Display for DbError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for DbError {}
impl From<PgError> for DbError {
fn from(err: PgError) -> Self {
DbError::new(format!("Postgres-Fehler: {err}"))
}
}
struct Database {
client: Client,
// Name -> SQL
prepared: HashMap<String, String>,
}
impl Database {
fn connect(conn_str: &str) -> Result<Self, DbError> {
let client = Client::connect(conn_str, NoTls)?;
Ok(Self {
client,
prepared: HashMap::new(),
})
}
fn is_valid(&mut self) -> bool {
self.client
.simple_query("SELECT 1")
.map(|_| true)
.unwrap_or(false)
}
#[allow(dead_code)]
fn query(&mut self, sql: &str) -> Result<Rows, DbError> {
let rows = self.client.query(sql, &[])?;
Ok(rows.into_iter().map(Self::row_to_map).collect())
}
fn prepare(&mut self, name: &str, sql: &str) -> Result<(), DbError> {
self.prepared.insert(name.to_string(), sql.to_string());
Ok(())
}
fn execute(
&mut self,
name: &str,
params: &[&(dyn postgres::types::ToSql + Sync)],
) -> Result<Rows, DbError> {
let sql = self
.prepared
.get(name)
.ok_or_else(|| DbError::new(format!("Unbekanntes Statement: {name}")))?;
let rows = self.client.query(sql.as_str(), params)?;
Ok(rows.into_iter().map(Self::row_to_map).collect())
}
fn row_to_map(row: postgres::Row) -> Row {
let mut map = HashMap::with_capacity(row.len());
for (idx, col) in row.columns().iter().enumerate() {
let name = col.name().to_string();
let value: Option<String> = row.get(idx);
map.insert(name, value.unwrap_or_default());
}
map
}
}
struct InnerPool {
connections: Mutex<Vec<Database>>,
available: Condvar,
conn_str: String,
}
impl InnerPool {
fn new(conn_str: String, size: usize) -> Result<Self, DbError> {
let mut connections = Vec::with_capacity(size);
for _ in 0..size {
connections.push(Database::connect(&conn_str)?);
}
Ok(Self {
connections: Mutex::new(connections),
available: Condvar::new(),
conn_str,
})
}
fn get(&self) -> Result<Database, DbError> {
let mut guard = self.connections.lock().unwrap();
loop {
if let Some(mut db) = guard.pop() {
if db.is_valid() {
return Ok(db);
}
// Versuche, eine neue Verbindung aufzubauen
match Database::connect(&self.conn_str) {
Ok(new_db) => return Ok(new_db),
Err(err) => {
eprintln!("[ConnectionPool] Fehler beim Neuaufbau der Verbindung: {err}");
// kurze Pause und erneut versuchen
std::thread::sleep(Duration::from_millis(100));
}
}
} else {
guard = self.available.wait(guard).unwrap();
}
}
}
fn put_back(&self, db: Database) {
let mut guard = self.connections.lock().unwrap();
guard.push(db);
self.available.notify_one();
}
}
#[derive(Clone)]
pub struct ConnectionPool {
inner: Arc<InnerPool>,
}
impl ConnectionPool {
pub fn new(conn_str: String, size: usize) -> Result<Self, DbError> {
let inner = InnerPool::new(conn_str, size)?;
Ok(Self {
inner: Arc::new(inner),
})
}
pub fn get(&self) -> Result<DbConnection, DbError> {
let db = self.inner.get()?;
Ok(DbConnection {
inner: self.inner.clone(),
db: Some(db),
})
}
}
pub struct DbConnection {
inner: Arc<InnerPool>,
db: Option<Database>,
}
impl DbConnection {
#[allow(dead_code)]
pub fn query(&mut self, sql: &str) -> Result<Rows, DbError> {
self.database_mut().query(sql)
}
pub fn prepare(&mut self, name: &str, sql: &str) -> Result<(), DbError> {
self.database_mut().prepare(name, sql)
}
pub fn execute(
&mut self,
name: &str,
params: &[&(dyn postgres::types::ToSql + Sync)],
) -> Result<Rows, DbError> {
self.database_mut().execute(name, params)
}
fn database_mut(&mut self) -> &mut Database {
self.db
.as_mut()
.expect("DbConnection ohne aktive Database verwendet")
}
}
impl Drop for DbConnection {
fn drop(&mut self) {
if let Some(db) = self.db.take() {
self.inner.put_back(db);
}
}
}

5
src/db/mod.rs Normal file
View File

@@ -0,0 +1,5 @@
mod connection;
pub use connection::{ConnectionPool, DbConnection, DbError, Row, Rows};