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#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13pub enum DataType {
14 String,
15 Integer,
16 Float,
17 Boolean,
18 DateTime,
19 Null,
20 Mixed, }
22
23impl DataType {
24 #[must_use]
26 pub fn infer_from_string(value: &str) -> Self {
27 if value.eq_ignore_ascii_case("null") {
29 return DataType::Null;
30 }
31
32 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 fn looks_like_datetime(value: &str) -> bool {
46 TypeInference::looks_like_datetime(value)
47 }
48
49 #[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#[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#[derive(Debug, Clone, PartialEq, PartialOrd)]
104pub enum DataValue {
105 String(String),
106 InternedString(Arc<String>), Integer(i64),
108 Float(f64),
109 Boolean(bool),
110 DateTime(String), Null,
112}
113
114impl std::hash::Hash for DataValue {
116 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
117 match self {
118 DataValue::String(s) => {
119 0u8.hash(state);
120 s.hash(state);
121 }
122 DataValue::InternedString(s) => {
123 1u8.hash(state);
124 s.hash(state);
125 }
126 DataValue::Integer(i) => {
127 2u8.hash(state);
128 i.hash(state);
129 }
130 DataValue::Float(f) => {
131 3u8.hash(state);
132 f.to_bits().hash(state);
134 }
135 DataValue::Boolean(b) => {
136 4u8.hash(state);
137 b.hash(state);
138 }
139 DataValue::DateTime(dt) => {
140 5u8.hash(state);
141 dt.hash(state);
142 }
143 DataValue::Null => {
144 6u8.hash(state);
145 }
146 }
147 }
148}
149
150impl Eq for DataValue {}
152
153impl DataValue {
154 pub fn from_string(s: &str, data_type: &DataType) -> Self {
155 if s.is_empty() || s.eq_ignore_ascii_case("null") {
156 return DataValue::Null;
157 }
158
159 match data_type {
160 DataType::String => DataValue::String(s.to_string()),
161 DataType::Integer => s
162 .parse::<i64>()
163 .map_or_else(|_| DataValue::String(s.to_string()), DataValue::Integer),
164 DataType::Float => s
165 .parse::<f64>()
166 .map_or_else(|_| DataValue::String(s.to_string()), DataValue::Float),
167 DataType::Boolean => {
168 let lower = s.to_lowercase();
169 DataValue::Boolean(lower == "true" || lower == "1" || lower == "yes")
170 }
171 DataType::DateTime => DataValue::DateTime(s.to_string()),
172 DataType::Null => DataValue::Null,
173 DataType::Mixed => {
174 let inferred = DataType::infer_from_string(s);
176 Self::from_string(s, &inferred)
177 }
178 }
179 }
180
181 #[must_use]
182 pub fn is_null(&self) -> bool {
183 matches!(self, DataValue::Null)
184 }
185
186 #[must_use]
187 pub fn data_type(&self) -> DataType {
188 match self {
189 DataValue::String(_) | DataValue::InternedString(_) => DataType::String,
190 DataValue::Integer(_) => DataType::Integer,
191 DataValue::Float(_) => DataType::Float,
192 DataValue::Boolean(_) => DataType::Boolean,
193 DataValue::DateTime(_) => DataType::DateTime,
194 DataValue::Null => DataType::Null,
195 }
196 }
197
198 #[must_use]
201 pub fn to_string_optimized(&self) -> String {
202 match self {
203 DataValue::String(s) => s.clone(), DataValue::InternedString(s) => s.as_ref().clone(), DataValue::DateTime(s) => s.clone(), DataValue::Integer(i) => i.to_string(),
207 DataValue::Float(f) => f.to_string(),
208 DataValue::Boolean(b) => {
209 if *b {
210 "true".to_string()
211 } else {
212 "false".to_string()
213 }
214 }
215 DataValue::Null => String::new(), }
217 }
218}
219
220impl fmt::Display for DataValue {
221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222 match self {
223 DataValue::String(s) => write!(f, "{s}"),
224 DataValue::InternedString(s) => write!(f, "{s}"),
225 DataValue::Integer(i) => write!(f, "{i}"),
226 DataValue::Float(fl) => write!(f, "{fl}"),
227 DataValue::Boolean(b) => write!(f, "{b}"),
228 DataValue::DateTime(dt) => write!(f, "{dt}"),
229 DataValue::Null => write!(f, ""),
230 }
231 }
232}
233
234#[derive(Debug, Clone)]
236pub struct DataRow {
237 pub values: Vec<DataValue>,
238}
239
240impl DataRow {
241 #[must_use]
242 pub fn new(values: Vec<DataValue>) -> Self {
243 Self { values }
244 }
245
246 #[must_use]
247 pub fn get(&self, index: usize) -> Option<&DataValue> {
248 self.values.get(index)
249 }
250
251 pub fn get_mut(&mut self, index: usize) -> Option<&mut DataValue> {
252 self.values.get_mut(index)
253 }
254
255 #[must_use]
256 pub fn len(&self) -> usize {
257 self.values.len()
258 }
259
260 #[must_use]
261 pub fn is_empty(&self) -> bool {
262 self.values.is_empty()
263 }
264}
265
266#[derive(Debug, Clone)]
268pub struct DataTable {
269 pub name: String,
270 pub columns: Vec<DataColumn>,
271 pub rows: Vec<DataRow>,
272 pub metadata: HashMap<String, String>,
273}
274
275impl DataTable {
276 pub fn new(name: impl Into<String>) -> Self {
277 Self {
278 name: name.into(),
279 columns: Vec::new(),
280 rows: Vec::new(),
281 metadata: HashMap::new(),
282 }
283 }
284
285 #[must_use]
288 pub fn dual() -> Self {
289 let mut table = DataTable::new("DUAL");
290 table.add_column(DataColumn::new("DUMMY").with_type(DataType::String));
291 table
292 .add_row(DataRow::new(vec![DataValue::String("X".to_string())]))
293 .unwrap();
294 table
295 }
296
297 pub fn add_column(&mut self, column: DataColumn) -> &mut Self {
298 self.columns.push(column);
299 self
300 }
301
302 pub fn add_row(&mut self, row: DataRow) -> Result<(), String> {
303 if row.len() != self.columns.len() {
304 return Err(format!(
305 "Row has {} values but table has {} columns",
306 row.len(),
307 self.columns.len()
308 ));
309 }
310 self.rows.push(row);
311 Ok(())
312 }
313
314 #[must_use]
315 pub fn get_column(&self, name: &str) -> Option<&DataColumn> {
316 self.columns.iter().find(|c| c.name == name)
317 }
318
319 #[must_use]
320 pub fn get_column_index(&self, name: &str) -> Option<usize> {
321 self.columns.iter().position(|c| c.name == name)
322 }
323
324 #[must_use]
325 pub fn column_count(&self) -> usize {
326 self.columns.len()
327 }
328
329 #[must_use]
330 pub fn row_count(&self) -> usize {
331 self.rows.len()
332 }
333
334 #[must_use]
335 pub fn is_empty(&self) -> bool {
336 self.rows.is_empty()
337 }
338
339 #[must_use]
341 pub fn column_names(&self) -> Vec<String> {
342 self.columns.iter().map(|c| c.name.clone()).collect()
343 }
344
345 pub fn infer_column_types(&mut self) {
347 for (col_idx, column) in self.columns.iter_mut().enumerate() {
348 let mut inferred_type = DataType::Null;
349 let mut null_count = 0;
350 let mut unique_values = std::collections::HashSet::new();
351
352 for row in &self.rows {
353 if let Some(value) = row.get(col_idx) {
354 if value.is_null() {
355 null_count += 1;
356 } else {
357 let value_type = value.data_type();
358 inferred_type = inferred_type.merge(&value_type);
359 unique_values.insert(value.to_string());
360 }
361 }
362 }
363
364 column.data_type = inferred_type;
365 column.null_count = null_count;
366 column.nullable = null_count > 0;
367 column.unique_values = Some(unique_values.len());
368 }
369 }
370
371 #[must_use]
373 pub fn get_value(&self, row: usize, col: usize) -> Option<&DataValue> {
374 self.rows.get(row)?.get(col)
375 }
376
377 #[must_use]
379 pub fn get_value_by_name(&self, row: usize, col_name: &str) -> Option<&DataValue> {
380 let col_idx = self.get_column_index(col_name)?;
381 self.get_value(row, col_idx)
382 }
383
384 #[must_use]
386 pub fn to_string_table(&self) -> Vec<Vec<String>> {
387 self.rows
388 .iter()
389 .map(|row| {
390 row.values
391 .iter()
392 .map(DataValue::to_string_optimized)
393 .collect()
394 })
395 .collect()
396 }
397
398 #[must_use]
400 pub fn get_stats(&self) -> DataTableStats {
401 DataTableStats {
402 row_count: self.row_count(),
403 column_count: self.column_count(),
404 memory_size: self.estimate_memory_size(),
405 null_count: self.columns.iter().map(|c| c.null_count).sum(),
406 }
407 }
408
409 #[must_use]
411 pub fn debug_dump(&self) -> String {
412 let mut output = String::new();
413
414 output.push_str(&format!("DataTable: {}\n", self.name));
415 output.push_str(&format!(
416 "Rows: {} | Columns: {}\n",
417 self.row_count(),
418 self.column_count()
419 ));
420
421 if !self.metadata.is_empty() {
422 output.push_str("Metadata:\n");
423 for (key, value) in &self.metadata {
424 output.push_str(&format!(" {key}: {value}\n"));
425 }
426 }
427
428 output.push_str("\nColumns:\n");
429 for column in &self.columns {
430 output.push_str(&format!(" {} ({:?})", column.name, column.data_type));
431 if column.nullable {
432 output.push_str(&format!(" - nullable, {} nulls", column.null_count));
433 }
434 if let Some(unique) = column.unique_values {
435 output.push_str(&format!(", {unique} unique"));
436 }
437 output.push('\n');
438 }
439
440 if self.row_count() > 0 {
442 let sample_size = 5.min(self.row_count());
443 output.push_str(&format!("\nFirst {sample_size} rows:\n"));
444
445 for row_idx in 0..sample_size {
446 output.push_str(&format!(" [{row_idx}]: "));
447 for (col_idx, value) in self.rows[row_idx].values.iter().enumerate() {
448 if col_idx > 0 {
449 output.push_str(", ");
450 }
451 output.push_str(&value.to_string());
452 }
453 output.push('\n');
454 }
455 }
456
457 output
458 }
459
460 #[must_use]
461 pub fn estimate_memory_size(&self) -> usize {
462 let mut size = std::mem::size_of::<Self>();
464
465 size += self.columns.len() * std::mem::size_of::<DataColumn>();
467 for col in &self.columns {
468 size += col.name.len();
469 }
470
471 size += self.rows.len() * std::mem::size_of::<DataRow>();
473
474 for row in &self.rows {
476 for value in &row.values {
477 size += std::mem::size_of::<DataValue>();
479 match value {
481 DataValue::String(s) | DataValue::DateTime(s) => size += s.len(),
482 _ => {} }
484 }
485 }
486
487 size
488 }
489
490 pub fn from_query_response(response: &QueryResponse, table_name: &str) -> Result<Self, String> {
493 debug!(
494 "V46: Converting QueryResponse to DataTable for table '{}'",
495 table_name
496 );
497
498 crate::utils::memory_tracker::track_memory("start_from_query_response");
500
501 let mut table = DataTable::new(table_name);
502
503 if let Some(first_row) = response.data.first() {
505 if let Some(obj) = first_row.as_object() {
506 for key in obj.keys() {
508 let column = DataColumn::new(key.clone());
509 table.add_column(column);
510 }
511
512 for json_row in &response.data {
514 if let Some(row_obj) = json_row.as_object() {
515 let mut values = Vec::new();
516
517 for column in &table.columns {
519 let value = row_obj
520 .get(&column.name)
521 .map_or(DataValue::Null, json_value_to_data_value);
522 values.push(value);
523 }
524
525 table.add_row(DataRow::new(values))?;
526 }
527 }
528
529 table.infer_column_types();
531
532 if let Some(source) = &response.source {
534 table.metadata.insert("source".to_string(), source.clone());
535 }
536 if let Some(cached) = response.cached {
537 table
538 .metadata
539 .insert("cached".to_string(), cached.to_string());
540 }
541 table
542 .metadata
543 .insert("original_count".to_string(), response.count.to_string());
544
545 debug!(
546 "V46: Created DataTable with {} columns and {} rows",
547 table.column_count(),
548 table.row_count()
549 );
550 } else {
551 table.add_column(DataColumn::new("value"));
553 for json_value in &response.data {
554 let value = json_value_to_data_value(json_value);
555 table.add_row(DataRow::new(vec![value]))?;
556 }
557 }
558 }
559
560 Ok(table)
561 }
562
563 #[must_use]
565 pub fn get_row(&self, index: usize) -> Option<&DataRow> {
566 self.rows.get(index)
567 }
568
569 #[must_use]
571 pub fn get_row_as_strings(&self, index: usize) -> Option<Vec<String>> {
572 self.rows.get(index).map(|row| {
573 row.values
574 .iter()
575 .map(DataValue::to_string_optimized)
576 .collect()
577 })
578 }
579
580 #[must_use]
582 pub fn pretty_print(&self) -> String {
583 let mut output = String::new();
584
585 output.push_str("╔═══════════════════════════════════════════════════════╗\n");
587 output.push_str(&format!("║ DataTable: {:^41} ║\n", self.name));
588 output.push_str("╠═══════════════════════════════════════════════════════╣\n");
589
590 output.push_str(&format!(
592 "║ Rows: {:6} | Columns: {:3} | Memory: ~{:6} bytes ║\n",
593 self.row_count(),
594 self.column_count(),
595 self.get_stats().memory_size
596 ));
597
598 if !self.metadata.is_empty() {
600 output.push_str("╠═══════════════════════════════════════════════════════╣\n");
601 output.push_str("║ Metadata: ║\n");
602 for (key, value) in &self.metadata {
603 let truncated_value = if value.len() > 35 {
604 format!("{}...", &value[..32])
605 } else {
606 value.clone()
607 };
608 output.push_str(&format!(
609 "║ {:15} : {:35} ║\n",
610 Self::truncate_string(key, 15),
611 truncated_value
612 ));
613 }
614 }
615
616 output.push_str("╠═══════════════════════════════════════════════════════╣\n");
618 output.push_str("║ Columns: ║\n");
619 output.push_str("╟───────────────────┬──────────┬─────────┬──────┬──────╢\n");
620 output.push_str("║ Name │ Type │ Nullable│ Nulls│Unique║\n");
621 output.push_str("╟───────────────────┼──────────┼─────────┼──────┼──────╢\n");
622
623 for column in &self.columns {
624 let type_str = match &column.data_type {
625 DataType::String => "String",
626 DataType::Integer => "Integer",
627 DataType::Float => "Float",
628 DataType::Boolean => "Boolean",
629 DataType::DateTime => "DateTime",
630 DataType::Null => "Null",
631 DataType::Mixed => "Mixed",
632 };
633
634 output.push_str(&format!(
635 "║ {:17} │ {:8} │ {:7} │ {:4} │ {:4} ║\n",
636 Self::truncate_string(&column.name, 17),
637 type_str,
638 if column.nullable { "Yes" } else { "No" },
639 column.null_count,
640 column.unique_values.unwrap_or(0)
641 ));
642 }
643
644 output.push_str("╚═══════════════════════════════════════════════════════╝\n");
645
646 output.push_str("\nSample Data (first 5 rows):\n");
648 let sample_count = self.rows.len().min(5);
649
650 if sample_count > 0 {
651 output.push('┌');
653 for (i, _col) in self.columns.iter().enumerate() {
654 if i > 0 {
655 output.push('┬');
656 }
657 output.push_str(&"─".repeat(20));
658 }
659 output.push_str("┐\n");
660
661 output.push('│');
662 for col in &self.columns {
663 output.push_str(&format!(" {:^18} │", Self::truncate_string(&col.name, 18)));
664 }
665 output.push('\n');
666
667 output.push('├');
668 for (i, _) in self.columns.iter().enumerate() {
669 if i > 0 {
670 output.push('┼');
671 }
672 output.push_str(&"─".repeat(20));
673 }
674 output.push_str("┤\n");
675
676 for row_idx in 0..sample_count {
678 if let Some(row) = self.rows.get(row_idx) {
679 output.push('│');
680 for value in &row.values {
681 let value_str = value.to_string();
682 output
683 .push_str(&format!(" {:18} │", Self::truncate_string(&value_str, 18)));
684 }
685 output.push('\n');
686 }
687 }
688
689 output.push('└');
690 for (i, _) in self.columns.iter().enumerate() {
691 if i > 0 {
692 output.push('┴');
693 }
694 output.push_str(&"─".repeat(20));
695 }
696 output.push_str("┘\n");
697 }
698
699 output
700 }
701
702 fn truncate_string(s: &str, max_len: usize) -> String {
703 if s.len() > max_len {
704 format!("{}...", &s[..max_len - 3])
705 } else {
706 s.to_string()
707 }
708 }
709
710 #[must_use]
712 pub fn get_schema_summary(&self) -> String {
713 let mut summary = String::new();
714 summary.push_str(&format!(
715 "DataTable Schema ({} columns, {} rows):\n",
716 self.columns.len(),
717 self.rows.len()
718 ));
719
720 for (idx, column) in self.columns.iter().enumerate() {
721 let type_str = match &column.data_type {
722 DataType::String => "String",
723 DataType::Integer => "Integer",
724 DataType::Float => "Float",
725 DataType::Boolean => "Boolean",
726 DataType::DateTime => "DateTime",
727 DataType::Null => "Null",
728 DataType::Mixed => "Mixed",
729 };
730
731 let nullable_str = if column.nullable {
732 "nullable"
733 } else {
734 "not null"
735 };
736 let null_info = if column.null_count > 0 {
737 format!(", {} nulls", column.null_count)
738 } else {
739 String::new()
740 };
741
742 summary.push_str(&format!(
743 " [{:3}] {} : {} ({}{})\n",
744 idx, column.name, type_str, nullable_str, null_info
745 ));
746 }
747
748 summary
749 }
750
751 #[must_use]
753 pub fn get_schema_info(&self) -> Vec<(String, String, bool, usize)> {
754 self.columns
755 .iter()
756 .map(|col| {
757 let type_name = format!("{:?}", col.data_type);
758 (col.name.clone(), type_name, col.nullable, col.null_count)
759 })
760 .collect()
761 }
762
763 pub fn reserve_rows(&mut self, additional: usize) {
765 self.rows.reserve(additional);
766 }
767
768 pub fn shrink_to_fit(&mut self) {
770 self.rows.shrink_to_fit();
771 for _column in &mut self.columns {
772 }
774 }
775
776 #[must_use]
778 pub fn get_memory_usage(&self) -> usize {
779 let mut size = std::mem::size_of::<Self>();
780
781 size += self.name.capacity();
783
784 size += self.columns.capacity() * std::mem::size_of::<DataColumn>();
786 for col in &self.columns {
787 size += col.name.capacity();
788 }
789
790 size += self.rows.capacity() * std::mem::size_of::<DataRow>();
792
793 for row in &self.rows {
795 size += row.values.capacity() * std::mem::size_of::<DataValue>();
796 for value in &row.values {
797 match value {
798 DataValue::String(s) => size += s.capacity(),
799 DataValue::InternedString(_) => size += std::mem::size_of::<Arc<String>>(),
800 DataValue::DateTime(s) => size += s.capacity(),
801 _ => {} }
803 }
804 }
805
806 size += self.metadata.capacity() * std::mem::size_of::<(String, String)>();
808 for (k, v) in &self.metadata {
809 size += k.capacity() + v.capacity();
810 }
811
812 size
813 }
814}
815
816fn json_value_to_data_value(json: &JsonValue) -> DataValue {
818 match json {
819 JsonValue::Null => DataValue::Null,
820 JsonValue::Bool(b) => DataValue::Boolean(*b),
821 JsonValue::Number(n) => {
822 if let Some(i) = n.as_i64() {
823 DataValue::Integer(i)
824 } else if let Some(f) = n.as_f64() {
825 DataValue::Float(f)
826 } else {
827 DataValue::String(n.to_string())
828 }
829 }
830 JsonValue::String(s) => {
831 if s.contains('-') && s.len() >= 8 && s.len() <= 30 {
833 DataValue::DateTime(s.clone())
835 } else {
836 DataValue::String(s.clone())
837 }
838 }
839 JsonValue::Array(_) | JsonValue::Object(_) => {
840 DataValue::String(json.to_string())
842 }
843 }
844}
845
846#[derive(Debug, Clone)]
848pub struct DataTableStats {
849 pub row_count: usize,
850 pub column_count: usize,
851 pub memory_size: usize,
852 pub null_count: usize,
853}
854
855impl DataProvider for DataTable {
858 fn get_row(&self, index: usize) -> Option<Vec<String>> {
859 self.rows.get(index).map(|row| {
860 row.values
861 .iter()
862 .map(DataValue::to_string_optimized)
863 .collect()
864 })
865 }
866
867 fn get_column_names(&self) -> Vec<String> {
868 self.column_names()
869 }
870
871 fn get_row_count(&self) -> usize {
872 self.row_count()
873 }
874
875 fn get_column_count(&self) -> usize {
876 self.column_count()
877 }
878}
879
880#[cfg(test)]
881mod tests {
882 use super::*;
883
884 #[test]
885 fn test_data_type_inference() {
886 assert_eq!(DataType::infer_from_string("123"), DataType::Integer);
887 assert_eq!(DataType::infer_from_string("123.45"), DataType::Float);
888 assert_eq!(DataType::infer_from_string("true"), DataType::Boolean);
889 assert_eq!(DataType::infer_from_string("hello"), DataType::String);
890 assert_eq!(DataType::infer_from_string(""), DataType::Null);
891 assert_eq!(
892 DataType::infer_from_string("2024-01-01"),
893 DataType::DateTime
894 );
895 }
896
897 #[test]
898 fn test_datatable_creation() {
899 let mut table = DataTable::new("test");
900
901 table.add_column(DataColumn::new("id").with_type(DataType::Integer));
902 table.add_column(DataColumn::new("name").with_type(DataType::String));
903 table.add_column(DataColumn::new("active").with_type(DataType::Boolean));
904
905 assert_eq!(table.column_count(), 3);
906 assert_eq!(table.row_count(), 0);
907
908 let row = DataRow::new(vec![
909 DataValue::Integer(1),
910 DataValue::String("Alice".to_string()),
911 DataValue::Boolean(true),
912 ]);
913
914 table.add_row(row).unwrap();
915 assert_eq!(table.row_count(), 1);
916
917 let value = table.get_value_by_name(0, "name").unwrap();
918 assert_eq!(value.to_string(), "Alice");
919 }
920
921 #[test]
922 fn test_type_inference() {
923 let mut table = DataTable::new("test");
924
925 table.add_column(DataColumn::new("mixed"));
927
928 table
930 .add_row(DataRow::new(vec![DataValue::Integer(1)]))
931 .unwrap();
932 table
933 .add_row(DataRow::new(vec![DataValue::Float(2.5)]))
934 .unwrap();
935 table.add_row(DataRow::new(vec![DataValue::Null])).unwrap();
936
937 table.infer_column_types();
938
939 assert_eq!(table.columns[0].data_type, DataType::Float);
941 assert_eq!(table.columns[0].null_count, 1);
942 assert!(table.columns[0].nullable);
943 }
944
945 #[test]
946 fn test_from_query_response() {
947 use crate::api_client::{QueryInfo, QueryResponse};
948 use serde_json::json;
949
950 let response = QueryResponse {
951 query: QueryInfo {
952 select: vec!["id".to_string(), "name".to_string(), "age".to_string()],
953 where_clause: None,
954 order_by: None,
955 },
956 data: vec![
957 json!({
958 "id": 1,
959 "name": "Alice",
960 "age": 30
961 }),
962 json!({
963 "id": 2,
964 "name": "Bob",
965 "age": 25
966 }),
967 json!({
968 "id": 3,
969 "name": "Carol",
970 "age": null
971 }),
972 ],
973 count: 3,
974 source: Some("test.csv".to_string()),
975 table: Some("test".to_string()),
976 cached: Some(false),
977 };
978
979 let table = DataTable::from_query_response(&response, "test").unwrap();
980
981 assert_eq!(table.name, "test");
982 assert_eq!(table.row_count(), 3);
983 assert_eq!(table.column_count(), 3);
984
985 let col_names = table.column_names();
987 assert!(col_names.contains(&"id".to_string()));
988 assert!(col_names.contains(&"name".to_string()));
989 assert!(col_names.contains(&"age".to_string()));
990
991 assert_eq!(table.metadata.get("source"), Some(&"test.csv".to_string()));
993 assert_eq!(table.metadata.get("cached"), Some(&"false".to_string()));
994
995 assert_eq!(
997 table.get_value_by_name(0, "id"),
998 Some(&DataValue::Integer(1))
999 );
1000 assert_eq!(
1001 table.get_value_by_name(0, "name"),
1002 Some(&DataValue::String("Alice".to_string()))
1003 );
1004 assert_eq!(
1005 table.get_value_by_name(0, "age"),
1006 Some(&DataValue::Integer(30))
1007 );
1008
1009 assert_eq!(table.get_value_by_name(2, "age"), Some(&DataValue::Null));
1011 }
1012}