use anyhow::{Context, Result};
use regex::Regex;
use std::fs;
use std::path::Path;
use crate::types::Table;
pub fn parse_sql_file<P: AsRef<Path>>(sql_file_path: P) -> Result<(Vec<Table>, String)> {
let content = fs::read_to_string(sql_file_path)
.context("Failed to read SQL file")?;
let mut tables = Vec::new();
let create_table_regex = Regex::new(r"(?i)(?s)CREATE TABLE\s+(\w+)\s*\((.*?)\)(?:\s|;)")?;
for captures in create_table_regex.captures_iter(&content) {
let table_name = captures.get(1).unwrap().as_str();
let columns_text = captures.get(2).unwrap().as_str();
let columns = parse_table_columns(columns_text);
if !columns.is_empty() {
let num_columns = columns.len();
println!("Found table: {} with {} columns", table_name, num_columns);
tables.push(Table {
name: table_name.to_string(),
columns,
});
}
}
Ok((tables, content))
}
pub fn parse_table_columns(columns_text: &str) -> Vec<String> {
let mut columns = Vec::new();
let joined = columns_text.lines()
.map(|l| l.trim())
.collect::<Vec<_>>()
.join(" ");
for part in joined.split(',') {
let part = part.trim();
if !part.is_empty()
&& !part.starts_with("FOREIGN KEY")
&& !part.starts_with("PRIMARY KEY") {
if let Some(first_word) = part.split_whitespace().next() {
let col_name = first_word.trim();
if !col_name.is_empty() && col_name.chars().all(|c| c.is_alphanumeric() || c == '_') {
columns.push(col_name.to_string());
}
}
}
}
columns
}
pub fn extract_insert_values(content: &str, table_name: &str) -> Result<Vec<Vec<String>>> {
let mut rows = Vec::new();
let patterns = vec![
format!(r#"(?s)INSERT INTO "{}" VALUES\((.*?)\);"#, regex::escape(table_name)),
format!(r"(?s)INSERT INTO {} VALUES\((.*?)\);", regex::escape(table_name)),
format!(r#"(?s)INSERT INTO `{}` VALUES\((.*?)\);"#, regex::escape(table_name)),
];
for pattern in patterns.iter() {
match Regex::new(pattern) {
Ok(insert_regex) => {
let matches = insert_regex.captures_iter(content);
for captures in matches {
let values_str = captures.get(1).unwrap().as_str();
let values_str = handle_replace_function(values_str);
let values = parse_values(&values_str);
rows.push(values);
}
if !rows.is_empty() {
break;
}
}
Err(e) => {
eprintln!("Warning: Failed to compile regex pattern: {}", e);
}
}
}
Ok(rows)
}
fn handle_replace_function(values_str: &str) -> String {
if !values_str.contains("replace(") {
return values_str.to_string();
}
let replace_regex = Regex::new(r"(?s)replace\('(.*?)',.*?\)").unwrap();
replace_regex.replace_all(values_str, |caps: ®ex::Captures| {
let json_content = caps.get(1)
.map_or("", |m| m.as_str())
.replace("\\'", "'")
.replace("\\n", "\n");
format!("'{}'", json_content)
}).to_string()
}
fn parse_values(values_str: &str) -> Vec<String> {
let mut values = Vec::new();
let mut current_value = String::new();
let mut in_quotes = false;
let mut quote_char = ' ';
let chars: Vec<char> = values_str.chars().collect();
let mut i = 0;
while i < chars.len() {
let char = chars[i];
if !in_quotes {
if char == '\'' || char == '"' {
in_quotes = true;
quote_char = char;
current_value.push(char);
} else if char == ',' {
values.push(current_value.trim().to_string());
current_value.clear();
} else {
current_value.push(char);
}
} else {
if char == quote_char {
if i + 1 < chars.len() && chars[i + 1] == quote_char {
current_value.push(char);
current_value.push(char);
i += 1; } else {
in_quotes = false;
quote_char = ' ';
current_value.push(char);
}
} else {
current_value.push(char);
}
}
i += 1;
}
if !current_value.is_empty() {
values.push(current_value.trim().to_string());
}
values.into_iter()
.map(|val| clean_value(val))
.collect()
}
fn clean_value(val: String) -> String {
let val = val.trim();
let cleaned = if (val.starts_with('\'') && val.ends_with('\'')) ||
(val.starts_with('"') && val.ends_with('"')) {
let inner = &val[1..val.len()-1];
inner.replace("''", "'").replace("\"\"", "\"")
} else {
val.to_string()
};
cleaned
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_table_columns() {
let columns_text = r#"
id TEXT PRIMARY KEY,
callSessionId TEXT NOT NULL,
type TEXT NOT NULL,
FOREIGN KEY (callSessionId) REFERENCES CallSession(id)
"#;
let columns = parse_table_columns(columns_text);
assert_eq!(columns, vec!["id", "callSessionId", "type"]);
}
#[test]
fn test_clean_value() {
assert_eq!(clean_value("'test'".to_string()), "test");
assert_eq!(clean_value("\"test\"".to_string()), "test");
assert_eq!(clean_value("'test''s'".to_string()), "test's");
assert_eq!(clean_value("test".to_string()), "test");
}
#[test]
fn test_parse_values() {
let values_str = "'value1', 'value2', 'value''3'";
let values = parse_values(values_str);
assert_eq!(values, vec!["value1", "value2", "value'3"]);
}
}