From 180431b1300a291cc3dcb60c09abbab31901e7ca Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Mon, 8 Dec 2025 16:34:57 +0100 Subject: [PATCH] Enhance change_falukant_user_money: Implement money clamping logic to ensure updates stay within numeric(10,2) limits, improving data integrity and error handling. --- src/worker/base.rs | 70 ++++++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/src/worker/base.rs b/src/worker/base.rs index fd622cf..8f1a0a5 100644 --- a/src/worker/base.rs +++ b/src/worker/base.rs @@ -158,46 +158,62 @@ impl BaseWorker { ))); } + // We must ensure the resulting money fits in numeric(10,2). + // numeric(10,2) max absolute value is < 10^8 (100_000_000) before rounding. + // Fetch current money for the user and clamp the delta if needed. + const QUERY_GET_MONEY: &str = r#" + SELECT money FROM falukant_data.falukant_user WHERE id = $1; + "#; + conn.prepare("get_money_for_clamp", QUERY_GET_MONEY)?; + let rows = conn.execute("get_money_for_clamp", &[&falukant_user_id])?; + + let current_money: f64 = rows + .get(0) + .and_then(|r| r.get("money")) + .and_then(|v| v.parse::().ok()) + .unwrap_or(0.0); + + // compute tentative result + let tentative = current_money + money_change; + + // numeric(10,2) allows values with absolute < 10^8 (100_000_000) + const MAX_ABS: f64 = 100_000_000.0 - 0.01; // leave room for scale + + let adjusted_money_change = if tentative >= MAX_ABS { + let clipped = MAX_ABS - current_money; + eprintln!( + "[BaseWorker] Clamping money_change: tentative {} exceeds numeric(10,2) max, clipping to {}", + tentative, clipped + ); + clipped + } else if tentative <= -MAX_ABS { + let clipped = -MAX_ABS - current_money; + eprintln!( + "[BaseWorker] Clamping money_change: tentative {} below min, clipping to {}", + tentative, clipped + ); + clipped + } else { + money_change + }; + // Send exact types matching the DB function signature: - // p_falukant_user_id integer, p_money_change numeric, p_activity text let uid_i32: i32 = falukant_user_id; - let money_str = money_change.to_string(); // numeric accepts text + let money_str = format!("{:.2}", adjusted_money_change); let p1: &(dyn ToSql + Sync) = &uid_i32; let p2: &(dyn ToSql + Sync) = &money_str; let p3: &(dyn ToSql + Sync) = &action; - // Minimal logging eprintln!( "[BaseWorker] change_falukant_user_money: update_money(user_id={}, money_change='{}', action={})", uid_i32, money_str, action ); - // Try parameterized call first - match conn.execute("update_money", &[p1, p2, p3]) { - Ok(_) => return Ok(()), - Err(err) => { - eprintln!( - "[BaseWorker] parameterized update_money failed: {err}, falling back to literal SQL", - - ); - // Fall back: build SQL with literals. Escape action safely (double single-quotes). - fn escape_sql_literal(s: &str) -> String { - s.replace('\'', "''") - } + // Execute parameterized + let _ = conn.execute("update_money", &[p1, p2, p3])?; - let escaped_action = escape_sql_literal(action); - // money_str is already a numeric literal string (e.g. "3726" or "1597.12") - let sql = format!( - "SELECT falukant_data.update_money({}, {}::numeric, '{}');", - uid_i32, money_str, escaped_action - ); - - // Use query without parameters - let _ = conn.query(&sql)?; - return Ok(()); - } - } + Ok(()) } }