Robust SQL extraction: handles explanations, markdown, prefixes

clean_sql now uses 3 strategies in priority order:
1. Extract from ```sql...``` markdown blocks
2. Find first SELECT/WITH/INSERT statement in text
3. Strip leading "sql" keyword fallback

Tested against 5 real model output patterns:
- Clean SQL ✓
- "sql" prefixed ✓
- Markdown fenced ✓
- Explanation before ```sql block ✓
- Explanation with SELECT buried in text ✓

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
root
2026-03-27 20:42:11 -05:00
co-authored by Claude Opus 4.6
parent 34c03894ae
commit 0bd753294b
5 changed files with 195 additions and 13 deletions
+37 -13
View File
@@ -1025,23 +1025,47 @@ fn ResultsTable(response: QueryResponse) -> Element {
}
}
/// Clean AI-generated SQL: strip markdown fences, leading "sql" keyword, explanations.
/// Clean AI-generated SQL: extract only the SQL query, strip everything else.
fn clean_sql(raw: &str) -> String {
let mut s = raw.trim().to_string();
// Remove markdown code fences
s = s.trim_start_matches("```sql").trim_start_matches("```").trim_end_matches("```").trim().to_string();
// Remove leading "sql" keyword on its own line
let lines: Vec<&str> = s.lines().collect();
if let Some(first) = lines.first() {
if first.trim().eq_ignore_ascii_case("sql") || first.trim().eq_ignore_ascii_case("sql;") {
s = lines[1..].join("\n").trim().to_string();
let s = raw.trim();
// Strategy 1: If there's a ```sql...``` block, extract just that
if let Some(start) = s.find("```sql") {
let after = &s[start + 6..];
if let Some(end) = after.find("```") {
return after[..end].trim().to_string();
}
}
// If the model added explanation after the SQL, keep only up to the first semicolon line
if let Some(pos) = s.find(";\n\n") {
s = s[..pos + 1].to_string();
if let Some(start) = s.find("```") {
let after = &s[start + 3..];
if let Some(end) = after.find("```") {
let inner = after[..end].trim();
// Skip leading "sql" keyword
let inner = inner.strip_prefix("sql").map(|s| s.trim_start()).unwrap_or(inner);
return inner.to_string();
}
}
s
// Strategy 2: Find the first SELECT/WITH/INSERT/UPDATE/DELETE statement
let upper = s.to_uppercase();
for keyword in &["SELECT", "WITH", "INSERT", "UPDATE", "DELETE"] {
if let Some(pos) = upper.find(keyword) {
let sql_part = &s[pos..];
// Take up to the first semicolon (or end)
let end = sql_part.find(';').map(|p| p + 1).unwrap_or(sql_part.len());
return sql_part[..end].trim().to_string();
}
}
// Strategy 3: Strip leading "sql" and clean up
let mut result = s.to_string();
let lines: Vec<&str> = result.lines().collect();
if let Some(first) = lines.first() {
if first.trim().eq_ignore_ascii_case("sql") {
result = lines[1..].join("\n").trim().to_string();
}
}
result
}
fn format_cell(val: Option<&serde_json::Value>) -> String {