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 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 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 #[must_use]
162 pub fn to_string_optimized(&self) -> String {
163 match self {
164 DataValue::String(s) => s.clone(), DataValue::InternedString(s) => s.as_ref().clone(), DataValue::DateTime(s) => s.clone(), 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(), }
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#[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#[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 #[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 #[must_use]
302 pub fn column_names(&self) -> Vec<String> {
303 self.columns.iter().map(|c| c.name.clone()).collect()
304 }
305
306 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 #[must_use]
334 pub fn get_value(&self, row: usize, col: usize) -> Option<&DataValue> {
335 self.rows.get(row)?.get(col)
336 }
337
338 #[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 #[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 #[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 #[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 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 let mut size = std::mem::size_of::<Self>();
425
426 size += self.columns.len() * std::mem::size_of::<DataColumn>();
428 for col in &self.columns {
429 size += col.name.len();
430 }
431
432 size += self.rows.len() * std::mem::size_of::<DataRow>();
434
435 for row in &self.rows {
437 for value in &row.values {
438 size += std::mem::size_of::<DataValue>();
440 match value {
442 DataValue::String(s) | DataValue::DateTime(s) => size += s.len(),
443 _ => {} }
445 }
446 }
447
448 size
449 }
450
451 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 crate::utils::memory_tracker::track_memory("start_from_query_response");
461
462 let mut table = DataTable::new(table_name);
463
464 if let Some(first_row) = response.data.first() {
466 if let Some(obj) = first_row.as_object() {
467 for key in obj.keys() {
469 let column = DataColumn::new(key.clone());
470 table.add_column(column);
471 }
472
473 for json_row in &response.data {
475 if let Some(row_obj) = json_row.as_object() {
476 let mut values = Vec::new();
477
478 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 table.infer_column_types();
492
493 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 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 #[must_use]
526 pub fn get_row(&self, index: usize) -> Option<&DataRow> {
527 self.rows.get(index)
528 }
529
530 #[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 #[must_use]
543 pub fn pretty_print(&self) -> String {
544 let mut output = String::new();
545
546 output.push_str("╔═══════════════════════════════════════════════════════╗\n");
548 output.push_str(&format!("║ DataTable: {:^41} ║\n", self.name));
549 output.push_str("╠═══════════════════════════════════════════════════════╣\n");
550
551 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 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 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 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 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 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 #[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 #[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 pub fn reserve_rows(&mut self, additional: usize) {
726 self.rows.reserve(additional);
727 }
728
729 pub fn shrink_to_fit(&mut self) {
731 self.rows.shrink_to_fit();
732 for _column in &mut self.columns {
733 }
735 }
736
737 #[must_use]
739 pub fn get_memory_usage(&self) -> usize {
740 let mut size = std::mem::size_of::<Self>();
741
742 size += self.name.capacity();
744
745 size += self.columns.capacity() * std::mem::size_of::<DataColumn>();
747 for col in &self.columns {
748 size += col.name.capacity();
749 }
750
751 size += self.rows.capacity() * std::mem::size_of::<DataRow>();
753
754 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 _ => {} }
764 }
765 }
766
767 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
777fn 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 if s.contains('-') && s.len() >= 8 && s.len() <= 30 {
794 DataValue::DateTime(s.clone())
796 } else {
797 DataValue::String(s.clone())
798 }
799 }
800 JsonValue::Array(_) | JsonValue::Object(_) => {
801 DataValue::String(json.to_string())
803 }
804 }
805}
806
807#[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
816impl 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 table.add_column(DataColumn::new("mixed"));
888
889 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 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 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 assert_eq!(table.metadata.get("source"), Some(&"test.csv".to_string()));
954 assert_eq!(table.metadata.get("cached"), Some(&"false".to_string()));
955
956 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 assert_eq!(table.get_value_by_name(2, "age"), Some(&DataValue::Null));
972 }
973}