Initial commit: Rust YpDaemon
This commit is contained in:
207
src/db/connection.rs
Normal file
207
src/db/connection.rs
Normal 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
5
src/db/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
mod connection;
|
||||
|
||||
pub use connection::{ConnectionPool, DbConnection, DbError, Row, Rows};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user