Verbessere Fehlerbehandlung in SQL-Abfragen: Füge detaillierte Fehlermeldungen für Vorbereitungs- und Ausführungsfehler in den Director- und Politics-Workern hinzu.

This commit is contained in:
Torsten Schulz (local)
2025-12-16 08:52:08 +01:00
parent b45990c1b6
commit 74fee2d4c9
4 changed files with 88 additions and 36 deletions

View File

@@ -92,8 +92,32 @@ impl Database {
.get(name) .get(name)
.ok_or_else(|| DbError::new(format!("Unbekanntes Statement: {name}")))?; .ok_or_else(|| DbError::new(format!("Unbekanntes Statement: {name}")))?;
let rows = self.client.query(sql.as_str(), params)?; match self.client.query(sql.as_str(), params) {
Ok(rows.into_iter().map(Self::row_to_map).collect()) Ok(rows) => Ok(rows.into_iter().map(Self::row_to_map).collect()),
Err(err) => {
if let Some(db_err) = err.as_db_error() {
let code = db_err.code().code().to_string();
let message = db_err.message();
let detail = db_err.detail().unwrap_or_default();
let hint = db_err.hint().unwrap_or_default();
// SQL ggf. kürzen, um Log-Flut zu vermeiden
let mut sql_preview = sql.clone();
const MAX_SQL_PREVIEW: usize = 800;
if sql_preview.len() > MAX_SQL_PREVIEW {
sql_preview.truncate(MAX_SQL_PREVIEW);
sql_preview.push_str("");
}
Err(DbError::new(format!(
"Postgres-Fehler bei Statement '{name}': {} (SQLSTATE: {}, Detail: {}, Hint: {}) | SQL: {}",
message, code, detail, hint, sql_preview
)))
} else {
Err(DbError::new(format!(
"Postgres-Fehler (Client) bei Statement '{name}': {err}"
)))
}
}
}
} }
fn row_to_map(row: postgres::Row) -> Row { fn row_to_map(row: postgres::Row) -> Row {

View File

@@ -740,13 +740,17 @@ impl DirectorWorker {
// Default // Default
let mut cumulative_tax_percent = DEFAULT_TAX_PERCENT; let mut cumulative_tax_percent = DEFAULT_TAX_PERCENT;
conn.prepare("get_branch_region", QUERY_GET_BRANCH_REGION)?; conn.prepare("get_branch_region", QUERY_GET_BRANCH_REGION)
let branch_rows = conn.execute("get_branch_region", &[&branch_id])?; .map_err(|e| DbError::new(format!("[DirectorWorker] prepare get_branch_region: {e}")))?;
let branch_rows = conn.execute("get_branch_region", &[&branch_id])
.map_err(|e| DbError::new(format!("[DirectorWorker] exec get_branch_region branch_id={}: {e}", branch_id)))?;
let branch_region_id: Option<i32> = branch_rows.first().and_then(|r| r.get("region_id")).and_then(|v| v.parse().ok()); let branch_region_id: Option<i32> = branch_rows.first().and_then(|r| r.get("region_id")).and_then(|v| v.parse().ok());
if let Some(region_id) = branch_region_id { if let Some(region_id) = branch_region_id {
conn.prepare("get_user_offices", QUERY_GET_USER_OFFICES)?; conn.prepare("get_user_offices", QUERY_GET_USER_OFFICES)
let offices = conn.execute("get_user_offices", &[&user_id])?; .map_err(|e| DbError::new(format!("[DirectorWorker] prepare get_user_offices: {e}")))?;
let offices = conn.execute("get_user_offices", &[&user_id])
.map_err(|e| DbError::new(format!("[DirectorWorker] exec get_user_offices user_id={}: {e}", user_id)))?;
let mut exempt_types: Vec<String> = Vec::new(); let mut exempt_types: Vec<String> = Vec::new();
let mut has_chancellor = false; let mut has_chancellor = false;
@@ -768,17 +772,21 @@ impl DirectorWorker {
} }
if exempt_types.is_empty() { if exempt_types.is_empty() {
conn.prepare("cumulative_tax_no_exempt", QUERY_CUMULATIVE_TAX_NO_EXEMPT)?; conn.prepare("cumulative_tax_no_exempt", QUERY_CUMULATIVE_TAX_NO_EXEMPT)
let res = conn.execute("cumulative_tax_no_exempt", &[&region_id])?; .map_err(|e| DbError::new(format!("[DirectorWorker] prepare cumulative_tax_no_exempt: {e}")))?;
let res = conn.execute("cumulative_tax_no_exempt", &[&region_id])
.map_err(|e| DbError::new(format!("[DirectorWorker] exec cumulative_tax_no_exempt region_id={}: {e}", region_id)))?;
if let Some(row) = res.first() if let Some(row) = res.first()
&& let Some(tp) = row.get("total_percent") && let Some(tp) = row.get("total_percent")
{ {
cumulative_tax_percent = tp.parse::<f64>().unwrap_or(DEFAULT_TAX_PERCENT); cumulative_tax_percent = tp.parse::<f64>().unwrap_or(DEFAULT_TAX_PERCENT);
} }
} else { } else {
conn.prepare("cumulative_tax_with_exempt", QUERY_CUMULATIVE_TAX_WITH_EXEMPT)?; conn.prepare("cumulative_tax_with_exempt", QUERY_CUMULATIVE_TAX_WITH_EXEMPT)
.map_err(|e| DbError::new(format!("[DirectorWorker] prepare cumulative_tax_with_exempt: {e}")))?;
let exempt_array: Vec<&str> = exempt_types.iter().map(|s| s.as_str()).collect(); let exempt_array: Vec<&str> = exempt_types.iter().map(|s| s.as_str()).collect();
let res = conn.execute("cumulative_tax_with_exempt", &[&region_id, &exempt_array])?; let res = conn.execute("cumulative_tax_with_exempt", &[&region_id, &exempt_array])
.map_err(|e| DbError::new(format!("[DirectorWorker] exec cumulative_tax_with_exempt region_id={} exempt={:?}: {}", region_id, exempt_array, e)))?;
if let Some(row) = res.first() && let Some(tp) = row.get("total_percent") { if let Some(row) = res.first() && let Some(tp) = row.get("total_percent") {
cumulative_tax_percent = tp.parse::<f64>().unwrap_or(DEFAULT_TAX_PERCENT); cumulative_tax_percent = tp.parse::<f64>().unwrap_or(DEFAULT_TAX_PERCENT);
} }

View File

@@ -165,8 +165,9 @@ impl PoliticsWorker {
conn.prepare( conn.prepare(
"count_offices_per_region", "count_offices_per_region",
QUERY_COUNT_OFFICES_PER_REGION, QUERY_COUNT_OFFICES_PER_REGION,
)?; ).map_err(|e| DbError::new(format!("[PoliticsWorker] prepare count_offices_per_region: {e}")))?;
let rows = conn.execute("count_offices_per_region", &[])?; let rows = conn.execute("count_offices_per_region", &[])
.map_err(|e| DbError::new(format!("[PoliticsWorker] exec count_offices_per_region: {e}")))?;
let mut result = Vec::with_capacity(rows.len()); let mut result = Vec::with_capacity(rows.len());
for row in rows { for row in rows {
@@ -202,7 +203,7 @@ impl PoliticsWorker {
conn.prepare( conn.prepare(
"trim_excess_offices_global", "trim_excess_offices_global",
QUERY_TRIM_EXCESS_OFFICES_GLOBAL, QUERY_TRIM_EXCESS_OFFICES_GLOBAL,
)?; ).map_err(|e| DbError::new(format!("[PoliticsWorker] prepare trim_excess_offices_global: {e}")))?;
if let Err(err) = conn.execute("trim_excess_offices_global", &[]) { if let Err(err) = conn.execute("trim_excess_offices_global", &[]) {
eprintln!( eprintln!(
@@ -229,8 +230,10 @@ impl PoliticsWorker {
.get() .get()
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
conn.prepare("find_office_gaps", QUERY_FIND_OFFICE_GAPS)?; conn.prepare("find_office_gaps", QUERY_FIND_OFFICE_GAPS)
let rows = conn.execute("find_office_gaps", &[])?; .map_err(|e| DbError::new(format!("[PoliticsWorker] prepare find_office_gaps: {e}")))?;
let rows = conn.execute("find_office_gaps", &[])
.map_err(|e| DbError::new(format!("[PoliticsWorker] exec find_office_gaps: {e}")))?;
if rows.is_empty() { if rows.is_empty() {
return Ok(()); return Ok(());
@@ -312,8 +315,10 @@ impl PoliticsWorker {
.get() .get()
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
conn.prepare("select_needed_elections", QUERY_SELECT_NEEDED_ELECTIONS)?; conn.prepare("select_needed_elections", QUERY_SELECT_NEEDED_ELECTIONS)
let rows = conn.execute("select_needed_elections", &[])?; .map_err(|e| DbError::new(format!("[PoliticsWorker] prepare select_needed_elections: {e}")))?;
let rows = conn.execute("select_needed_elections", &[])
.map_err(|e| DbError::new(format!("[PoliticsWorker] exec select_needed_elections: {e}")))?;
let mut elections = Vec::with_capacity(rows.len()); let mut elections = Vec::with_capacity(rows.len());
for row in rows { for row in rows {
@@ -342,8 +347,9 @@ impl PoliticsWorker {
conn.prepare( conn.prepare(
"select_elections_needing_candidates", "select_elections_needing_candidates",
QUERY_SELECT_ELECTIONS_NEEDING_CANDIDATES, QUERY_SELECT_ELECTIONS_NEEDING_CANDIDATES,
)?; ).map_err(|e| DbError::new(format!("[PoliticsWorker] prepare select_elections_needing_candidates: {e}")))?;
let rows = conn.execute("select_elections_needing_candidates", &[])?; let rows = conn.execute("select_elections_needing_candidates", &[])
.map_err(|e| DbError::new(format!("[PoliticsWorker] exec select_elections_needing_candidates: {e}")))?;
let mut elections = Vec::with_capacity(rows.len()); let mut elections = Vec::with_capacity(rows.len());
for row in rows { for row in rows {
@@ -370,13 +376,14 @@ impl PoliticsWorker {
.get() .get()
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
conn.prepare("insert_candidates", QUERY_INSERT_CANDIDATES)?; conn.prepare("insert_candidates", QUERY_INSERT_CANDIDATES)
.map_err(|e| DbError::new(format!("[PoliticsWorker] prepare insert_candidates: {e}")))?;
for e in elections { for e in elections {
conn.execute( conn.execute(
"insert_candidates", "insert_candidates",
&[&e.election_id, &e.region_id, &e.posts_to_fill], &[&e.election_id, &e.region_id, &e.posts_to_fill],
)?; ).map_err(|err| DbError::new(format!("[PoliticsWorker] exec insert_candidates eid={} rid={}: {}", e.election_id, e.region_id, err)))?;
} }
Ok(()) Ok(())
@@ -389,8 +396,10 @@ impl PoliticsWorker {
.get() .get()
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
conn.prepare("process_expired_and_fill", QUERY_PROCESS_EXPIRED_AND_FILL)?; conn.prepare("process_expired_and_fill", QUERY_PROCESS_EXPIRED_AND_FILL)
let rows = conn.execute("process_expired_and_fill", &[])?; .map_err(|e| DbError::new(format!("[PoliticsWorker] prepare process_expired_and_fill: {e}")))?;
let rows = conn.execute("process_expired_and_fill", &[])
.map_err(|e| DbError::new(format!("[PoliticsWorker] exec process_expired_and_fill: {e}")))?;
Ok(rows Ok(rows
.into_iter() .into_iter()
@@ -433,14 +442,17 @@ impl PoliticsWorker {
.get() .get()
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
conn.prepare("notify_office_expiration", QUERY_NOTIFY_OFFICE_EXPIRATION)?; conn.prepare("notify_office_expiration", QUERY_NOTIFY_OFFICE_EXPIRATION)
conn.execute("notify_office_expiration", &[])?; .map_err(|e| DbError::new(format!("[PoliticsWorker] prepare notify_office_expiration: {e}")))?;
conn.execute("notify_office_expiration", &[])
.map_err(|e| DbError::new(format!("[PoliticsWorker] exec notify_office_expiration: {e}")))?;
conn.prepare( conn.prepare(
"get_users_with_expiring_offices", "get_users_with_expiring_offices",
QUERY_GET_USERS_WITH_EXPIRING_OFFICES, QUERY_GET_USERS_WITH_EXPIRING_OFFICES,
)?; ).map_err(|e| DbError::new(format!("[PoliticsWorker] prepare get_users_with_expiring_offices: {e}")))?;
let rows = conn.execute("get_users_with_expiring_offices", &[])?; let rows = conn.execute("get_users_with_expiring_offices", &[])
.map_err(|e| DbError::new(format!("[PoliticsWorker] exec get_users_with_expiring_offices: {e}")))?;
for row in rows { for row in rows {
if let Some(user_id) = row.get("user_id").and_then(|v| v.parse::<i32>().ok()) { if let Some(user_id) = row.get("user_id").and_then(|v| v.parse::<i32>().ok()) {
@@ -462,17 +474,20 @@ impl PoliticsWorker {
.get() .get()
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
conn.prepare("notify_election_created", QUERY_NOTIFY_ELECTION_CREATED)?; conn.prepare("notify_election_created", QUERY_NOTIFY_ELECTION_CREATED)
.map_err(|e| DbError::new(format!("[PoliticsWorker] prepare notify_election_created: {e}")))?;
for uid in user_ids { for uid in user_ids {
conn.execute("notify_election_created", &[uid])?; conn.execute("notify_election_created", &[uid])
.map_err(|e| DbError::new(format!("[PoliticsWorker] exec notify_election_created uid={}: {}", uid, e)))?;
} }
conn.prepare( conn.prepare(
"get_users_in_regions_with_elections", "get_users_in_regions_with_elections",
QUERY_GET_USERS_IN_REGIONS_WITH_ELECTIONS, QUERY_GET_USERS_IN_REGIONS_WITH_ELECTIONS,
)?; ).map_err(|e| DbError::new(format!("[PoliticsWorker] prepare get_users_in_regions_with_elections: {e}")))?;
let rows = conn.execute("get_users_in_regions_with_elections", &[])?; let rows = conn.execute("get_users_in_regions_with_elections", &[])
.map_err(|e| DbError::new(format!("[PoliticsWorker] exec get_users_in_regions_with_elections: {e}")))?;
for row in rows { for row in rows {
if let Some(user_id) = row.get("user_id").and_then(|v| v.parse::<i32>().ok()) { if let Some(user_id) = row.get("user_id").and_then(|v| v.parse::<i32>().ok()) {
@@ -494,7 +509,8 @@ impl PoliticsWorker {
.get() .get()
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
conn.prepare("notify_office_filled", QUERY_NOTIFY_OFFICE_FILLED)?; conn.prepare("notify_office_filled", QUERY_NOTIFY_OFFICE_FILLED)
.map_err(|e| DbError::new(format!("[PoliticsWorker] prepare notify_office_filled: {e}")))?;
for office in new_offices { for office in new_offices {
// Debug-Logging mit allen Feldern, damit sie aktiv genutzt werden // Debug-Logging mit allen Feldern, damit sie aktiv genutzt werden
@@ -502,14 +518,16 @@ impl PoliticsWorker {
"[PoliticsWorker] Office filled: id={}, type={}, character={}, region={}", "[PoliticsWorker] Office filled: id={}, type={}, character={}, region={}",
office.office_id, office.office_type_id, office.character_id, office.region_id office.office_id, office.office_type_id, office.character_id, office.region_id
); );
conn.execute("notify_office_filled", &[&office.character_id])?; conn.execute("notify_office_filled", &[&office.character_id])
.map_err(|e| DbError::new(format!("[PoliticsWorker] exec notify_office_filled office_id={} character_id={}: {}", office.office_id, office.character_id, e)))?;
} }
conn.prepare( conn.prepare(
"get_users_with_filled_offices", "get_users_with_filled_offices",
QUERY_GET_USERS_WITH_FILLED_OFFICES, QUERY_GET_USERS_WITH_FILLED_OFFICES,
)?; ).map_err(|e| DbError::new(format!("[PoliticsWorker] prepare get_users_with_filled_offices: {e}")))?;
let rows = conn.execute("get_users_with_filled_offices", &[])?; let rows = conn.execute("get_users_with_filled_offices", &[])
.map_err(|e| DbError::new(format!("[PoliticsWorker] exec get_users_with_filled_offices: {e}")))?;
for row in rows { for row in rows {
if let Some(user_id) = row.get("user_id").and_then(|v| v.parse::<i32>().ok()) { if let Some(user_id) = row.get("user_id").and_then(|v| v.parse::<i32>().ok()) {

View File

@@ -265,7 +265,9 @@ FROM falukant_data.political_office po
JOIN falukant_type.political_office_type pot ON pot.id = po.office_type_id JOIN falukant_type.political_office_type pot ON pot.id = po.office_type_id
JOIN falukant_data.region r ON r.id = po.region_id JOIN falukant_data.region r ON r.id = po.region_id
JOIN falukant_type.region rt ON rt.id = r.region_type_id JOIN falukant_type.region rt ON rt.id = r.region_type_id
WHERE po.holder_id = $1 AND (po.end_date IS NULL OR po.end_date > NOW()); JOIN falukant_data.character ch ON ch.id = po.character_id
WHERE ch.user_id = $1
AND (po.created_at + (pot.term_length * INTERVAL '1 day')) > NOW();
"#; "#;
pub const QUERY_CUMULATIVE_TAX_NO_EXEMPT: &str = r#" pub const QUERY_CUMULATIVE_TAX_NO_EXEMPT: &str = r#"