use regex::Regex;
use std::sync::OnceLock;
static SQL_PATTERNS: OnceLock<SqlPatterns> = OnceLock::new();
struct SqlPatterns {
select: Regex,
insert: Regex,
update: Regex,
delete: Regex,
constraint: Regex,
column_name: Regex,
table_name: Regex,
}
fn get_patterns() -> &'static SqlPatterns {
SQL_PATTERNS.get_or_init(|| SqlPatterns {
select: Regex::new(r"(?i)SELECT\s+[\s\S]*?FROM\s+[\s\S]*?(?:WHERE|LIMIT|ORDER|GROUP|$)")
.unwrap(),
insert: Regex::new(r"(?i)INSERT\s+INTO\s+[\s\S]*?(?:VALUES|SELECT|RETURNING|$)").unwrap(),
update: Regex::new(r"(?i)UPDATE\s+[\s\S]*?SET\s+[\s\S]*?(?:WHERE|RETURNING|$)").unwrap(),
delete: Regex::new(r"(?i)DELETE\s+FROM\s+[\s\S]*?(?:WHERE|RETURNING|$)").unwrap(),
constraint: Regex::new(r#"constraint\s+"([^"]+)""#).unwrap(),
column_name: Regex::new(r#"column\s+"?([a-zA-Z_][a-zA-Z0-9_]*)"?"#).unwrap(),
table_name: Regex::new(r#"(?:relation|table)\s+"?([a-zA-Z_][a-zA-Z0-9_]*)"?"#).unwrap(),
})
}
#[derive(Debug, Clone, Default)]
pub struct ExtractedInfo {
pub constraint_name: Option<String>,
pub column_name: Option<String>,
pub table_name: Option<String>,
}
pub fn sanitize_error_message(error_msg: &str) -> String {
let patterns: &SqlPatterns = get_patterns();
let mut sanitized: String = error_msg.to_string();
sanitized = patterns
.select
.replace_all(&sanitized, "[SQL query removed]")
.to_string();
sanitized = patterns
.insert
.replace_all(&sanitized, "[SQL query removed]")
.to_string();
sanitized = patterns
.update
.replace_all(&sanitized, "[SQL query removed]")
.to_string();
sanitized = patterns
.delete
.replace_all(&sanitized, "[SQL query removed]")
.to_string();
while sanitized.contains("[SQL query removed][SQL query removed]") {
sanitized = sanitized.replace(
"[SQL query removed][SQL query removed]",
"[SQL query removed]",
);
}
sanitized = sanitized
.replace(": [SQL query removed]:", ":")
.replace(": [SQL query removed]", "")
.replace("[SQL query removed]: ", "")
.trim()
.to_string();
if sanitized.is_empty() || sanitized == "[SQL query removed]" {
sanitized = "Database query execution failed".to_string();
}
sanitized
}
pub fn extract_metadata(error_msg: &str) -> ExtractedInfo {
let patterns: &SqlPatterns = get_patterns();
let mut info: ExtractedInfo = ExtractedInfo::default();
if let Some(captures) = patterns.constraint.captures(error_msg)
&& let Some(constraint) = captures.get(1)
{
info.constraint_name = Some(constraint.as_str().to_string());
}
if let Some(captures) = patterns.column_name.captures(error_msg)
&& let Some(column) = captures.get(1)
{
info.column_name = Some(column.as_str().to_string());
}
if let Some(captures) = patterns.table_name.captures(error_msg)
&& let Some(table) = captures.get(1)
{
info.table_name = Some(table.as_str().to_string());
}
info
}
pub fn sanitize_and_extract(error_msg: &str) -> (String, ExtractedInfo) {
let sanitized: String = sanitize_error_message(error_msg);
let metadata: ExtractedInfo = extract_metadata(error_msg);
(sanitized, metadata)
}