From 74fee2d4c9ccdfd8ba91c3f9d79d0c00a7a6136d Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Tue, 16 Dec 2025 08:52:08 +0100 Subject: [PATCH] =?UTF-8?q?Verbessere=20Fehlerbehandlung=20in=20SQL-Abfrag?= =?UTF-8?q?en:=20F=C3=BCge=20detaillierte=20Fehlermeldungen=20f=C3=BCr=20V?= =?UTF-8?q?orbereitungs-=20und=20Ausf=C3=BChrungsfehler=20in=20den=20Direc?= =?UTF-8?q?tor-=20und=20Politics-Workern=20hinzu.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/connection.rs | 28 +++++++++++++++-- src/worker/director.rs | 24 ++++++++++----- src/worker/politics.rs | 68 ++++++++++++++++++++++++++---------------- src/worker/sql.rs | 4 ++- 4 files changed, 88 insertions(+), 36 deletions(-) diff --git a/src/db/connection.rs b/src/db/connection.rs index 8e1b049..ecdddaf 100644 --- a/src/db/connection.rs +++ b/src/db/connection.rs @@ -92,8 +92,32 @@ impl Database { .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()) + match self.client.query(sql.as_str(), params) { + 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 { diff --git a/src/worker/director.rs b/src/worker/director.rs index 0431e3e..da61ed1 100644 --- a/src/worker/director.rs +++ b/src/worker/director.rs @@ -740,13 +740,17 @@ impl DirectorWorker { // Default let mut cumulative_tax_percent = DEFAULT_TAX_PERCENT; - conn.prepare("get_branch_region", QUERY_GET_BRANCH_REGION)?; - let branch_rows = conn.execute("get_branch_region", &[&branch_id])?; + conn.prepare("get_branch_region", QUERY_GET_BRANCH_REGION) + .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 = branch_rows.first().and_then(|r| r.get("region_id")).and_then(|v| v.parse().ok()); if let Some(region_id) = branch_region_id { - conn.prepare("get_user_offices", QUERY_GET_USER_OFFICES)?; - let offices = conn.execute("get_user_offices", &[&user_id])?; + conn.prepare("get_user_offices", QUERY_GET_USER_OFFICES) + .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 = Vec::new(); let mut has_chancellor = false; @@ -768,17 +772,21 @@ impl DirectorWorker { } if exempt_types.is_empty() { - conn.prepare("cumulative_tax_no_exempt", QUERY_CUMULATIVE_TAX_NO_EXEMPT)?; - let res = conn.execute("cumulative_tax_no_exempt", &[®ion_id])?; + conn.prepare("cumulative_tax_no_exempt", QUERY_CUMULATIVE_TAX_NO_EXEMPT) + .map_err(|e| DbError::new(format!("[DirectorWorker] prepare cumulative_tax_no_exempt: {e}")))?; + let res = conn.execute("cumulative_tax_no_exempt", &[®ion_id]) + .map_err(|e| DbError::new(format!("[DirectorWorker] exec cumulative_tax_no_exempt region_id={}: {e}", region_id)))?; if let Some(row) = res.first() && let Some(tp) = row.get("total_percent") { cumulative_tax_percent = tp.parse::().unwrap_or(DEFAULT_TAX_PERCENT); } } 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 res = conn.execute("cumulative_tax_with_exempt", &[®ion_id, &exempt_array])?; + let res = conn.execute("cumulative_tax_with_exempt", &[®ion_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") { cumulative_tax_percent = tp.parse::().unwrap_or(DEFAULT_TAX_PERCENT); } diff --git a/src/worker/politics.rs b/src/worker/politics.rs index 7ae9f37..5a014c1 100644 --- a/src/worker/politics.rs +++ b/src/worker/politics.rs @@ -165,8 +165,9 @@ impl PoliticsWorker { conn.prepare( "count_offices_per_region", QUERY_COUNT_OFFICES_PER_REGION, - )?; - let rows = conn.execute("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", &[]) + .map_err(|e| DbError::new(format!("[PoliticsWorker] exec count_offices_per_region: {e}")))?; let mut result = Vec::with_capacity(rows.len()); for row in rows { @@ -202,7 +203,7 @@ impl PoliticsWorker { conn.prepare( "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", &[]) { eprintln!( @@ -229,8 +230,10 @@ impl PoliticsWorker { .get() .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; - conn.prepare("find_office_gaps", QUERY_FIND_OFFICE_GAPS)?; - let rows = conn.execute("find_office_gaps", &[])?; + conn.prepare("find_office_gaps", QUERY_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() { return Ok(()); @@ -312,8 +315,10 @@ impl PoliticsWorker { .get() .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; - conn.prepare("select_needed_elections", QUERY_SELECT_NEEDED_ELECTIONS)?; - let rows = conn.execute("select_needed_elections", &[])?; + conn.prepare("select_needed_elections", QUERY_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()); for row in rows { @@ -342,8 +347,9 @@ impl PoliticsWorker { conn.prepare( "select_elections_needing_candidates", QUERY_SELECT_ELECTIONS_NEEDING_CANDIDATES, - )?; - let rows = conn.execute("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", &[]) + .map_err(|e| DbError::new(format!("[PoliticsWorker] exec select_elections_needing_candidates: {e}")))?; let mut elections = Vec::with_capacity(rows.len()); for row in rows { @@ -370,13 +376,14 @@ impl PoliticsWorker { .get() .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 { conn.execute( "insert_candidates", &[&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(()) @@ -389,8 +396,10 @@ impl PoliticsWorker { .get() .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; - conn.prepare("process_expired_and_fill", QUERY_PROCESS_EXPIRED_AND_FILL)?; - let rows = conn.execute("process_expired_and_fill", &[])?; + conn.prepare("process_expired_and_fill", QUERY_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 .into_iter() @@ -433,14 +442,17 @@ impl PoliticsWorker { .get() .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; - conn.prepare("notify_office_expiration", QUERY_NOTIFY_OFFICE_EXPIRATION)?; - conn.execute("notify_office_expiration", &[])?; + conn.prepare("notify_office_expiration", QUERY_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( "get_users_with_expiring_offices", QUERY_GET_USERS_WITH_EXPIRING_OFFICES, - )?; - let rows = conn.execute("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", &[]) + .map_err(|e| DbError::new(format!("[PoliticsWorker] exec get_users_with_expiring_offices: {e}")))?; for row in rows { if let Some(user_id) = row.get("user_id").and_then(|v| v.parse::().ok()) { @@ -462,17 +474,20 @@ impl PoliticsWorker { .get() .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 { - 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( "get_users_in_regions_with_elections", QUERY_GET_USERS_IN_REGIONS_WITH_ELECTIONS, - )?; - let rows = conn.execute("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", &[]) + .map_err(|e| DbError::new(format!("[PoliticsWorker] exec get_users_in_regions_with_elections: {e}")))?; for row in rows { if let Some(user_id) = row.get("user_id").and_then(|v| v.parse::().ok()) { @@ -494,7 +509,8 @@ impl PoliticsWorker { .get() .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 { // Debug-Logging mit allen Feldern, damit sie aktiv genutzt werden @@ -502,14 +518,16 @@ impl PoliticsWorker { "[PoliticsWorker] Office filled: id={}, type={}, character={}, region={}", 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( "get_users_with_filled_offices", QUERY_GET_USERS_WITH_FILLED_OFFICES, - )?; - let rows = conn.execute("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", &[]) + .map_err(|e| DbError::new(format!("[PoliticsWorker] exec get_users_with_filled_offices: {e}")))?; for row in rows { if let Some(user_id) = row.get("user_id").and_then(|v| v.parse::().ok()) { diff --git a/src/worker/sql.rs b/src/worker/sql.rs index 4949a03..8d8e5da 100644 --- a/src/worker/sql.rs +++ b/src/worker/sql.rs @@ -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_data.region r ON r.id = po.region_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#"