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 pub fn infer_from_string(value: &str) -> Self {
26 if value.eq_ignore_ascii_case("null") {
28 return DataType::Null;
29 }
30
31 match TypeInference::infer_from_string(value) {
33 InferredType::Null => DataType::Null,
34 InferredType::Boolean => DataType::Boolean,
35 InferredType::Integer => DataType::Integer,
36 InferredType::Float => DataType::Float,
37 InferredType::DateTime => DataType::DateTime,
38 InferredType::String => DataType::String,
39 }
40 }
41
42 fn looks_like_datetime(value: &str) -> bool {
45 TypeInference::looks_like_datetime(value)
46 }
47
48 pub fn merge(&self, other: &DataType) -> DataType {
50 if self == other {
51 return self.clone();
52 }
53
54 match (self, other) {
55 (DataType::Null, t) | (t, DataType::Null) => t.clone(),
56 (DataType::Integer, DataType::Float) | (DataType::Float, DataType::Integer) => {
57 DataType::Float
58 }
59 _ => DataType::Mixed,
60 }
61 }
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct DataColumn {
67 pub name: String,
68 pub data_type: DataType,
69 pub nullable: bool,
70 pub unique_values: Option<usize>,
71 pub null_count: usize,
72 pub metadata: HashMap<String, String>,
73}
74
75impl DataColumn {
76 pub fn new(name: impl Into<String>) -> Self {
77 Self {
78 name: name.into(),
79 data_type: DataType::String,
80 nullable: true,
81 unique_values: None,
82 null_count: 0,
83 metadata: HashMap::new(),
84 }
85 }
86
87 pub fn with_type(mut self, data_type: DataType) -> Self {
88 self.data_type = data_type;
89 self
90 }
91
92 pub fn with_nullable(mut self, nullable: bool) -> Self {
93 self.nullable = nullable;
94 self
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, PartialOrd)]
100pub enum DataValue {
101 String(String),
102 InternedString(Arc<String>), Integer(i64),
104 Float(f64),
105 Boolean(bool),
106 DateTime(String), Null,
108}
109
110impl DataValue {
111 pub fn from_string(s: &str, data_type: &DataType) -> Self {
112 if s.is_empty() || s.eq_ignore_ascii_case("null") {
113 return DataValue::Null;
114 }
115
116 match data_type {
117 DataType::String => DataValue::String(s.to_string()),
118 DataType::Integer => s
119 .parse::<i64>()
120 .map(DataValue::Integer)
121 .unwrap_or_else(|_| DataValue::String(s.to_string())),
122 DataType::Float => s
123 .parse::<f64>()
124 .map(DataValue::Float)
125 .unwrap_or_else(|_| DataValue::String(s.to_string())),
126 DataType::Boolean => {
127 let lower = s.to_lowercase();
128 DataValue::Boolean(lower == "true" || lower == "1" || lower == "yes")
129 }
130 DataType::DateTime => DataValue::DateTime(s.to_string()),
131 DataType::Null => DataValue::Null,
132 DataType::Mixed => {
133 let inferred = DataType::infer_from_string(s);
135 Self::from_string(s, &inferred)
136 }
137 }
138 }
139
140 pub fn is_null(&self) -> bool {
141 matches!(self, DataValue::Null)
142 }
143
144 pub fn data_type(&self) -> DataType {
145 match self {
146 DataValue::String(_) | DataValue::InternedString(_) => DataType::String,
147 DataValue::Integer(_) => DataType::Integer,
148 DataValue::Float(_) => DataType::Float,
149 DataValue::Boolean(_) => DataType::Boolean,
150 DataValue::DateTime(_) => DataType::DateTime,
151 DataValue::Null => DataType::Null,
152 }
153 }
154
155 pub fn to_string_optimized(&self) -> String {
158 match self {
159 DataValue::String(s) => s.clone(), DataValue::InternedString(s) => s.as_ref().clone(), DataValue::DateTime(s) => s.clone(), DataValue::Integer(i) => i.to_string(),
163 DataValue::Float(f) => f.to_string(),
164 DataValue::Boolean(b) => {
165 if *b {
166 "true".to_string()
167 } else {
168 "false".to_string()
169 }
170 }
171 DataValue::Null => String::new(), }
173 }
174}
175
176impl fmt::Display for DataValue {
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 match self {
179 DataValue::String(s) => write!(f, "{}", s),
180 DataValue::InternedString(s) => write!(f, "{}", s),
181 DataValue::Integer(i) => write!(f, "{}", i),
182 DataValue::Float(fl) => write!(f, "{}", fl),
183 DataValue::Boolean(b) => write!(f, "{}", b),
184 DataValue::DateTime(dt) => write!(f, "{}", dt),
185 DataValue::Null => write!(f, ""),
186 }
187 }
188}
189
190#[derive(Debug, Clone)]
192pub struct DataRow {
193 pub values: Vec<DataValue>,
194}
195
196impl DataRow {
197 pub fn new(values: Vec<DataValue>) -> Self {
198 Self { values }
199 }
200
201 pub fn get(&self, index: usize) -> Option<&DataValue> {
202 self.values.get(index)
203 }
204
205 pub fn get_mut(&mut self, index: usize) -> Option<&mut DataValue> {
206 self.values.get_mut(index)
207 }
208
209 pub fn len(&self) -> usize {
210 self.values.len()
211 }
212
213 pub fn is_empty(&self) -> bool {
214 self.values.is_empty()
215 }
216}
217
218#[derive(Debug, Clone)]
220pub struct DataTable {
221 pub name: String,
222 pub columns: Vec<DataColumn>,
223 pub rows: Vec<DataRow>,
224 pub metadata: HashMap<String, String>,
225}
226
227impl DataTable {
228 pub fn new(name: impl Into<String>) -> Self {
229 Self {
230 name: name.into(),
231 columns: Vec::new(),
232 rows: Vec::new(),
233 metadata: HashMap::new(),
234 }
235 }
236
237 pub fn dual() -> Self {
240 let mut table = DataTable::new("DUAL");
241 table.add_column(DataColumn::new("DUMMY").with_type(DataType::String));
242 table
243 .add_row(DataRow::new(vec![DataValue::String("X".to_string())]))
244 .unwrap();
245 table
246 }
247
248 pub fn add_column(&mut self, column: DataColumn) -> &mut Self {
249 self.columns.push(column);
250 self
251 }
252
253 pub fn add_row(&mut self, row: DataRow) -> Result<(), String> {
254 if row.len() != self.columns.len() {
255 return Err(format!(
256 "Row has {} values but table has {} columns",
257 row.len(),
258 self.columns.len()
259 ));
260 }
261 self.rows.push(row);
262 Ok(())
263 }
264
265 pub fn get_column(&self, name: &str) -> Option<&DataColumn> {
266 self.columns.iter().find(|c| c.name == name)
267 }
268
269 pub fn get_column_index(&self, name: &str) -> Option<usize> {
270 self.columns.iter().position(|c| c.name == name)
271 }
272
273 pub fn column_count(&self) -> usize {
274 self.columns.len()
275 }
276
277 pub fn row_count(&self) -> usize {
278 self.rows.len()
279 }
280
281 pub fn is_empty(&self) -> bool {
282 self.rows.is_empty()
283 }
284
285 pub fn column_names(&self) -> Vec<String> {
287 self.columns.iter().map(|c| c.name.clone()).collect()
288 }
289
290 pub fn infer_column_types(&mut self) {
292 for (col_idx, column) in self.columns.iter_mut().enumerate() {
293 let mut inferred_type = DataType::Null;
294 let mut null_count = 0;
295 let mut unique_values = std::collections::HashSet::new();
296
297 for row in &self.rows {
298 if let Some(value) = row.get(col_idx) {
299 if value.is_null() {
300 null_count += 1;
301 } else {
302 let value_type = value.data_type();
303 inferred_type = inferred_type.merge(&value_type);
304 unique_values.insert(value.to_string());
305 }
306 }
307 }
308
309 column.data_type = inferred_type;
310 column.null_count = null_count;
311 column.nullable = null_count > 0;
312 column.unique_values = Some(unique_values.len());
313 }
314 }
315
316 pub fn get_value(&self, row: usize, col: usize) -> Option<&DataValue> {
318 self.rows.get(row)?.get(col)
319 }
320
321 pub fn get_value_by_name(&self, row: usize, col_name: &str) -> Option<&DataValue> {
323 let col_idx = self.get_column_index(col_name)?;
324 self.get_value(row, col_idx)
325 }
326
327 pub fn to_string_table(&self) -> Vec<Vec<String>> {
329 self.rows
330 .iter()
331 .map(|row| row.values.iter().map(|v| v.to_string_optimized()).collect())
332 .collect()
333 }
334
335 pub fn get_stats(&self) -> DataTableStats {
337 DataTableStats {
338 row_count: self.row_count(),
339 column_count: self.column_count(),
340 memory_size: self.estimate_memory_size(),
341 null_count: self.columns.iter().map(|c| c.null_count).sum(),
342 }
343 }
344
345 pub fn debug_dump(&self) -> String {
347 let mut output = String::new();
348
349 output.push_str(&format!("DataTable: {}\n", self.name));
350 output.push_str(&format!(
351 "Rows: {} | Columns: {}\n",
352 self.row_count(),
353 self.column_count()
354 ));
355
356 if !self.metadata.is_empty() {
357 output.push_str("Metadata:\n");
358 for (key, value) in &self.metadata {
359 output.push_str(&format!(" {}: {}\n", key, value));
360 }
361 }
362
363 output.push_str("\nColumns:\n");
364 for column in &self.columns {
365 output.push_str(&format!(" {} ({:?})", column.name, column.data_type));
366 if column.nullable {
367 output.push_str(&format!(" - nullable, {} nulls", column.null_count));
368 }
369 if let Some(unique) = column.unique_values {
370 output.push_str(&format!(", {} unique", unique));
371 }
372 output.push('\n');
373 }
374
375 if self.row_count() > 0 {
377 let sample_size = 5.min(self.row_count());
378 output.push_str(&format!("\nFirst {} rows:\n", sample_size));
379
380 for row_idx in 0..sample_size {
381 output.push_str(&format!(" [{}]: ", row_idx));
382 for (col_idx, value) in self.rows[row_idx].values.iter().enumerate() {
383 if col_idx > 0 {
384 output.push_str(", ");
385 }
386 output.push_str(&value.to_string());
387 }
388 output.push('\n');
389 }
390 }
391
392 output
393 }
394
395 pub fn estimate_memory_size(&self) -> usize {
396 let mut size = std::mem::size_of::<Self>();
398
399 size += self.columns.len() * std::mem::size_of::<DataColumn>();
401 for col in &self.columns {
402 size += col.name.len();
403 }
404
405 size += self.rows.len() * std::mem::size_of::<DataRow>();
407
408 for row in &self.rows {
410 for value in &row.values {
411 size += std::mem::size_of::<DataValue>();
413 match value {
415 DataValue::String(s) | DataValue::DateTime(s) => size += s.len(),
416 _ => {} }
418 }
419 }
420
421 size
422 }
423
424 pub fn from_query_response(response: &QueryResponse, table_name: &str) -> Result<Self, String> {
427 debug!(
428 "V46: Converting QueryResponse to DataTable for table '{}'",
429 table_name
430 );
431
432 crate::utils::memory_tracker::track_memory("start_from_query_response");
434
435 let mut table = DataTable::new(table_name);
436
437 if let Some(first_row) = response.data.first() {
439 if let Some(obj) = first_row.as_object() {
440 for key in obj.keys() {
442 let column = DataColumn::new(key.clone());
443 table.add_column(column);
444 }
445
446 for json_row in &response.data {
448 if let Some(row_obj) = json_row.as_object() {
449 let mut values = Vec::new();
450
451 for column in &table.columns {
453 let value = row_obj
454 .get(&column.name)
455 .map(|v| json_value_to_data_value(v))
456 .unwrap_or(DataValue::Null);
457 values.push(value);
458 }
459
460 table.add_row(DataRow::new(values))?;
461 }
462 }
463
464 table.infer_column_types();
466
467 if let Some(source) = &response.source {
469 table.metadata.insert("source".to_string(), source.clone());
470 }
471 if let Some(cached) = response.cached {
472 table
473 .metadata
474 .insert("cached".to_string(), cached.to_string());
475 }
476 table
477 .metadata
478 .insert("original_count".to_string(), response.count.to_string());
479
480 debug!(
481 "V46: Created DataTable with {} columns and {} rows",
482 table.column_count(),
483 table.row_count()
484 );
485 } else {
486 table.add_column(DataColumn::new("value"));
488 for json_value in &response.data {
489 let value = json_value_to_data_value(json_value);
490 table.add_row(DataRow::new(vec![value]))?;
491 }
492 }
493 }
494
495 Ok(table)
496 }
497
498 pub fn get_row(&self, index: usize) -> Option<&DataRow> {
500 self.rows.get(index)
501 }
502
503 pub fn get_row_as_strings(&self, index: usize) -> Option<Vec<String>> {
505 self.rows.get(index).map(|row| {
506 row.values
507 .iter()
508 .map(|value| value.to_string_optimized())
509 .collect()
510 })
511 }
512
513 pub fn pretty_print(&self) -> String {
515 let mut output = String::new();
516
517 output.push_str("╔═══════════════════════════════════════════════════════╗\n");
519 output.push_str(&format!("║ DataTable: {:^41} ║\n", self.name));
520 output.push_str("╠═══════════════════════════════════════════════════════╣\n");
521
522 output.push_str(&format!(
524 "║ Rows: {:6} | Columns: {:3} | Memory: ~{:6} bytes ║\n",
525 self.row_count(),
526 self.column_count(),
527 self.get_stats().memory_size
528 ));
529
530 if !self.metadata.is_empty() {
532 output.push_str("╠═══════════════════════════════════════════════════════╣\n");
533 output.push_str("║ Metadata: ║\n");
534 for (key, value) in &self.metadata {
535 let truncated_value = if value.len() > 35 {
536 format!("{}...", &value[..32])
537 } else {
538 value.clone()
539 };
540 output.push_str(&format!(
541 "║ {:15} : {:35} ║\n",
542 Self::truncate_string(key, 15),
543 truncated_value
544 ));
545 }
546 }
547
548 output.push_str("╠═══════════════════════════════════════════════════════╣\n");
550 output.push_str("║ Columns: ║\n");
551 output.push_str("╟───────────────────┬──────────┬─────────┬──────┬──────╢\n");
552 output.push_str("║ Name │ Type │ Nullable│ Nulls│Unique║\n");
553 output.push_str("╟───────────────────┼──────────┼─────────┼──────┼──────╢\n");
554
555 for column in &self.columns {
556 let type_str = match &column.data_type {
557 DataType::String => "String",
558 DataType::Integer => "Integer",
559 DataType::Float => "Float",
560 DataType::Boolean => "Boolean",
561 DataType::DateTime => "DateTime",
562 DataType::Null => "Null",
563 DataType::Mixed => "Mixed",
564 };
565
566 output.push_str(&format!(
567 "║ {:17} │ {:8} │ {:7} │ {:4} │ {:4} ║\n",
568 Self::truncate_string(&column.name, 17),
569 type_str,
570 if column.nullable { "Yes" } else { "No" },
571 column.null_count,
572 column.unique_values.unwrap_or(0)
573 ));
574 }
575
576 output.push_str("╚═══════════════════════════════════════════════════════╝\n");
577
578 output.push_str("\nSample Data (first 5 rows):\n");
580 let sample_count = self.rows.len().min(5);
581
582 if sample_count > 0 {
583 output.push_str("┌");
585 for (i, col) in self.columns.iter().enumerate() {
586 if i > 0 {
587 output.push_str("┬");
588 }
589 output.push_str(&"─".repeat(20));
590 }
591 output.push_str("┐\n");
592
593 output.push_str("│");
594 for col in &self.columns {
595 output.push_str(&format!(" {:^18} │", Self::truncate_string(&col.name, 18)));
596 }
597 output.push_str("\n");
598
599 output.push_str("├");
600 for (i, _) in self.columns.iter().enumerate() {
601 if i > 0 {
602 output.push_str("┼");
603 }
604 output.push_str(&"─".repeat(20));
605 }
606 output.push_str("┤\n");
607
608 for row_idx in 0..sample_count {
610 if let Some(row) = self.rows.get(row_idx) {
611 output.push_str("│");
612 for value in &row.values {
613 let value_str = value.to_string();
614 output
615 .push_str(&format!(" {:18} │", Self::truncate_string(&value_str, 18)));
616 }
617 output.push_str("\n");
618 }
619 }
620
621 output.push_str("└");
622 for (i, _) in self.columns.iter().enumerate() {
623 if i > 0 {
624 output.push_str("┴");
625 }
626 output.push_str(&"─".repeat(20));
627 }
628 output.push_str("┘\n");
629 }
630
631 output
632 }
633
634 fn truncate_string(s: &str, max_len: usize) -> String {
635 if s.len() > max_len {
636 format!("{}...", &s[..max_len - 3])
637 } else {
638 s.to_string()
639 }
640 }
641
642 pub fn get_schema_summary(&self) -> String {
644 let mut summary = String::new();
645 summary.push_str(&format!(
646 "DataTable Schema ({} columns, {} rows):\n",
647 self.columns.len(),
648 self.rows.len()
649 ));
650
651 for (idx, column) in self.columns.iter().enumerate() {
652 let type_str = match &column.data_type {
653 DataType::String => "String",
654 DataType::Integer => "Integer",
655 DataType::Float => "Float",
656 DataType::Boolean => "Boolean",
657 DataType::DateTime => "DateTime",
658 DataType::Null => "Null",
659 DataType::Mixed => "Mixed",
660 };
661
662 let nullable_str = if column.nullable {
663 "nullable"
664 } else {
665 "not null"
666 };
667 let null_info = if column.null_count > 0 {
668 format!(", {} nulls", column.null_count)
669 } else {
670 String::new()
671 };
672
673 summary.push_str(&format!(
674 " [{:3}] {} : {} ({}{})\n",
675 idx, column.name, type_str, nullable_str, null_info
676 ));
677 }
678
679 summary
680 }
681
682 pub fn get_schema_info(&self) -> Vec<(String, String, bool, usize)> {
684 self.columns
685 .iter()
686 .map(|col| {
687 let type_name = format!("{:?}", col.data_type);
688 (col.name.clone(), type_name, col.nullable, col.null_count)
689 })
690 .collect()
691 }
692
693 pub fn reserve_rows(&mut self, additional: usize) {
695 self.rows.reserve(additional);
696 }
697
698 pub fn shrink_to_fit(&mut self) {
700 self.rows.shrink_to_fit();
701 for column in &mut self.columns {
702 }
704 }
705
706 pub fn get_memory_usage(&self) -> usize {
708 let mut size = std::mem::size_of::<Self>();
709
710 size += self.name.capacity();
712
713 size += self.columns.capacity() * std::mem::size_of::<DataColumn>();
715 for col in &self.columns {
716 size += col.name.capacity();
717 }
718
719 size += self.rows.capacity() * std::mem::size_of::<DataRow>();
721
722 for row in &self.rows {
724 size += row.values.capacity() * std::mem::size_of::<DataValue>();
725 for value in &row.values {
726 match value {
727 DataValue::String(s) => size += s.capacity(),
728 DataValue::InternedString(_) => size += std::mem::size_of::<Arc<String>>(),
729 DataValue::DateTime(s) => size += s.capacity(),
730 _ => {} }
732 }
733 }
734
735 size += self.metadata.capacity() * std::mem::size_of::<(String, String)>();
737 for (k, v) in &self.metadata {
738 size += k.capacity() + v.capacity();
739 }
740
741 size
742 }
743}
744
745fn json_value_to_data_value(json: &JsonValue) -> DataValue {
747 match json {
748 JsonValue::Null => DataValue::Null,
749 JsonValue::Bool(b) => DataValue::Boolean(*b),
750 JsonValue::Number(n) => {
751 if let Some(i) = n.as_i64() {
752 DataValue::Integer(i)
753 } else if let Some(f) = n.as_f64() {
754 DataValue::Float(f)
755 } else {
756 DataValue::String(n.to_string())
757 }
758 }
759 JsonValue::String(s) => {
760 if s.contains('-') && s.len() >= 8 && s.len() <= 30 {
762 DataValue::DateTime(s.clone())
764 } else {
765 DataValue::String(s.clone())
766 }
767 }
768 JsonValue::Array(_) | JsonValue::Object(_) => {
769 DataValue::String(json.to_string())
771 }
772 }
773}
774
775#[derive(Debug, Clone)]
777pub struct DataTableStats {
778 pub row_count: usize,
779 pub column_count: usize,
780 pub memory_size: usize,
781 pub null_count: usize,
782}
783
784impl DataProvider for DataTable {
787 fn get_row(&self, index: usize) -> Option<Vec<String>> {
788 self.rows
789 .get(index)
790 .map(|row| row.values.iter().map(|v| v.to_string_optimized()).collect())
791 }
792
793 fn get_column_names(&self) -> Vec<String> {
794 self.column_names()
795 }
796
797 fn get_row_count(&self) -> usize {
798 self.row_count()
799 }
800
801 fn get_column_count(&self) -> usize {
802 self.column_count()
803 }
804}
805
806#[cfg(test)]
807mod tests {
808 use super::*;
809
810 #[test]
811 fn test_data_type_inference() {
812 assert_eq!(DataType::infer_from_string("123"), DataType::Integer);
813 assert_eq!(DataType::infer_from_string("123.45"), DataType::Float);
814 assert_eq!(DataType::infer_from_string("true"), DataType::Boolean);
815 assert_eq!(DataType::infer_from_string("hello"), DataType::String);
816 assert_eq!(DataType::infer_from_string(""), DataType::Null);
817 assert_eq!(
818 DataType::infer_from_string("2024-01-01"),
819 DataType::DateTime
820 );
821 }
822
823 #[test]
824 fn test_datatable_creation() {
825 let mut table = DataTable::new("test");
826
827 table.add_column(DataColumn::new("id").with_type(DataType::Integer));
828 table.add_column(DataColumn::new("name").with_type(DataType::String));
829 table.add_column(DataColumn::new("active").with_type(DataType::Boolean));
830
831 assert_eq!(table.column_count(), 3);
832 assert_eq!(table.row_count(), 0);
833
834 let row = DataRow::new(vec![
835 DataValue::Integer(1),
836 DataValue::String("Alice".to_string()),
837 DataValue::Boolean(true),
838 ]);
839
840 table.add_row(row).unwrap();
841 assert_eq!(table.row_count(), 1);
842
843 let value = table.get_value_by_name(0, "name").unwrap();
844 assert_eq!(value.to_string(), "Alice");
845 }
846
847 #[test]
848 fn test_type_inference() {
849 let mut table = DataTable::new("test");
850
851 table.add_column(DataColumn::new("mixed"));
853
854 table
856 .add_row(DataRow::new(vec![DataValue::Integer(1)]))
857 .unwrap();
858 table
859 .add_row(DataRow::new(vec![DataValue::Float(2.5)]))
860 .unwrap();
861 table.add_row(DataRow::new(vec![DataValue::Null])).unwrap();
862
863 table.infer_column_types();
864
865 assert_eq!(table.columns[0].data_type, DataType::Float);
867 assert_eq!(table.columns[0].null_count, 1);
868 assert!(table.columns[0].nullable);
869 }
870
871 #[test]
872 fn test_from_query_response() {
873 use crate::api_client::{QueryInfo, QueryResponse};
874 use serde_json::json;
875
876 let response = QueryResponse {
877 query: QueryInfo {
878 select: vec!["id".to_string(), "name".to_string(), "age".to_string()],
879 where_clause: None,
880 order_by: None,
881 },
882 data: vec![
883 json!({
884 "id": 1,
885 "name": "Alice",
886 "age": 30
887 }),
888 json!({
889 "id": 2,
890 "name": "Bob",
891 "age": 25
892 }),
893 json!({
894 "id": 3,
895 "name": "Carol",
896 "age": null
897 }),
898 ],
899 count: 3,
900 source: Some("test.csv".to_string()),
901 table: Some("test".to_string()),
902 cached: Some(false),
903 };
904
905 let table = DataTable::from_query_response(&response, "test").unwrap();
906
907 assert_eq!(table.name, "test");
908 assert_eq!(table.row_count(), 3);
909 assert_eq!(table.column_count(), 3);
910
911 let col_names = table.column_names();
913 assert!(col_names.contains(&"id".to_string()));
914 assert!(col_names.contains(&"name".to_string()));
915 assert!(col_names.contains(&"age".to_string()));
916
917 assert_eq!(table.metadata.get("source"), Some(&"test.csv".to_string()));
919 assert_eq!(table.metadata.get("cached"), Some(&"false".to_string()));
920
921 assert_eq!(
923 table.get_value_by_name(0, "id"),
924 Some(&DataValue::Integer(1))
925 );
926 assert_eq!(
927 table.get_value_by_name(0, "name"),
928 Some(&DataValue::String("Alice".to_string()))
929 );
930 assert_eq!(
931 table.get_value_by_name(0, "age"),
932 Some(&DataValue::Integer(30))
933 );
934
935 assert_eq!(table.get_value_by_name(2, "age"), Some(&DataValue::Null));
937 }
938}