Extract column mappings from SELECT query with AS aliases
(query: &str, table: &str)
| 2602 | |
| 2603 | /// Extract column mappings from SELECT query with AS aliases |
| 2604 | fn extract_column_mappings_from_query(query: &str, table: &str) -> std::collections::HashMap<String, String> { |
| 2605 | use regex::Regex; |
| 2606 | use std::collections::HashMap; |
| 2607 | |
| 2608 | let mut mappings = HashMap::new(); |
| 2609 | |
| 2610 | debug!("extract_column_mappings_from_query: query='{}', table='{}'", query, table); |
| 2611 | |
| 2612 | // First, try to match patterns like "table.column_name AS alias" |
| 2613 | let table_pattern = Regex::new(&format!( |
| 2614 | r"(?i)\b{}\.(\w+)\s+AS\s+(\w+)", |
| 2615 | regex::escape(table) |
| 2616 | )); |
| 2617 | |
| 2618 | if let Ok(re) = table_pattern { |
| 2619 | debug!("Table pattern regex created: {:?}", re.as_str()); |
| 2620 | let matches_found = re.captures_iter(query).count(); |
| 2621 | debug!("Table pattern matches found: {}", matches_found); |
| 2622 | |
| 2623 | for captures in re.captures_iter(query) { |
| 2624 | if let (Some(source_col), Some(alias)) = (captures.get(1), captures.get(2)) { |
| 2625 | let source_column = source_col.as_str().to_string(); |
| 2626 | let alias_name = alias.as_str().to_string(); |
| 2627 | |
| 2628 | debug!("Column mapping (with table prefix): {} -> {}.{}", alias_name, table, source_column); |
| 2629 | mappings.insert(alias_name, source_column); |
| 2630 | } |
| 2631 | } |
| 2632 | } else { |
| 2633 | debug!("Failed to create table pattern regex"); |
| 2634 | } |
| 2635 | |
| 2636 | // Also match simple patterns like "column_name AS alias" (without table prefix) |
| 2637 | // This is common in queries like "SELECT id AS event_id, created_at AS event_created_at FROM events" |
| 2638 | // BUT we need to be careful not to match the table name in "table.column AS alias" patterns |
| 2639 | let simple_pattern = Regex::new(r"(?i)(?:^|,|\s)(\w+)\s+AS\s+(\w+)"); |
| 2640 | |
| 2641 | if let Ok(re) = simple_pattern { |
| 2642 | for captures in re.captures_iter(query) { |
| 2643 | if let (Some(source_col), Some(alias)) = (captures.get(1), captures.get(2)) { |
| 2644 | let source_column = source_col.as_str().to_string(); |
| 2645 | let alias_name = alias.as_str().to_string(); |
| 2646 | |
| 2647 | // Only add if we haven't already found this alias with a table prefix |
| 2648 | // (table-prefixed mappings are more specific and should take precedence) |
| 2649 | if let std::collections::hash_map::Entry::Vacant(e) = mappings.entry(alias_name.clone()) { |
| 2650 | // Check if this is actually a table name (if the character before it is a dot) |
| 2651 | // We need to look at the full match to see if there's a dot before |
| 2652 | let _full_match = captures.get(0).unwrap().as_str(); |
| 2653 | // Skip if this looks like it's part of a table.column pattern |
| 2654 | // (i.e., the source_column is actually the table name) |
| 2655 | if !query.contains(&format!("{source_column}.{alias_name}")) && |
| 2656 | !query.contains(&format!("{source_column}.")) { |
| 2657 | debug!("Column mapping (simple alias): {} -> {}", alias_name, source_column); |
| 2658 | e.insert(source_column); |
| 2659 | } |
| 2660 | } |
| 2661 | } |
no test coverage detected