sql_cli/data/
datatable.rs

1use crate::api_client::QueryResponse;
2use crate::data::data_provider::DataProvider;
3use crate::data::type_inference::{InferredType, TypeInference};
4use serde::{Deserialize, Serialize};
5use serde_json::Value as JsonValue;
6use std::collections::HashMap;
7use std::fmt;
8use std::sync::Arc;
9use tracing::debug;
10
11/// Represents the data type of a column
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13pub enum DataType {
14    String,
15    Integer,
16    Float,
17    Boolean,
18    DateTime,
19    Null,
20    Mixed, // For columns with mixed types
21}
22
23impl DataType {
24    /// Infer type from a string value
25    #[must_use]
26    pub fn infer_from_string(value: &str) -> Self {
27        // Handle explicit null string
28        if value.eq_ignore_ascii_case("null") {
29            return DataType::Null;
30        }
31
32        // Use the shared type inference logic
33        match TypeInference::infer_from_string(value) {
34            InferredType::Null => DataType::Null,
35            InferredType::Boolean => DataType::Boolean,
36            InferredType::Integer => DataType::Integer,
37            InferredType::Float => DataType::Float,
38            InferredType::DateTime => DataType::DateTime,
39            InferredType::String => DataType::String,
40        }
41    }
42
43    /// Check if a string looks like a datetime value
44    /// Delegates to shared type inference logic
45    fn looks_like_datetime(value: &str) -> bool {
46        TypeInference::looks_like_datetime(value)
47    }
48
49    /// Merge two types (for columns with mixed types)
50    #[must_use]
51    pub fn merge(&self, other: &DataType) -> DataType {
52        if self == other {
53            return self.clone();
54        }
55
56        match (self, other) {
57            (DataType::Null, t) | (t, DataType::Null) => t.clone(),
58            (DataType::Integer, DataType::Float) | (DataType::Float, DataType::Integer) => {
59                DataType::Float
60            }
61            _ => DataType::Mixed,
62        }
63    }
64}
65
66/// Column metadata and definition
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct DataColumn {
69    pub name: String,
70    pub data_type: DataType,
71    pub nullable: bool,
72    pub unique_values: Option<usize>,
73    pub null_count: usize,
74    pub metadata: HashMap<String, String>,
75}
76
77impl DataColumn {
78    pub fn new(name: impl Into<String>) -> Self {
79        Self {
80            name: name.into(),
81            data_type: DataType::String,
82            nullable: true,
83            unique_values: None,
84            null_count: 0,
85            metadata: HashMap::new(),
86        }
87    }
88
89    #[must_use]
90    pub fn with_type(mut self, data_type: DataType) -> Self {
91        self.data_type = data_type;
92        self
93    }
94
95    #[must_use]
96    pub fn with_nullable(mut self, nullable: bool) -> Self {
97        self.nullable = nullable;
98        self
99    }
100}
101
102/// A single cell value in the table
103#[derive(Debug, Clone, PartialEq, PartialOrd)]
104pub enum DataValue {
105    String(String),
106    InternedString(Arc<String>), // For repeated strings (e.g., status, trader names)
107    Integer(i64),
108    Float(f64),
109    Boolean(bool),
110    DateTime(String), // Store as ISO 8601 string for now
111    Null,
112}
113
114impl DataValue {
115    pub fn from_string(s: &str, data_type: &DataType) -> Self {
116        if s.is_empty() || s.eq_ignore_ascii_case("null") {
117            return DataValue::Null;
118        }
119
120        match data_type {
121            DataType::String => DataValue::String(s.to_string()),
122            DataType::Integer => s
123                .parse::<i64>()
124                .map_or_else(|_| DataValue::String(s.to_string()), DataValue::Integer),
125            DataType::Float => s
126                .parse::<f64>()
127                .map_or_else(|_| DataValue::String(s.to_string()), DataValue::Float),
128            DataType::Boolean => {
129                let lower = s.to_lowercase();
130                DataValue::Boolean(lower == "true" || lower == "1" || lower == "yes")
131            }
132            DataType::DateTime => DataValue::DateTime(s.to_string()),
133            DataType::Null => DataValue::Null,
134            DataType::Mixed => {
135                // Try to infer for mixed columns
136                let inferred = DataType::infer_from_string(s);
137                Self::from_string(s, &inferred)
138            }
139        }
140    }
141
142    #[must_use]
143    pub fn is_null(&self) -> bool {
144        matches!(self, DataValue::Null)
145    }
146
147    #[must_use]
148    pub fn data_type(&self) -> DataType {
149        match self {
150            DataValue::String(_) | DataValue::InternedString(_) => DataType::String,
151            DataValue::Integer(_) => DataType::Integer,
152            DataValue::Float(_) => DataType::Float,
153            DataValue::Boolean(_) => DataType::Boolean,
154            DataValue::DateTime(_) => DataType::DateTime,
155            DataValue::Null => DataType::Null,
156        }
157    }
158
159    /// Get string representation without allocation when possible
160    /// Returns owned String for compatibility but tries to reuse existing strings
161    #[must_use]
162    pub fn to_string_optimized(&self) -> String {
163        match self {
164            DataValue::String(s) => s.clone(), // Clone existing string
165            DataValue::InternedString(s) => s.as_ref().clone(), // Clone from Rc
166            DataValue::DateTime(s) => s.clone(), // Clone existing string
167            DataValue::Integer(i) => i.to_string(),
168            DataValue::Float(f) => f.to_string(),
169            DataValue::Boolean(b) => {
170                if *b {
171                    "true".to_string()
172                } else {
173                    "false".to_string()
174                }
175            }
176            DataValue::Null => String::new(), // Empty string, minimal allocation
177        }
178    }
179}
180
181impl fmt::Display for DataValue {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        match self {
184            DataValue::String(s) => write!(f, "{s}"),
185            DataValue::InternedString(s) => write!(f, "{s}"),
186            DataValue::Integer(i) => write!(f, "{i}"),
187            DataValue::Float(fl) => write!(f, "{fl}"),
188            DataValue::Boolean(b) => write!(f, "{b}"),
189            DataValue::DateTime(dt) => write!(f, "{dt}"),
190            DataValue::Null => write!(f, ""),
191        }
192    }
193}
194
195/// A row of data in the table
196#[derive(Debug, Clone)]
197pub struct DataRow {
198    pub values: Vec<DataValue>,
199}
200
201impl DataRow {
202    #[must_use]
203    pub fn new(values: Vec<DataValue>) -> Self {
204        Self { values }
205    }
206
207    #[must_use]
208    pub fn get(&self, index: usize) -> Option<&DataValue> {
209        self.values.get(index)
210    }
211
212    pub fn get_mut(&mut self, index: usize) -> Option<&mut DataValue> {
213        self.values.get_mut(index)
214    }
215
216    #[must_use]
217    pub fn len(&self) -> usize {
218        self.values.len()
219    }
220
221    #[must_use]
222    pub fn is_empty(&self) -> bool {
223        self.values.is_empty()
224    }
225}
226
227/// The main `DataTable` structure
228#[derive(Debug, Clone)]
229pub struct DataTable {
230    pub name: String,
231    pub columns: Vec<DataColumn>,
232    pub rows: Vec<DataRow>,
233    pub metadata: HashMap<String, String>,
234}
235
236impl DataTable {
237    pub fn new(name: impl Into<String>) -> Self {
238        Self {
239            name: name.into(),
240            columns: Vec::new(),
241            rows: Vec::new(),
242            metadata: HashMap::new(),
243        }
244    }
245
246    /// Create a DUAL table (similar to Oracle's DUAL) with one row and one column
247    /// Used for evaluating expressions without a data source
248    #[must_use]
249    pub fn dual() -> Self {
250        let mut table = DataTable::new("DUAL");
251        table.add_column(DataColumn::new("DUMMY").with_type(DataType::String));
252        table
253            .add_row(DataRow::new(vec![DataValue::String("X".to_string())]))
254            .unwrap();
255        table
256    }
257
258    pub fn add_column(&mut self, column: DataColumn) -> &mut Self {
259        self.columns.push(column);
260        self
261    }
262
263    pub fn add_row(&mut self, row: DataRow) -> Result<(), String> {
264        if row.len() != self.columns.len() {
265            return Err(format!(
266                "Row has {} values but table has {} columns",
267                row.len(),
268                self.columns.len()
269            ));
270        }
271        self.rows.push(row);
272        Ok(())
273    }
274
275    #[must_use]
276    pub fn get_column(&self, name: &str) -> Option<&DataColumn> {
277        self.columns.iter().find(|c| c.name == name)
278    }
279
280    #[must_use]
281    pub fn get_column_index(&self, name: &str) -> Option<usize> {
282        self.columns.iter().position(|c| c.name == name)
283    }
284
285    #[must_use]
286    pub fn column_count(&self) -> usize {
287        self.columns.len()
288    }
289
290    #[must_use]
291    pub fn row_count(&self) -> usize {
292        self.rows.len()
293    }
294
295    #[must_use]
296    pub fn is_empty(&self) -> bool {
297        self.rows.is_empty()
298    }
299
300    /// Get column names as a vector
301    #[must_use]
302    pub fn column_names(&self) -> Vec<String> {
303        self.columns.iter().map(|c| c.name.clone()).collect()
304    }
305
306    /// Infer and update column types based on data
307    pub fn infer_column_types(&mut self) {
308        for (col_idx, column) in self.columns.iter_mut().enumerate() {
309            let mut inferred_type = DataType::Null;
310            let mut null_count = 0;
311            let mut unique_values = std::collections::HashSet::new();
312
313            for row in &self.rows {
314                if let Some(value) = row.get(col_idx) {
315                    if value.is_null() {
316                        null_count += 1;
317                    } else {
318                        let value_type = value.data_type();
319                        inferred_type = inferred_type.merge(&value_type);
320                        unique_values.insert(value.to_string());
321                    }
322                }
323            }
324
325            column.data_type = inferred_type;
326            column.null_count = null_count;
327            column.nullable = null_count > 0;
328            column.unique_values = Some(unique_values.len());
329        }
330    }
331
332    /// Get a value at specific row and column
333    #[must_use]
334    pub fn get_value(&self, row: usize, col: usize) -> Option<&DataValue> {
335        self.rows.get(row)?.get(col)
336    }
337
338    /// Get a value by row index and column name
339    #[must_use]
340    pub fn get_value_by_name(&self, row: usize, col_name: &str) -> Option<&DataValue> {
341        let col_idx = self.get_column_index(col_name)?;
342        self.get_value(row, col_idx)
343    }
344
345    /// Convert to a vector of string vectors (for display/compatibility)
346    #[must_use]
347    pub fn to_string_table(&self) -> Vec<Vec<String>> {
348        self.rows
349            .iter()
350            .map(|row| {
351                row.values
352                    .iter()
353                    .map(DataValue::to_string_optimized)
354                    .collect()
355            })
356            .collect()
357    }
358
359    /// Get table statistics
360    #[must_use]
361    pub fn get_stats(&self) -> DataTableStats {
362        DataTableStats {
363            row_count: self.row_count(),
364            column_count: self.column_count(),
365            memory_size: self.estimate_memory_size(),
366            null_count: self.columns.iter().map(|c| c.null_count).sum(),
367        }
368    }
369
370    /// Generate a debug dump string for display
371    #[must_use]
372    pub fn debug_dump(&self) -> String {
373        let mut output = String::new();
374
375        output.push_str(&format!("DataTable: {}\n", self.name));
376        output.push_str(&format!(
377            "Rows: {} | Columns: {}\n",
378            self.row_count(),
379            self.column_count()
380        ));
381
382        if !self.metadata.is_empty() {
383            output.push_str("Metadata:\n");
384            for (key, value) in &self.metadata {
385                output.push_str(&format!("  {key}: {value}\n"));
386            }
387        }
388
389        output.push_str("\nColumns:\n");
390        for column in &self.columns {
391            output.push_str(&format!("  {} ({:?})", column.name, column.data_type));
392            if column.nullable {
393                output.push_str(&format!(" - nullable, {} nulls", column.null_count));
394            }
395            if let Some(unique) = column.unique_values {
396                output.push_str(&format!(", {unique} unique"));
397            }
398            output.push('\n');
399        }
400
401        // Show first few rows
402        if self.row_count() > 0 {
403            let sample_size = 5.min(self.row_count());
404            output.push_str(&format!("\nFirst {sample_size} rows:\n"));
405
406            for row_idx in 0..sample_size {
407                output.push_str(&format!("  [{row_idx}]: "));
408                for (col_idx, value) in self.rows[row_idx].values.iter().enumerate() {
409                    if col_idx > 0 {
410                        output.push_str(", ");
411                    }
412                    output.push_str(&value.to_string());
413                }
414                output.push('\n');
415            }
416        }
417
418        output
419    }
420
421    #[must_use]
422    pub fn estimate_memory_size(&self) -> usize {
423        // Base structure size
424        let mut size = std::mem::size_of::<Self>();
425
426        // Column metadata
427        size += self.columns.len() * std::mem::size_of::<DataColumn>();
428        for col in &self.columns {
429            size += col.name.len();
430        }
431
432        // Row structure overhead
433        size += self.rows.len() * std::mem::size_of::<DataRow>();
434
435        // Actual data values
436        for row in &self.rows {
437            for value in &row.values {
438                // Base enum size
439                size += std::mem::size_of::<DataValue>();
440                // Add string content size
441                match value {
442                    DataValue::String(s) | DataValue::DateTime(s) => size += s.len(),
443                    _ => {} // Numbers and booleans are inline
444                }
445            }
446        }
447
448        size
449    }
450
451    /// V46: Create `DataTable` from `QueryResponse`
452    /// This is the key conversion function that bridges old and new systems
453    pub fn from_query_response(response: &QueryResponse, table_name: &str) -> Result<Self, String> {
454        debug!(
455            "V46: Converting QueryResponse to DataTable for table '{}'",
456            table_name
457        );
458
459        // Track memory before conversion
460        crate::utils::memory_tracker::track_memory("start_from_query_response");
461
462        let mut table = DataTable::new(table_name);
463
464        // Extract column names and types from first row
465        if let Some(first_row) = response.data.first() {
466            if let Some(obj) = first_row.as_object() {
467                // Create columns based on the keys in the JSON object
468                for key in obj.keys() {
469                    let column = DataColumn::new(key.clone());
470                    table.add_column(column);
471                }
472
473                // Now convert all rows
474                for json_row in &response.data {
475                    if let Some(row_obj) = json_row.as_object() {
476                        let mut values = Vec::new();
477
478                        // Ensure we get values in the same order as columns
479                        for column in &table.columns {
480                            let value = row_obj
481                                .get(&column.name)
482                                .map_or(DataValue::Null, json_value_to_data_value);
483                            values.push(value);
484                        }
485
486                        table.add_row(DataRow::new(values))?;
487                    }
488                }
489
490                // Infer column types from the data
491                table.infer_column_types();
492
493                // Add metadata
494                if let Some(source) = &response.source {
495                    table.metadata.insert("source".to_string(), source.clone());
496                }
497                if let Some(cached) = response.cached {
498                    table
499                        .metadata
500                        .insert("cached".to_string(), cached.to_string());
501                }
502                table
503                    .metadata
504                    .insert("original_count".to_string(), response.count.to_string());
505
506                debug!(
507                    "V46: Created DataTable with {} columns and {} rows",
508                    table.column_count(),
509                    table.row_count()
510                );
511            } else {
512                // Handle non-object JSON (single values)
513                table.add_column(DataColumn::new("value"));
514                for json_value in &response.data {
515                    let value = json_value_to_data_value(json_value);
516                    table.add_row(DataRow::new(vec![value]))?;
517                }
518            }
519        }
520
521        Ok(table)
522    }
523
524    /// Get a single row by index
525    #[must_use]
526    pub fn get_row(&self, index: usize) -> Option<&DataRow> {
527        self.rows.get(index)
528    }
529
530    /// V50: Get a single row as strings
531    #[must_use]
532    pub fn get_row_as_strings(&self, index: usize) -> Option<Vec<String>> {
533        self.rows.get(index).map(|row| {
534            row.values
535                .iter()
536                .map(DataValue::to_string_optimized)
537                .collect()
538        })
539    }
540
541    /// Pretty print the `DataTable` with a nice box drawing
542    #[must_use]
543    pub fn pretty_print(&self) -> String {
544        let mut output = String::new();
545
546        // Header
547        output.push_str("╔═══════════════════════════════════════════════════════╗\n");
548        output.push_str(&format!("║ DataTable: {:^41} ║\n", self.name));
549        output.push_str("╠═══════════════════════════════════════════════════════╣\n");
550
551        // Summary stats
552        output.push_str(&format!(
553            "║ Rows: {:6} | Columns: {:3} | Memory: ~{:6} bytes ║\n",
554            self.row_count(),
555            self.column_count(),
556            self.get_stats().memory_size
557        ));
558
559        // Metadata if any
560        if !self.metadata.is_empty() {
561            output.push_str("╠═══════════════════════════════════════════════════════╣\n");
562            output.push_str("║ Metadata:                                             ║\n");
563            for (key, value) in &self.metadata {
564                let truncated_value = if value.len() > 35 {
565                    format!("{}...", &value[..32])
566                } else {
567                    value.clone()
568                };
569                output.push_str(&format!(
570                    "║   {:15} : {:35} ║\n",
571                    Self::truncate_string(key, 15),
572                    truncated_value
573                ));
574            }
575        }
576
577        // Column details
578        output.push_str("╠═══════════════════════════════════════════════════════╣\n");
579        output.push_str("║ Columns:                                              ║\n");
580        output.push_str("╟───────────────────┬──────────┬─────────┬──────┬──────╢\n");
581        output.push_str("║ Name              │ Type     │ Nullable│ Nulls│Unique║\n");
582        output.push_str("╟───────────────────┼──────────┼─────────┼──────┼──────╢\n");
583
584        for column in &self.columns {
585            let type_str = match &column.data_type {
586                DataType::String => "String",
587                DataType::Integer => "Integer",
588                DataType::Float => "Float",
589                DataType::Boolean => "Boolean",
590                DataType::DateTime => "DateTime",
591                DataType::Null => "Null",
592                DataType::Mixed => "Mixed",
593            };
594
595            output.push_str(&format!(
596                "║ {:17} │ {:8} │ {:7} │ {:4} │ {:4} ║\n",
597                Self::truncate_string(&column.name, 17),
598                type_str,
599                if column.nullable { "Yes" } else { "No" },
600                column.null_count,
601                column.unique_values.unwrap_or(0)
602            ));
603        }
604
605        output.push_str("╚═══════════════════════════════════════════════════════╝\n");
606
607        // Sample data (first 5 rows)
608        output.push_str("\nSample Data (first 5 rows):\n");
609        let sample_count = self.rows.len().min(5);
610
611        if sample_count > 0 {
612            // Column headers
613            output.push('┌');
614            for (i, _col) in self.columns.iter().enumerate() {
615                if i > 0 {
616                    output.push('┬');
617                }
618                output.push_str(&"─".repeat(20));
619            }
620            output.push_str("┐\n");
621
622            output.push('│');
623            for col in &self.columns {
624                output.push_str(&format!(" {:^18} │", Self::truncate_string(&col.name, 18)));
625            }
626            output.push('\n');
627
628            output.push('├');
629            for (i, _) in self.columns.iter().enumerate() {
630                if i > 0 {
631                    output.push('┼');
632                }
633                output.push_str(&"─".repeat(20));
634            }
635            output.push_str("┤\n");
636
637            // Data rows
638            for row_idx in 0..sample_count {
639                if let Some(row) = self.rows.get(row_idx) {
640                    output.push('│');
641                    for value in &row.values {
642                        let value_str = value.to_string();
643                        output
644                            .push_str(&format!(" {:18} │", Self::truncate_string(&value_str, 18)));
645                    }
646                    output.push('\n');
647                }
648            }
649
650            output.push('└');
651            for (i, _) in self.columns.iter().enumerate() {
652                if i > 0 {
653                    output.push('┴');
654                }
655                output.push_str(&"─".repeat(20));
656            }
657            output.push_str("┘\n");
658        }
659
660        output
661    }
662
663    fn truncate_string(s: &str, max_len: usize) -> String {
664        if s.len() > max_len {
665            format!("{}...", &s[..max_len - 3])
666        } else {
667            s.to_string()
668        }
669    }
670
671    /// Get a schema summary of the `DataTable`
672    #[must_use]
673    pub fn get_schema_summary(&self) -> String {
674        let mut summary = String::new();
675        summary.push_str(&format!(
676            "DataTable Schema ({} columns, {} rows):\n",
677            self.columns.len(),
678            self.rows.len()
679        ));
680
681        for (idx, column) in self.columns.iter().enumerate() {
682            let type_str = match &column.data_type {
683                DataType::String => "String",
684                DataType::Integer => "Integer",
685                DataType::Float => "Float",
686                DataType::Boolean => "Boolean",
687                DataType::DateTime => "DateTime",
688                DataType::Null => "Null",
689                DataType::Mixed => "Mixed",
690            };
691
692            let nullable_str = if column.nullable {
693                "nullable"
694            } else {
695                "not null"
696            };
697            let null_info = if column.null_count > 0 {
698                format!(", {} nulls", column.null_count)
699            } else {
700                String::new()
701            };
702
703            summary.push_str(&format!(
704                "  [{:3}] {} : {} ({}{})\n",
705                idx, column.name, type_str, nullable_str, null_info
706            ));
707        }
708
709        summary
710    }
711
712    /// Get detailed schema information as a structured format
713    #[must_use]
714    pub fn get_schema_info(&self) -> Vec<(String, String, bool, usize)> {
715        self.columns
716            .iter()
717            .map(|col| {
718                let type_name = format!("{:?}", col.data_type);
719                (col.name.clone(), type_name, col.nullable, col.null_count)
720            })
721            .collect()
722    }
723
724    /// Reserve capacity for rows to avoid reallocations
725    pub fn reserve_rows(&mut self, additional: usize) {
726        self.rows.reserve(additional);
727    }
728
729    /// Shrink vectors to fit actual data (removes excess capacity)
730    pub fn shrink_to_fit(&mut self) {
731        self.rows.shrink_to_fit();
732        for _column in &mut self.columns {
733            // Shrink any column-specific data if needed
734        }
735    }
736
737    /// Get actual memory usage estimate (more accurate than `estimate_memory_size`)
738    #[must_use]
739    pub fn get_memory_usage(&self) -> usize {
740        let mut size = std::mem::size_of::<Self>();
741
742        // Account for string allocations
743        size += self.name.capacity();
744
745        // Account for columns
746        size += self.columns.capacity() * std::mem::size_of::<DataColumn>();
747        for col in &self.columns {
748            size += col.name.capacity();
749        }
750
751        // Account for rows and their capacity
752        size += self.rows.capacity() * std::mem::size_of::<DataRow>();
753
754        // Account for actual data values
755        for row in &self.rows {
756            size += row.values.capacity() * std::mem::size_of::<DataValue>();
757            for value in &row.values {
758                match value {
759                    DataValue::String(s) => size += s.capacity(),
760                    DataValue::InternedString(_) => size += std::mem::size_of::<Arc<String>>(),
761                    DataValue::DateTime(s) => size += s.capacity(),
762                    _ => {} // Other types are inline
763                }
764            }
765        }
766
767        // Account for metadata
768        size += self.metadata.capacity() * std::mem::size_of::<(String, String)>();
769        for (k, v) in &self.metadata {
770            size += k.capacity() + v.capacity();
771        }
772
773        size
774    }
775}
776
777/// V46: Helper function to convert JSON value to `DataValue`
778fn json_value_to_data_value(json: &JsonValue) -> DataValue {
779    match json {
780        JsonValue::Null => DataValue::Null,
781        JsonValue::Bool(b) => DataValue::Boolean(*b),
782        JsonValue::Number(n) => {
783            if let Some(i) = n.as_i64() {
784                DataValue::Integer(i)
785            } else if let Some(f) = n.as_f64() {
786                DataValue::Float(f)
787            } else {
788                DataValue::String(n.to_string())
789            }
790        }
791        JsonValue::String(s) => {
792            // Try to detect if it's a date/time
793            if s.contains('-') && s.len() >= 8 && s.len() <= 30 {
794                // Simple heuristic for dates
795                DataValue::DateTime(s.clone())
796            } else {
797                DataValue::String(s.clone())
798            }
799        }
800        JsonValue::Array(_) | JsonValue::Object(_) => {
801            // Store complex types as JSON string
802            DataValue::String(json.to_string())
803        }
804    }
805}
806
807/// Statistics about a `DataTable`
808#[derive(Debug, Clone)]
809pub struct DataTableStats {
810    pub row_count: usize,
811    pub column_count: usize,
812    pub memory_size: usize,
813    pub null_count: usize,
814}
815
816/// Implementation of `DataProvider` for `DataTable`
817/// This allows `DataTable` to be used wherever `DataProvider` trait is expected
818impl DataProvider for DataTable {
819    fn get_row(&self, index: usize) -> Option<Vec<String>> {
820        self.rows.get(index).map(|row| {
821            row.values
822                .iter()
823                .map(DataValue::to_string_optimized)
824                .collect()
825        })
826    }
827
828    fn get_column_names(&self) -> Vec<String> {
829        self.column_names()
830    }
831
832    fn get_row_count(&self) -> usize {
833        self.row_count()
834    }
835
836    fn get_column_count(&self) -> usize {
837        self.column_count()
838    }
839}
840
841#[cfg(test)]
842mod tests {
843    use super::*;
844
845    #[test]
846    fn test_data_type_inference() {
847        assert_eq!(DataType::infer_from_string("123"), DataType::Integer);
848        assert_eq!(DataType::infer_from_string("123.45"), DataType::Float);
849        assert_eq!(DataType::infer_from_string("true"), DataType::Boolean);
850        assert_eq!(DataType::infer_from_string("hello"), DataType::String);
851        assert_eq!(DataType::infer_from_string(""), DataType::Null);
852        assert_eq!(
853            DataType::infer_from_string("2024-01-01"),
854            DataType::DateTime
855        );
856    }
857
858    #[test]
859    fn test_datatable_creation() {
860        let mut table = DataTable::new("test");
861
862        table.add_column(DataColumn::new("id").with_type(DataType::Integer));
863        table.add_column(DataColumn::new("name").with_type(DataType::String));
864        table.add_column(DataColumn::new("active").with_type(DataType::Boolean));
865
866        assert_eq!(table.column_count(), 3);
867        assert_eq!(table.row_count(), 0);
868
869        let row = DataRow::new(vec![
870            DataValue::Integer(1),
871            DataValue::String("Alice".to_string()),
872            DataValue::Boolean(true),
873        ]);
874
875        table.add_row(row).unwrap();
876        assert_eq!(table.row_count(), 1);
877
878        let value = table.get_value_by_name(0, "name").unwrap();
879        assert_eq!(value.to_string(), "Alice");
880    }
881
882    #[test]
883    fn test_type_inference() {
884        let mut table = DataTable::new("test");
885
886        // Add columns without types
887        table.add_column(DataColumn::new("mixed"));
888
889        // Add rows with different types
890        table
891            .add_row(DataRow::new(vec![DataValue::Integer(1)]))
892            .unwrap();
893        table
894            .add_row(DataRow::new(vec![DataValue::Float(2.5)]))
895            .unwrap();
896        table.add_row(DataRow::new(vec![DataValue::Null])).unwrap();
897
898        table.infer_column_types();
899
900        // Should infer Float since we have both Integer and Float
901        assert_eq!(table.columns[0].data_type, DataType::Float);
902        assert_eq!(table.columns[0].null_count, 1);
903        assert!(table.columns[0].nullable);
904    }
905
906    #[test]
907    fn test_from_query_response() {
908        use crate::api_client::{QueryInfo, QueryResponse};
909        use serde_json::json;
910
911        let response = QueryResponse {
912            query: QueryInfo {
913                select: vec!["id".to_string(), "name".to_string(), "age".to_string()],
914                where_clause: None,
915                order_by: None,
916            },
917            data: vec![
918                json!({
919                    "id": 1,
920                    "name": "Alice",
921                    "age": 30
922                }),
923                json!({
924                    "id": 2,
925                    "name": "Bob",
926                    "age": 25
927                }),
928                json!({
929                    "id": 3,
930                    "name": "Carol",
931                    "age": null
932                }),
933            ],
934            count: 3,
935            source: Some("test.csv".to_string()),
936            table: Some("test".to_string()),
937            cached: Some(false),
938        };
939
940        let table = DataTable::from_query_response(&response, "test").unwrap();
941
942        assert_eq!(table.name, "test");
943        assert_eq!(table.row_count(), 3);
944        assert_eq!(table.column_count(), 3);
945
946        // Check column names
947        let col_names = table.column_names();
948        assert!(col_names.contains(&"id".to_string()));
949        assert!(col_names.contains(&"name".to_string()));
950        assert!(col_names.contains(&"age".to_string()));
951
952        // Check metadata
953        assert_eq!(table.metadata.get("source"), Some(&"test.csv".to_string()));
954        assert_eq!(table.metadata.get("cached"), Some(&"false".to_string()));
955
956        // Check first row values
957        assert_eq!(
958            table.get_value_by_name(0, "id"),
959            Some(&DataValue::Integer(1))
960        );
961        assert_eq!(
962            table.get_value_by_name(0, "name"),
963            Some(&DataValue::String("Alice".to_string()))
964        );
965        assert_eq!(
966            table.get_value_by_name(0, "age"),
967            Some(&DataValue::Integer(30))
968        );
969
970        // Check null handling
971        assert_eq!(table.get_value_by_name(2, "age"), Some(&DataValue::Null));
972    }
973}