sql_cli/data/
query_engine.rs

1use anyhow::Result;
2use std::sync::Arc;
3use std::time::Instant;
4use tracing::{debug, info};
5
6use crate::data::arithmetic_evaluator::ArithmeticEvaluator;
7use crate::data::data_view::DataView;
8use crate::data::datatable::{DataColumn, DataRow, DataTable};
9use crate::data::recursive_where_evaluator::RecursiveWhereEvaluator;
10use crate::sql::recursive_parser::{
11    OrderByColumn, Parser, SelectItem, SelectStatement, SortDirection,
12};
13
14/// Query engine that executes SQL directly on DataTable
15pub struct QueryEngine {
16    case_insensitive: bool,
17}
18
19impl QueryEngine {
20    pub fn new() -> Self {
21        Self {
22            case_insensitive: false,
23        }
24    }
25
26    /// Find a column name similar to the given name using edit distance
27    fn find_similar_column(&self, table: &DataTable, name: &str) -> Option<String> {
28        let columns = table.column_names();
29        let mut best_match: Option<(String, usize)> = None;
30
31        for col in columns {
32            let distance = self.edit_distance(&col.to_lowercase(), &name.to_lowercase());
33            // Only suggest if distance is small (likely a typo)
34            // Allow up to 3 edits for longer names
35            let max_distance = if name.len() > 10 { 3 } else { 2 };
36            if distance <= max_distance {
37                match &best_match {
38                    None => best_match = Some((col, distance)),
39                    Some((_, best_dist)) if distance < *best_dist => {
40                        best_match = Some((col, distance));
41                    }
42                    _ => {}
43                }
44            }
45        }
46
47        best_match.map(|(name, _)| name)
48    }
49
50    /// Calculate Levenshtein edit distance between two strings
51    fn edit_distance(&self, s1: &str, s2: &str) -> usize {
52        let len1 = s1.len();
53        let len2 = s2.len();
54        let mut matrix = vec![vec![0; len2 + 1]; len1 + 1];
55
56        for i in 0..=len1 {
57            matrix[i][0] = i;
58        }
59        for j in 0..=len2 {
60            matrix[0][j] = j;
61        }
62
63        for (i, c1) in s1.chars().enumerate() {
64            for (j, c2) in s2.chars().enumerate() {
65                let cost = if c1 == c2 { 0 } else { 1 };
66                matrix[i + 1][j + 1] = std::cmp::min(
67                    matrix[i][j + 1] + 1, // deletion
68                    std::cmp::min(
69                        matrix[i + 1][j] + 1, // insertion
70                        matrix[i][j] + cost,  // substitution
71                    ),
72                );
73            }
74        }
75
76        matrix[len1][len2]
77    }
78
79    pub fn with_case_insensitive(case_insensitive: bool) -> Self {
80        Self { case_insensitive }
81    }
82
83    /// Execute a SQL query on a DataTable and return a DataView (for backward compatibility)
84    pub fn execute(&self, table: Arc<DataTable>, sql: &str) -> Result<DataView> {
85        let start_time = Instant::now();
86
87        // Parse the SQL query
88        let parse_start = Instant::now();
89        let mut parser = Parser::new(sql);
90        let statement = parser
91            .parse()
92            .map_err(|e| anyhow::anyhow!("Parse error: {}", e))?;
93        let parse_duration = parse_start.elapsed();
94
95        // Convert SelectStatement to DataView operations
96        let build_start = Instant::now();
97        let result = self.build_view(table, statement)?;
98        let build_duration = build_start.elapsed();
99
100        let total_duration = start_time.elapsed();
101        info!(
102            "Query execution complete: parse={:?}, build={:?}, total={:?}, rows={}",
103            parse_duration,
104            build_duration,
105            total_duration,
106            result.row_count()
107        );
108
109        Ok(result)
110    }
111
112    /// Build a DataView from a parsed SQL statement
113    fn build_view(&self, table: Arc<DataTable>, statement: SelectStatement) -> Result<DataView> {
114        debug!(
115            "QueryEngine::build_view - select_items: {:?}",
116            statement.select_items
117        );
118        debug!(
119            "QueryEngine::build_view - where_clause: {:?}",
120            statement.where_clause
121        );
122
123        // Start with all rows visible
124        let mut visible_rows: Vec<usize> = (0..table.row_count()).collect();
125
126        // Apply WHERE clause filtering using recursive evaluator
127        if let Some(where_clause) = &statement.where_clause {
128            let total_rows = table.row_count();
129            debug!("QueryEngine: Applying WHERE clause to {} rows", total_rows);
130            debug!("QueryEngine: WHERE clause = {:?}", where_clause);
131
132            let filter_start = Instant::now();
133            // Filter visible rows based on WHERE clause
134            let mut filtered_rows = Vec::new();
135            for row_idx in visible_rows {
136                // Only log for first few rows to avoid performance impact
137                if row_idx < 3 {
138                    debug!("QueryEngine: Evaluating WHERE clause for row {}", row_idx);
139                }
140                let evaluator =
141                    RecursiveWhereEvaluator::with_case_insensitive(&*table, self.case_insensitive);
142                match evaluator.evaluate(where_clause, row_idx) {
143                    Ok(result) => {
144                        if row_idx < 3 {
145                            debug!("QueryEngine: Row {} WHERE result: {}", row_idx, result);
146                        }
147                        if result {
148                            filtered_rows.push(row_idx);
149                        }
150                    }
151                    Err(e) => {
152                        if row_idx < 3 {
153                            debug!(
154                                "QueryEngine: WHERE evaluation error for row {}: {}",
155                                row_idx, e
156                            );
157                        }
158                        // Propagate WHERE clause errors instead of silently ignoring them
159                        return Err(e);
160                    }
161                }
162            }
163            visible_rows = filtered_rows;
164            let filter_duration = filter_start.elapsed();
165            info!(
166                "WHERE clause filtering: {} rows -> {} rows in {:?}",
167                total_rows,
168                visible_rows.len(),
169                filter_duration
170            );
171
172            // Debug log moved to info level above with timing
173        }
174
175        // Create initial DataView with filtered rows
176        let mut view = DataView::new(table.clone());
177        view = view.with_rows(visible_rows);
178
179        // Apply column projection or computed expressions (SELECT clause) - do this AFTER filtering
180        if !statement.select_items.is_empty() {
181            // Check if we have ANY non-star items (not just the first one)
182            let has_non_star_items = statement
183                .select_items
184                .iter()
185                .any(|item| !matches!(item, SelectItem::Star));
186
187            // Apply select items if:
188            // 1. We have computed expressions or explicit columns
189            // 2. OR we have a mix of star and other items (e.g., SELECT *, computed_col)
190            if has_non_star_items || statement.select_items.len() > 1 {
191                view = self.apply_select_items(view, &statement.select_items)?;
192            }
193            // If it's just a single star, no projection needed
194        } else if !statement.columns.is_empty() && statement.columns[0] != "*" {
195            // Fallback to legacy column projection for backward compatibility
196            let column_indices = self.resolve_column_indices(&*table, &statement.columns)?;
197            view = view.with_columns(column_indices);
198        }
199
200        // Apply ORDER BY sorting
201        if let Some(order_by_columns) = &statement.order_by {
202            if !order_by_columns.is_empty() {
203                view = self.apply_multi_order_by(view, order_by_columns)?;
204            }
205        }
206
207        // Apply LIMIT/OFFSET
208        if let Some(limit) = statement.limit {
209            let offset = statement.offset.unwrap_or(0);
210            view = view.with_limit(limit, offset);
211        }
212
213        Ok(view)
214    }
215
216    /// Resolve column names to indices
217    fn resolve_column_indices(&self, table: &DataTable, columns: &[String]) -> Result<Vec<usize>> {
218        let mut indices = Vec::new();
219        let table_columns = table.column_names();
220
221        for col_name in columns {
222            let index = table_columns
223                .iter()
224                .position(|c| c.eq_ignore_ascii_case(col_name))
225                .ok_or_else(|| {
226                    let suggestion = self.find_similar_column(table, col_name);
227                    match suggestion {
228                        Some(similar) => anyhow::anyhow!(
229                            "Column '{}' not found. Did you mean '{}'?",
230                            col_name,
231                            similar
232                        ),
233                        None => anyhow::anyhow!("Column '{}' not found", col_name),
234                    }
235                })?;
236            indices.push(index);
237        }
238
239        Ok(indices)
240    }
241
242    /// Apply SELECT items (columns and computed expressions) to create new view
243    fn apply_select_items(&self, view: DataView, select_items: &[SelectItem]) -> Result<DataView> {
244        debug!(
245            "QueryEngine::apply_select_items - items: {:?}",
246            select_items
247        );
248        debug!(
249            "QueryEngine::apply_select_items - input view has {} rows",
250            view.row_count()
251        );
252
253        // Check if we need to create computed columns
254        let has_computed_expressions = select_items
255            .iter()
256            .any(|item| matches!(item, SelectItem::Expression { .. }));
257
258        debug!(
259            "QueryEngine::apply_select_items - has_computed_expressions: {}",
260            has_computed_expressions
261        );
262
263        if !has_computed_expressions {
264            // Simple case: only columns, use existing projection logic
265            let column_indices = self.resolve_select_columns(view.source(), select_items)?;
266            return Ok(view.with_columns(column_indices));
267        }
268
269        // Complex case: we have computed expressions
270        // IMPORTANT: We create a PROJECTED view, not a new table
271        // This preserves the original DataTable reference
272
273        let source_table = view.source();
274        let visible_rows = view.visible_row_indices();
275
276        // Create a temporary table just for the computed result view
277        // But this table is only used for the current query result
278        let mut computed_table = DataTable::new("query_result");
279
280        // First, expand any Star selectors to actual columns
281        let mut expanded_items = Vec::new();
282        for item in select_items {
283            match item {
284                SelectItem::Star => {
285                    // Expand * to all columns from source table
286                    for col_name in source_table.column_names() {
287                        expanded_items.push(SelectItem::Column(col_name));
288                    }
289                }
290                _ => expanded_items.push(item.clone()),
291            }
292        }
293
294        // Add columns based on expanded SelectItems, handling duplicates
295        let mut column_name_counts: std::collections::HashMap<String, usize> =
296            std::collections::HashMap::new();
297
298        for item in &expanded_items {
299            let base_name = match item {
300                SelectItem::Column(name) => name.clone(),
301                SelectItem::Expression { alias, .. } => alias.clone(),
302                SelectItem::Star => unreachable!("Star should have been expanded"),
303            };
304
305            // Check if this column name has been used before
306            let count = column_name_counts.entry(base_name.clone()).or_insert(0);
307            let column_name = if *count == 0 {
308                // First occurrence, use the name as-is
309                base_name.clone()
310            } else {
311                // Duplicate, append a suffix
312                format!("{}_{}", base_name, count)
313            };
314            *count += 1;
315
316            computed_table.add_column(DataColumn::new(&column_name));
317        }
318
319        // Calculate values for each row
320        let evaluator = ArithmeticEvaluator::new(source_table);
321
322        for &row_idx in visible_rows {
323            let mut row_values = Vec::new();
324
325            for item in &expanded_items {
326                let value = match item {
327                    SelectItem::Column(col_name) => {
328                        // Simple column reference
329                        let col_idx = source_table.get_column_index(col_name).ok_or_else(|| {
330                            let suggestion = self.find_similar_column(source_table, col_name);
331                            match suggestion {
332                                Some(similar) => anyhow::anyhow!(
333                                    "Column '{}' not found. Did you mean '{}'?",
334                                    col_name,
335                                    similar
336                                ),
337                                None => anyhow::anyhow!("Column '{}' not found", col_name),
338                            }
339                        })?;
340                        let row = source_table
341                            .get_row(row_idx)
342                            .ok_or_else(|| anyhow::anyhow!("Row {} not found", row_idx))?;
343                        row.get(col_idx)
344                            .ok_or_else(|| anyhow::anyhow!("Column {} not found in row", col_idx))?
345                            .clone()
346                    }
347                    SelectItem::Expression { expr, .. } => {
348                        // Computed expression
349                        evaluator.evaluate(expr, row_idx)?
350                    }
351                    SelectItem::Star => unreachable!("Star should have been expanded"),
352                };
353                row_values.push(value);
354            }
355
356            computed_table
357                .add_row(DataRow::new(row_values))
358                .map_err(|e| anyhow::anyhow!("Failed to add row: {}", e))?;
359        }
360
361        // Return a view of the computed result
362        // This is a temporary view for this query only
363        Ok(DataView::new(Arc::new(computed_table)))
364    }
365
366    /// Resolve SelectItem columns to indices (for simple column projections only)
367    fn resolve_select_columns(
368        &self,
369        table: &DataTable,
370        select_items: &[SelectItem],
371    ) -> Result<Vec<usize>> {
372        let mut indices = Vec::new();
373        let table_columns = table.column_names();
374
375        for item in select_items {
376            match item {
377                SelectItem::Column(col_name) => {
378                    let index = table_columns
379                        .iter()
380                        .position(|c| c.eq_ignore_ascii_case(col_name))
381                        .ok_or_else(|| {
382                            let suggestion = self.find_similar_column(table, col_name);
383                            match suggestion {
384                                Some(similar) => anyhow::anyhow!(
385                                    "Column '{}' not found. Did you mean '{}'?",
386                                    col_name,
387                                    similar
388                                ),
389                                None => anyhow::anyhow!("Column '{}' not found", col_name),
390                            }
391                        })?;
392                    indices.push(index);
393                }
394                SelectItem::Star => {
395                    // Expand * to all column indices
396                    for i in 0..table_columns.len() {
397                        indices.push(i);
398                    }
399                }
400                SelectItem::Expression { .. } => {
401                    return Err(anyhow::anyhow!(
402                        "Computed expressions require new table creation"
403                    ));
404                }
405            }
406        }
407
408        Ok(indices)
409    }
410
411    /// Apply multi-column ORDER BY sorting to the view
412    fn apply_multi_order_by(
413        &self,
414        mut view: DataView,
415        order_by_columns: &[OrderByColumn],
416    ) -> Result<DataView> {
417        // Build list of (source_column_index, ascending) tuples
418        let mut sort_columns = Vec::new();
419
420        for order_col in order_by_columns {
421            // Get column index
422            let col_index = view
423                .source()
424                .get_column_index(&order_col.column)
425                .ok_or_else(|| {
426                    let suggestion = self.find_similar_column(view.source(), &order_col.column);
427                    match suggestion {
428                        Some(similar) => anyhow::anyhow!(
429                            "Column '{}' not found. Did you mean '{}'?",
430                            order_col.column,
431                            similar
432                        ),
433                        None => anyhow::anyhow!("Column '{}' not found", order_col.column),
434                    }
435                })?;
436
437            let ascending = matches!(order_col.direction, SortDirection::Asc);
438            sort_columns.push((col_index, ascending));
439        }
440
441        // Apply multi-column sorting
442        view.apply_multi_sort(&sort_columns)?;
443        Ok(view)
444    }
445}
446
447#[cfg(test)]
448mod tests {
449    use super::*;
450    use crate::data::datatable::{DataColumn, DataRow, DataValue};
451
452    fn create_test_table() -> Arc<DataTable> {
453        let mut table = DataTable::new("test");
454
455        // Add columns
456        table.add_column(DataColumn::new("id"));
457        table.add_column(DataColumn::new("name"));
458        table.add_column(DataColumn::new("age"));
459
460        // Add rows
461        table
462            .add_row(DataRow::new(vec![
463                DataValue::Integer(1),
464                DataValue::String("Alice".to_string()),
465                DataValue::Integer(30),
466            ]))
467            .unwrap();
468
469        table
470            .add_row(DataRow::new(vec![
471                DataValue::Integer(2),
472                DataValue::String("Bob".to_string()),
473                DataValue::Integer(25),
474            ]))
475            .unwrap();
476
477        table
478            .add_row(DataRow::new(vec![
479                DataValue::Integer(3),
480                DataValue::String("Charlie".to_string()),
481                DataValue::Integer(35),
482            ]))
483            .unwrap();
484
485        Arc::new(table)
486    }
487
488    #[test]
489    fn test_select_all() {
490        let table = create_test_table();
491        let engine = QueryEngine::new();
492
493        let view = engine
494            .execute(table.clone(), "SELECT * FROM users")
495            .unwrap();
496        assert_eq!(view.row_count(), 3);
497        assert_eq!(view.column_count(), 3);
498    }
499
500    #[test]
501    fn test_select_columns() {
502        let table = create_test_table();
503        let engine = QueryEngine::new();
504
505        let view = engine
506            .execute(table.clone(), "SELECT name, age FROM users")
507            .unwrap();
508        assert_eq!(view.row_count(), 3);
509        assert_eq!(view.column_count(), 2);
510    }
511
512    #[test]
513    fn test_select_with_limit() {
514        let table = create_test_table();
515        let engine = QueryEngine::new();
516
517        let view = engine
518            .execute(table.clone(), "SELECT * FROM users LIMIT 2")
519            .unwrap();
520        assert_eq!(view.row_count(), 2);
521    }
522
523    #[test]
524    fn test_type_coercion_contains() {
525        // Initialize tracing for debug output
526        let _ = tracing_subscriber::fmt()
527            .with_max_level(tracing::Level::DEBUG)
528            .try_init();
529
530        let mut table = DataTable::new("test");
531        table.add_column(DataColumn::new("id"));
532        table.add_column(DataColumn::new("status"));
533        table.add_column(DataColumn::new("price"));
534
535        // Add test data with mixed types
536        table
537            .add_row(DataRow::new(vec![
538                DataValue::Integer(1),
539                DataValue::String("Pending".to_string()),
540                DataValue::Float(99.99),
541            ]))
542            .unwrap();
543
544        table
545            .add_row(DataRow::new(vec![
546                DataValue::Integer(2),
547                DataValue::String("Confirmed".to_string()),
548                DataValue::Float(150.50),
549            ]))
550            .unwrap();
551
552        table
553            .add_row(DataRow::new(vec![
554                DataValue::Integer(3),
555                DataValue::String("Pending".to_string()),
556                DataValue::Float(75.00),
557            ]))
558            .unwrap();
559
560        let table = Arc::new(table);
561        let engine = QueryEngine::new();
562
563        println!("\n=== Testing WHERE clause with Contains ===");
564        println!("Table has {} rows", table.row_count());
565        for i in 0..table.row_count() {
566            let status = table.get_value(i, 1);
567            println!("Row {}: status = {:?}", i, status);
568        }
569
570        // Test 1: Basic string contains (should work)
571        println!("\n--- Test 1: status.Contains('pend') ---");
572        let result = engine.execute(
573            table.clone(),
574            "SELECT * FROM test WHERE status.Contains('pend')",
575        );
576        match result {
577            Ok(view) => {
578                println!("SUCCESS: Found {} matching rows", view.row_count());
579                assert_eq!(view.row_count(), 2); // Should find both Pending rows
580            }
581            Err(e) => {
582                panic!("Query failed: {}", e);
583            }
584        }
585
586        // Test 2: Numeric contains (should work with type coercion)
587        println!("\n--- Test 2: price.Contains('9') ---");
588        let result = engine.execute(
589            table.clone(),
590            "SELECT * FROM test WHERE price.Contains('9')",
591        );
592        match result {
593            Ok(view) => {
594                println!(
595                    "SUCCESS: Found {} matching rows with price containing '9'",
596                    view.row_count()
597                );
598                // Should find 99.99 row
599                assert!(view.row_count() >= 1);
600            }
601            Err(e) => {
602                panic!("Numeric coercion query failed: {}", e);
603            }
604        }
605
606        println!("\n=== All tests passed! ===");
607    }
608
609    #[test]
610    fn test_not_in_clause() {
611        // Initialize tracing for debug output
612        let _ = tracing_subscriber::fmt()
613            .with_max_level(tracing::Level::DEBUG)
614            .try_init();
615
616        let mut table = DataTable::new("test");
617        table.add_column(DataColumn::new("id"));
618        table.add_column(DataColumn::new("country"));
619
620        // Add test data
621        table
622            .add_row(DataRow::new(vec![
623                DataValue::Integer(1),
624                DataValue::String("CA".to_string()),
625            ]))
626            .unwrap();
627
628        table
629            .add_row(DataRow::new(vec![
630                DataValue::Integer(2),
631                DataValue::String("US".to_string()),
632            ]))
633            .unwrap();
634
635        table
636            .add_row(DataRow::new(vec![
637                DataValue::Integer(3),
638                DataValue::String("UK".to_string()),
639            ]))
640            .unwrap();
641
642        let table = Arc::new(table);
643        let engine = QueryEngine::new();
644
645        println!("\n=== Testing NOT IN clause ===");
646        println!("Table has {} rows", table.row_count());
647        for i in 0..table.row_count() {
648            let country = table.get_value(i, 1);
649            println!("Row {}: country = {:?}", i, country);
650        }
651
652        // Test NOT IN clause - should exclude CA, return US and UK (2 rows)
653        println!("\n--- Test: country NOT IN ('CA') ---");
654        let result = engine.execute(
655            table.clone(),
656            "SELECT * FROM test WHERE country NOT IN ('CA')",
657        );
658        match result {
659            Ok(view) => {
660                println!("SUCCESS: Found {} rows not in ('CA')", view.row_count());
661                assert_eq!(view.row_count(), 2); // Should find US and UK
662            }
663            Err(e) => {
664                panic!("NOT IN query failed: {}", e);
665            }
666        }
667
668        println!("\n=== NOT IN test complete! ===");
669    }
670
671    #[test]
672    fn test_case_insensitive_in_and_not_in() {
673        // Initialize tracing for debug output
674        let _ = tracing_subscriber::fmt()
675            .with_max_level(tracing::Level::DEBUG)
676            .try_init();
677
678        let mut table = DataTable::new("test");
679        table.add_column(DataColumn::new("id"));
680        table.add_column(DataColumn::new("country"));
681
682        // Add test data with mixed case
683        table
684            .add_row(DataRow::new(vec![
685                DataValue::Integer(1),
686                DataValue::String("CA".to_string()), // uppercase
687            ]))
688            .unwrap();
689
690        table
691            .add_row(DataRow::new(vec![
692                DataValue::Integer(2),
693                DataValue::String("us".to_string()), // lowercase
694            ]))
695            .unwrap();
696
697        table
698            .add_row(DataRow::new(vec![
699                DataValue::Integer(3),
700                DataValue::String("UK".to_string()), // uppercase
701            ]))
702            .unwrap();
703
704        let table = Arc::new(table);
705
706        println!("\n=== Testing Case-Insensitive IN clause ===");
707        println!("Table has {} rows", table.row_count());
708        for i in 0..table.row_count() {
709            let country = table.get_value(i, 1);
710            println!("Row {}: country = {:?}", i, country);
711        }
712
713        // Test case-insensitive IN - should match 'CA' with 'ca'
714        println!("\n--- Test: country IN ('ca') with case_insensitive=true ---");
715        let engine = QueryEngine::with_case_insensitive(true);
716        let result = engine.execute(table.clone(), "SELECT * FROM test WHERE country IN ('ca')");
717        match result {
718            Ok(view) => {
719                println!(
720                    "SUCCESS: Found {} rows matching 'ca' (case-insensitive)",
721                    view.row_count()
722                );
723                assert_eq!(view.row_count(), 1); // Should find CA row
724            }
725            Err(e) => {
726                panic!("Case-insensitive IN query failed: {}", e);
727            }
728        }
729
730        // Test case-insensitive NOT IN - should exclude 'CA' when searching for 'ca'
731        println!("\n--- Test: country NOT IN ('ca') with case_insensitive=true ---");
732        let result = engine.execute(
733            table.clone(),
734            "SELECT * FROM test WHERE country NOT IN ('ca')",
735        );
736        match result {
737            Ok(view) => {
738                println!(
739                    "SUCCESS: Found {} rows not matching 'ca' (case-insensitive)",
740                    view.row_count()
741                );
742                assert_eq!(view.row_count(), 2); // Should find us and UK rows
743            }
744            Err(e) => {
745                panic!("Case-insensitive NOT IN query failed: {}", e);
746            }
747        }
748
749        // Test case-sensitive (default) - should NOT match 'CA' with 'ca'
750        println!("\n--- Test: country IN ('ca') with case_insensitive=false ---");
751        let engine_case_sensitive = QueryEngine::new(); // defaults to case_insensitive=false
752        let result = engine_case_sensitive
753            .execute(table.clone(), "SELECT * FROM test WHERE country IN ('ca')");
754        match result {
755            Ok(view) => {
756                println!(
757                    "SUCCESS: Found {} rows matching 'ca' (case-sensitive)",
758                    view.row_count()
759                );
760                assert_eq!(view.row_count(), 0); // Should find no rows (CA != ca)
761            }
762            Err(e) => {
763                panic!("Case-sensitive IN query failed: {}", e);
764            }
765        }
766
767        println!("\n=== Case-insensitive IN/NOT IN test complete! ===");
768    }
769
770    #[test]
771    #[ignore = "Parentheses in WHERE clause not yet implemented"]
772    fn test_parentheses_in_where_clause() {
773        // Initialize tracing for debug output
774        let _ = tracing_subscriber::fmt()
775            .with_max_level(tracing::Level::DEBUG)
776            .try_init();
777
778        let mut table = DataTable::new("test");
779        table.add_column(DataColumn::new("id"));
780        table.add_column(DataColumn::new("status"));
781        table.add_column(DataColumn::new("priority"));
782
783        // Add test data
784        table
785            .add_row(DataRow::new(vec![
786                DataValue::Integer(1),
787                DataValue::String("Pending".to_string()),
788                DataValue::String("High".to_string()),
789            ]))
790            .unwrap();
791
792        table
793            .add_row(DataRow::new(vec![
794                DataValue::Integer(2),
795                DataValue::String("Complete".to_string()),
796                DataValue::String("High".to_string()),
797            ]))
798            .unwrap();
799
800        table
801            .add_row(DataRow::new(vec![
802                DataValue::Integer(3),
803                DataValue::String("Pending".to_string()),
804                DataValue::String("Low".to_string()),
805            ]))
806            .unwrap();
807
808        table
809            .add_row(DataRow::new(vec![
810                DataValue::Integer(4),
811                DataValue::String("Complete".to_string()),
812                DataValue::String("Low".to_string()),
813            ]))
814            .unwrap();
815
816        let table = Arc::new(table);
817        let engine = QueryEngine::new();
818
819        println!("\n=== Testing Parentheses in WHERE clause ===");
820        println!("Table has {} rows", table.row_count());
821        for i in 0..table.row_count() {
822            let status = table.get_value(i, 1);
823            let priority = table.get_value(i, 2);
824            println!(
825                "Row {}: status = {:?}, priority = {:?}",
826                i, status, priority
827            );
828        }
829
830        // Test OR with parentheses - should get (Pending AND High) OR (Complete AND Low)
831        println!("\n--- Test: (status = 'Pending' AND priority = 'High') OR (status = 'Complete' AND priority = 'Low') ---");
832        let result = engine.execute(
833            table.clone(),
834            "SELECT * FROM test WHERE (status = 'Pending' AND priority = 'High') OR (status = 'Complete' AND priority = 'Low')",
835        );
836        match result {
837            Ok(view) => {
838                println!(
839                    "SUCCESS: Found {} rows with parenthetical logic",
840                    view.row_count()
841                );
842                assert_eq!(view.row_count(), 2); // Should find rows 1 and 4
843            }
844            Err(e) => {
845                panic!("Parentheses query failed: {}", e);
846            }
847        }
848
849        println!("\n=== Parentheses test complete! ===");
850    }
851
852    #[test]
853    #[ignore = "Numeric type coercion needs fixing"]
854    fn test_numeric_type_coercion() {
855        // Initialize tracing for debug output
856        let _ = tracing_subscriber::fmt()
857            .with_max_level(tracing::Level::DEBUG)
858            .try_init();
859
860        let mut table = DataTable::new("test");
861        table.add_column(DataColumn::new("id"));
862        table.add_column(DataColumn::new("price"));
863        table.add_column(DataColumn::new("quantity"));
864
865        // Add test data with different numeric types
866        table
867            .add_row(DataRow::new(vec![
868                DataValue::Integer(1),
869                DataValue::Float(99.50), // Contains '.'
870                DataValue::Integer(100),
871            ]))
872            .unwrap();
873
874        table
875            .add_row(DataRow::new(vec![
876                DataValue::Integer(2),
877                DataValue::Float(150.0), // Contains '.' and '0'
878                DataValue::Integer(200),
879            ]))
880            .unwrap();
881
882        table
883            .add_row(DataRow::new(vec![
884                DataValue::Integer(3),
885                DataValue::Integer(75), // No decimal point
886                DataValue::Integer(50),
887            ]))
888            .unwrap();
889
890        let table = Arc::new(table);
891        let engine = QueryEngine::new();
892
893        println!("\n=== Testing Numeric Type Coercion ===");
894        println!("Table has {} rows", table.row_count());
895        for i in 0..table.row_count() {
896            let price = table.get_value(i, 1);
897            let quantity = table.get_value(i, 2);
898            println!("Row {}: price = {:?}, quantity = {:?}", i, price, quantity);
899        }
900
901        // Test Contains on float values - should find rows with decimal points
902        println!("\n--- Test: price.Contains('.') ---");
903        let result = engine.execute(
904            table.clone(),
905            "SELECT * FROM test WHERE price.Contains('.')",
906        );
907        match result {
908            Ok(view) => {
909                println!(
910                    "SUCCESS: Found {} rows with decimal points in price",
911                    view.row_count()
912                );
913                assert_eq!(view.row_count(), 2); // Should find 99.50 and 150.0
914            }
915            Err(e) => {
916                panic!("Numeric Contains query failed: {}", e);
917            }
918        }
919
920        // Test Contains on integer values converted to string
921        println!("\n--- Test: quantity.Contains('0') ---");
922        let result = engine.execute(
923            table.clone(),
924            "SELECT * FROM test WHERE quantity.Contains('0')",
925        );
926        match result {
927            Ok(view) => {
928                println!(
929                    "SUCCESS: Found {} rows with '0' in quantity",
930                    view.row_count()
931                );
932                assert_eq!(view.row_count(), 2); // Should find 100 and 200
933            }
934            Err(e) => {
935                panic!("Integer Contains query failed: {}", e);
936            }
937        }
938
939        println!("\n=== Numeric type coercion test complete! ===");
940    }
941
942    #[test]
943    fn test_datetime_comparisons() {
944        // Initialize tracing for debug output
945        let _ = tracing_subscriber::fmt()
946            .with_max_level(tracing::Level::DEBUG)
947            .try_init();
948
949        let mut table = DataTable::new("test");
950        table.add_column(DataColumn::new("id"));
951        table.add_column(DataColumn::new("created_date"));
952
953        // Add test data with date strings (as they would come from CSV)
954        table
955            .add_row(DataRow::new(vec![
956                DataValue::Integer(1),
957                DataValue::String("2024-12-15".to_string()),
958            ]))
959            .unwrap();
960
961        table
962            .add_row(DataRow::new(vec![
963                DataValue::Integer(2),
964                DataValue::String("2025-01-15".to_string()),
965            ]))
966            .unwrap();
967
968        table
969            .add_row(DataRow::new(vec![
970                DataValue::Integer(3),
971                DataValue::String("2025-02-15".to_string()),
972            ]))
973            .unwrap();
974
975        let table = Arc::new(table);
976        let engine = QueryEngine::new();
977
978        println!("\n=== Testing DateTime Comparisons ===");
979        println!("Table has {} rows", table.row_count());
980        for i in 0..table.row_count() {
981            let date = table.get_value(i, 1);
982            println!("Row {}: created_date = {:?}", i, date);
983        }
984
985        // Test DateTime constructor comparison - should find dates after 2025-01-01
986        println!("\n--- Test: created_date > DateTime(2025,1,1) ---");
987        let result = engine.execute(
988            table.clone(),
989            "SELECT * FROM test WHERE created_date > DateTime(2025,1,1)",
990        );
991        match result {
992            Ok(view) => {
993                println!("SUCCESS: Found {} rows after 2025-01-01", view.row_count());
994                assert_eq!(view.row_count(), 2); // Should find 2025-01-15 and 2025-02-15
995            }
996            Err(e) => {
997                panic!("DateTime comparison query failed: {}", e);
998            }
999        }
1000
1001        println!("\n=== DateTime comparison test complete! ===");
1002    }
1003
1004    #[test]
1005    fn test_not_with_method_calls() {
1006        // Initialize tracing for debug output
1007        let _ = tracing_subscriber::fmt()
1008            .with_max_level(tracing::Level::DEBUG)
1009            .try_init();
1010
1011        let mut table = DataTable::new("test");
1012        table.add_column(DataColumn::new("id"));
1013        table.add_column(DataColumn::new("status"));
1014
1015        // Add test data
1016        table
1017            .add_row(DataRow::new(vec![
1018                DataValue::Integer(1),
1019                DataValue::String("Pending Review".to_string()),
1020            ]))
1021            .unwrap();
1022
1023        table
1024            .add_row(DataRow::new(vec![
1025                DataValue::Integer(2),
1026                DataValue::String("Complete".to_string()),
1027            ]))
1028            .unwrap();
1029
1030        table
1031            .add_row(DataRow::new(vec![
1032                DataValue::Integer(3),
1033                DataValue::String("Pending Approval".to_string()),
1034            ]))
1035            .unwrap();
1036
1037        let table = Arc::new(table);
1038        let engine = QueryEngine::with_case_insensitive(true);
1039
1040        println!("\n=== Testing NOT with Method Calls ===");
1041        println!("Table has {} rows", table.row_count());
1042        for i in 0..table.row_count() {
1043            let status = table.get_value(i, 1);
1044            println!("Row {}: status = {:?}", i, status);
1045        }
1046
1047        // Test NOT with Contains - should exclude rows containing "pend"
1048        println!("\n--- Test: NOT status.Contains('pend') ---");
1049        let result = engine.execute(
1050            table.clone(),
1051            "SELECT * FROM test WHERE NOT status.Contains('pend')",
1052        );
1053        match result {
1054            Ok(view) => {
1055                println!(
1056                    "SUCCESS: Found {} rows NOT containing 'pend'",
1057                    view.row_count()
1058                );
1059                assert_eq!(view.row_count(), 1); // Should find only "Complete"
1060            }
1061            Err(e) => {
1062                panic!("NOT Contains query failed: {}", e);
1063            }
1064        }
1065
1066        // Test NOT with StartsWith
1067        println!("\n--- Test: NOT status.StartsWith('Pending') ---");
1068        let result = engine.execute(
1069            table.clone(),
1070            "SELECT * FROM test WHERE NOT status.StartsWith('Pending')",
1071        );
1072        match result {
1073            Ok(view) => {
1074                println!(
1075                    "SUCCESS: Found {} rows NOT starting with 'Pending'",
1076                    view.row_count()
1077                );
1078                assert_eq!(view.row_count(), 1); // Should find only "Complete"
1079            }
1080            Err(e) => {
1081                panic!("NOT StartsWith query failed: {}", e);
1082            }
1083        }
1084
1085        println!("\n=== NOT with method calls test complete! ===");
1086    }
1087
1088    #[test]
1089    #[ignore = "Complex logical expressions with parentheses not yet implemented"]
1090    fn test_complex_logical_expressions() {
1091        // Initialize tracing for debug output
1092        let _ = tracing_subscriber::fmt()
1093            .with_max_level(tracing::Level::DEBUG)
1094            .try_init();
1095
1096        let mut table = DataTable::new("test");
1097        table.add_column(DataColumn::new("id"));
1098        table.add_column(DataColumn::new("status"));
1099        table.add_column(DataColumn::new("priority"));
1100        table.add_column(DataColumn::new("assigned"));
1101
1102        // Add comprehensive test data
1103        table
1104            .add_row(DataRow::new(vec![
1105                DataValue::Integer(1),
1106                DataValue::String("Pending".to_string()),
1107                DataValue::String("High".to_string()),
1108                DataValue::String("John".to_string()),
1109            ]))
1110            .unwrap();
1111
1112        table
1113            .add_row(DataRow::new(vec![
1114                DataValue::Integer(2),
1115                DataValue::String("Complete".to_string()),
1116                DataValue::String("High".to_string()),
1117                DataValue::String("Jane".to_string()),
1118            ]))
1119            .unwrap();
1120
1121        table
1122            .add_row(DataRow::new(vec![
1123                DataValue::Integer(3),
1124                DataValue::String("Pending".to_string()),
1125                DataValue::String("Low".to_string()),
1126                DataValue::String("John".to_string()),
1127            ]))
1128            .unwrap();
1129
1130        table
1131            .add_row(DataRow::new(vec![
1132                DataValue::Integer(4),
1133                DataValue::String("In Progress".to_string()),
1134                DataValue::String("Medium".to_string()),
1135                DataValue::String("Jane".to_string()),
1136            ]))
1137            .unwrap();
1138
1139        let table = Arc::new(table);
1140        let engine = QueryEngine::new();
1141
1142        println!("\n=== Testing Complex Logical Expressions ===");
1143        println!("Table has {} rows", table.row_count());
1144        for i in 0..table.row_count() {
1145            let status = table.get_value(i, 1);
1146            let priority = table.get_value(i, 2);
1147            let assigned = table.get_value(i, 3);
1148            println!(
1149                "Row {}: status = {:?}, priority = {:?}, assigned = {:?}",
1150                i, status, priority, assigned
1151            );
1152        }
1153
1154        // Test complex AND/OR logic
1155        println!("\n--- Test: status = 'Pending' AND (priority = 'High' OR assigned = 'John') ---");
1156        let result = engine.execute(
1157            table.clone(),
1158            "SELECT * FROM test WHERE status = 'Pending' AND (priority = 'High' OR assigned = 'John')",
1159        );
1160        match result {
1161            Ok(view) => {
1162                println!(
1163                    "SUCCESS: Found {} rows with complex logic",
1164                    view.row_count()
1165                );
1166                assert_eq!(view.row_count(), 2); // Should find rows 1 and 3 (both Pending, one High priority, both assigned to John)
1167            }
1168            Err(e) => {
1169                panic!("Complex logic query failed: {}", e);
1170            }
1171        }
1172
1173        // Test NOT with complex expressions
1174        println!("\n--- Test: NOT (status.Contains('Complete') OR priority = 'Low') ---");
1175        let result = engine.execute(
1176            table.clone(),
1177            "SELECT * FROM test WHERE NOT (status.Contains('Complete') OR priority = 'Low')",
1178        );
1179        match result {
1180            Ok(view) => {
1181                println!(
1182                    "SUCCESS: Found {} rows with NOT complex logic",
1183                    view.row_count()
1184                );
1185                assert_eq!(view.row_count(), 2); // Should find rows 1 (Pending+High) and 4 (In Progress+Medium)
1186            }
1187            Err(e) => {
1188                panic!("NOT complex logic query failed: {}", e);
1189            }
1190        }
1191
1192        println!("\n=== Complex logical expressions test complete! ===");
1193    }
1194
1195    #[test]
1196    fn test_mixed_data_types_and_edge_cases() {
1197        // Initialize tracing for debug output
1198        let _ = tracing_subscriber::fmt()
1199            .with_max_level(tracing::Level::DEBUG)
1200            .try_init();
1201
1202        let mut table = DataTable::new("test");
1203        table.add_column(DataColumn::new("id"));
1204        table.add_column(DataColumn::new("value"));
1205        table.add_column(DataColumn::new("nullable_field"));
1206
1207        // Add test data with mixed types and edge cases
1208        table
1209            .add_row(DataRow::new(vec![
1210                DataValue::Integer(1),
1211                DataValue::String("123.45".to_string()),
1212                DataValue::String("present".to_string()),
1213            ]))
1214            .unwrap();
1215
1216        table
1217            .add_row(DataRow::new(vec![
1218                DataValue::Integer(2),
1219                DataValue::Float(678.90),
1220                DataValue::Null,
1221            ]))
1222            .unwrap();
1223
1224        table
1225            .add_row(DataRow::new(vec![
1226                DataValue::Integer(3),
1227                DataValue::Boolean(true),
1228                DataValue::String("also present".to_string()),
1229            ]))
1230            .unwrap();
1231
1232        table
1233            .add_row(DataRow::new(vec![
1234                DataValue::Integer(4),
1235                DataValue::String("false".to_string()),
1236                DataValue::Null,
1237            ]))
1238            .unwrap();
1239
1240        let table = Arc::new(table);
1241        let engine = QueryEngine::new();
1242
1243        println!("\n=== Testing Mixed Data Types and Edge Cases ===");
1244        println!("Table has {} rows", table.row_count());
1245        for i in 0..table.row_count() {
1246            let value = table.get_value(i, 1);
1247            let nullable = table.get_value(i, 2);
1248            println!(
1249                "Row {}: value = {:?}, nullable_field = {:?}",
1250                i, value, nullable
1251            );
1252        }
1253
1254        // Test type coercion with boolean Contains
1255        println!("\n--- Test: value.Contains('true') (boolean to string coercion) ---");
1256        let result = engine.execute(
1257            table.clone(),
1258            "SELECT * FROM test WHERE value.Contains('true')",
1259        );
1260        match result {
1261            Ok(view) => {
1262                println!(
1263                    "SUCCESS: Found {} rows with boolean coercion",
1264                    view.row_count()
1265                );
1266                assert_eq!(view.row_count(), 1); // Should find the boolean true row
1267            }
1268            Err(e) => {
1269                panic!("Boolean coercion query failed: {}", e);
1270            }
1271        }
1272
1273        // Test multiple IN values with mixed types
1274        println!("\n--- Test: id IN (1, 3) ---");
1275        let result = engine.execute(table.clone(), "SELECT * FROM test WHERE id IN (1, 3)");
1276        match result {
1277            Ok(view) => {
1278                println!("SUCCESS: Found {} rows with IN clause", view.row_count());
1279                assert_eq!(view.row_count(), 2); // Should find rows with id 1 and 3
1280            }
1281            Err(e) => {
1282                panic!("Multiple IN values query failed: {}", e);
1283            }
1284        }
1285
1286        println!("\n=== Mixed data types test complete! ===");
1287    }
1288
1289    #[test]
1290    fn test_not_in_parsing() {
1291        use crate::sql::recursive_parser::Parser;
1292
1293        let query = "SELECT * FROM test WHERE country NOT IN ('CA')";
1294        println!("\n=== Testing NOT IN parsing ===");
1295        println!("Parsing query: {}", query);
1296
1297        let mut parser = Parser::new(query);
1298        match parser.parse() {
1299            Ok(statement) => {
1300                println!("Parsed statement: {:#?}", statement);
1301                if let Some(where_clause) = statement.where_clause {
1302                    println!("WHERE conditions: {:#?}", where_clause.conditions);
1303                    if let Some(first_condition) = where_clause.conditions.first() {
1304                        println!("First condition expression: {:#?}", first_condition.expr);
1305                    }
1306                }
1307            }
1308            Err(e) => {
1309                panic!("Parse error: {}", e);
1310            }
1311        }
1312    }
1313}
1314
1315#[cfg(test)]
1316#[path = "query_engine_tests.rs"]
1317mod query_engine_tests;