Enhance change_falukant_user_money: Implement money clamping logic to ensure updates stay within numeric(10,2) limits, improving data integrity and error handling.
This commit is contained in:
@@ -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::<f64>().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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user