Skip to main content

zql_cli/db/
dataset.rs

1use crate::core::case::Payload;
2use crate::db::format::Format;
3use crate::db::layout::flatten::{FlattenLayout, FlattenVerbose};
4use crate::db::layout::table::{TableHeader, TableLayout};
5use crate::db::metadata::Metadata;
6use crate::error::MyResult;
7use crate::util::convert::format_null;
8use crate::util::painter::{Painter, Style};
9use csv::{Reader, ReaderBuilder};
10use odbc_api::buffers::TextRowSet;
11use odbc_api::handles::StatementImpl;
12use odbc_api::{Cursor, CursorImpl, DataType, Nullability};
13use std::borrow::Cow;
14use std::cell::RefCell;
15use std::cmp::Ordering;
16use std::io::Read;
17use std::iter::zip;
18
19pub type Column = (String, Format, Metadata);
20pub type Record = Vec<(String, Format)>;
21
22enum Source<'a, R: Read> {
23    Cursor(CursorImpl<StatementImpl<'a>>),
24    File(Reader<R>),
25    None,
26}
27
28pub struct Dataset<'a, R: Read> {
29    pub columns: Vec<Column>,
30    records: RefCell<Vec<(Record, bool)>>,
31    source: RefCell<Source<'a, R>>,
32    widths: RefCell<Option<Vec<Format>>>,
33    auto: bool,
34}
35
36impl<'a, R: Read> Dataset<'a, R> {
37    pub fn from_cursor(mut cursor: CursorImpl<StatementImpl<'a>>) -> MyResult<Option<Self>> {
38        let columns = fetch_columns(&mut cursor)?;
39        if !columns.is_empty() {
40            let records = RefCell::new(Vec::new());
41            let source = RefCell::new(Source::Cursor(cursor));
42            let widths = RefCell::new(None);
43            let dataset = Self { columns, records, source, widths, auto: false };
44            Ok(Some(dataset))
45        } else {
46            Ok(None)
47        }
48    }
49
50    pub fn from_file(reader: R, auto: bool) -> MyResult<Option<Self>> {
51        let mut reader = ReaderBuilder::new().from_reader(reader);
52        let headers = reader.headers()?;
53        if !headers.is_empty() {
54            let dtype = if auto { DataType::Unknown } else { DataType::Char { length: None } };
55            let columns = headers
56                .iter()
57                .map(|name| measure_text(name))
58                .map(|(name, format)| (name, format, Metadata::new(dtype, Nullability::Unknown)))
59                .collect();
60            let records = RefCell::new(Vec::new());
61            let source = RefCell::new(Source::File(reader));
62            let widths = RefCell::new(None);
63            let dataset = Self { columns, records, source, widths, auto };
64            Ok(Some(dataset))
65        } else {
66            Ok(None)
67        }
68    }
69
70    pub fn from_keywords(
71        column: &str,
72        records: Vec<&str>,
73    ) -> Self {
74        let (name, format) = measure_text(column);
75        let columns = vec![
76            (name, format, Metadata::new(DataType::Unknown, Nullability::Unknown)),
77        ];
78        let records = records
79            .into_iter()
80            .map(|value| measure_text(value))
81            .map(|pair| (vec![pair], false))
82            .collect();
83        let records = RefCell::new(records);
84        let source = RefCell::new(Source::None);
85        let widths = RefCell::new(None);
86        Self { columns, records, source, widths, auto: false }
87    }
88
89    // noinspection DuplicatedCode
90    pub fn from_tables(
91        column1: Option<&str>,
92        column2: Option<&str>,
93        column3: &str,
94        records: Vec<(&str, &str, &str)>,
95    ) -> Self {
96        let mut columns = Vec::new();
97        let mut append = |column: &str, dtype: DataType| {
98            let (name, format) = measure_text(column);
99            columns.push((name, format, Metadata::new(dtype, Nullability::Unknown)));
100        };
101        let records = if let Some(column1) = column1 {
102            if let Some(column2) = column2 {
103                append(column1, DataType::Unknown);
104                append(column2, DataType::Unknown);
105                append(column3, DataType::Unknown);
106                records
107                    .into_iter()
108                    .map(|(v1, v2, v3)| (measure_text(v1), measure_text(v2), measure_text(v3)))
109                    .map(|(p1, p2, p3)| (vec![p1, p2, p3], false))
110                    .collect()
111            } else {
112                append(column1, DataType::Unknown);
113                append(column3, DataType::Unknown);
114                records
115                    .into_iter()
116                    .map(|(v1, _, v3)| (measure_text(v1), measure_text(v3)))
117                    .map(|(p1, p3)| (vec![p1, p3], false))
118                    .collect()
119            }
120        } else {
121            append(column3, DataType::Unknown);
122            records
123                .into_iter()
124                .map(|(_, _, v3)| measure_text(v3))
125                .map(|p3| (vec![p3], false))
126                .collect()
127        };
128        let records = RefCell::new(records);
129        let source = RefCell::new(Source::None);
130        let widths = RefCell::new(None);
131        Self { columns, records, source, widths, auto: false }
132    }
133
134    // noinspection DuplicatedCode
135    pub fn from_columns(
136        column1: Option<&str>,
137        column2: Option<&str>,
138        records: Vec<(&str, &str, &str, &str, Option<&Payload>)>,
139    ) -> Self {
140        let mut columns = Vec::new();
141        let mut append = |column: &str, dtype: DataType| {
142            let (name, format) = measure_text(column);
143            columns.push((name, format, Metadata::new(dtype, Nullability::Unknown)));
144        };
145        let metadata = Metadata::new(DataType::Integer, Nullability::Unknown);
146        let records = if let Some(column1) = column1 {
147            if let Some(column2) = column2 {
148                append(column1, DataType::Unknown);
149                append(column2, DataType::Unknown);
150                append("table", DataType::Unknown);
151                append("column", DataType::Unknown);
152                append("type", DataType::Unknown);
153                append("nullable", DataType::Unknown);
154                append("index", DataType::Integer);
155                records
156                    .into_iter()
157                    .map(|(v1, v2, v3, v4, p5)| (
158                        measure_text(v1),
159                        measure_text(v2),
160                        measure_text(v3),
161                        measure_text(v4),
162                        measure_type(p5),
163                        measure_null(p5),
164                        measure_index(p5, &metadata),
165                        group_index(p5),
166                    ))
167                    .map(|(p1, p2, p3, p4, p5, p6, p7, sep)| (vec![p1, p2, p3, p4, p5, p6, p7], sep))
168                    .collect()
169            } else {
170                append(column1, DataType::Unknown);
171                append("table", DataType::Unknown);
172                append("column", DataType::Unknown);
173                append("type", DataType::Unknown);
174                append("nullable", DataType::Unknown);
175                append("index", DataType::Integer);
176                records
177                    .into_iter()
178                    .map(|(v1, _, v3, v4, p5)| (
179                        measure_text(v1),
180                        measure_text(v3),
181                        measure_text(v4),
182                        measure_type(p5),
183                        measure_null(p5),
184                        measure_index(p5, &metadata),
185                        group_index(p5),
186                    ))
187                    .map(|(p1, p3, p4, p5, p6, p7, sep)| (vec![p1, p3, p4, p5, p6, p7], sep))
188                    .collect()
189            }
190        } else {
191            append("table", DataType::Unknown);
192            append("column", DataType::Unknown);
193            append("type", DataType::Unknown);
194            append("nullable", DataType::Unknown);
195            append("index", DataType::Integer);
196            records
197                .into_iter()
198                .map(|(_, _, v3, v4, p5)| (
199                    measure_text(v3),
200                    measure_text(v4),
201                    measure_type(p5),
202                    measure_null(p5),
203                    measure_index(p5, &metadata),
204                    group_index(p5),
205                ))
206                .map(|(p3, p4, p5, p6, p7, sep)| (vec![p3, p4, p5, p6, p7], sep))
207                .collect()
208        };
209        let records = RefCell::new(records);
210        let source = RefCell::new(Source::None);
211        let widths = RefCell::new(None);
212        Self { columns, records, source, widths, auto: false }
213    }
214
215    pub fn get_columns(&self, painter: Painter) -> Vec<String> {
216        self.columns
217            .iter()
218            .map(|(x, _, _)| painter.wrap_style(x, Style::Column))
219            .collect()
220    }
221
222    pub fn has_records(&self) -> bool {
223        // Must be called after records have been retrieved.
224        !self.records.borrow().is_empty()
225    }
226
227    pub fn group_records(&mut self, group: usize) {
228        // Must be called after records have been retrieved.  Note, this
229        // relies on the sort algorithm being stable, i.e. not reordering
230        // equal elements.
231        self.records.borrow_mut().sort_by(|(l, _), (r, _)| Self::compare_records(l, r, group));
232    }
233
234    fn compare_records(left: &Record, right: &Record, group: usize) -> Ordering {
235        let left = left.get(group).map(|(x, _)| x.as_str()).unwrap_or_default();
236        let right = right.get(group).map(|(x, _)| x.as_str()).unwrap_or_default();
237        left.cmp(right)
238    }
239
240    pub fn get_records<F>(&self, limit: usize, mut function: F) -> MyResult<()> where
241        F: FnMut(Vec<Cow<str>>) -> MyResult<()>,
242    {
243        match self.source.replace(Source::None) {
244            Source::Cursor(cursor) => {
245                fetch_cursor(cursor, limit, function)
246            }
247            Source::File(reader) => {
248                fetch_file(reader, function)
249            }
250            Source::None => {
251                let records = self.records.borrow();
252                for (record, _) in records.iter() {
253                    let record = record.iter().map(|(x, _)| Cow::Borrowed(x.as_ref())).collect();
254                    function(record)?;
255                }
256                Ok(())
257            }
258        }
259    }
260
261    pub fn get_measured<F>(&self, limit: usize, mut function: F) -> MyResult<()> where
262        F: FnMut(Record, bool) -> MyResult<()>,
263    {
264        match self.source.replace(Source::None) {
265            Source::Cursor(cursor) => {
266                fetch_cursor(cursor, limit, |record| {
267                    let record = zip(record, &self.columns)
268                        .map(|(value, (_, _, metadata))| measure_value(value, metadata))
269                        .collect();
270                    function(record, false)?;
271                    Ok(())
272                })
273            }
274            Source::File(reader) => {
275                fetch_file(reader, |record| {
276                    let record = record
277                        .into_iter()
278                        .map(measure_text)
279                        .collect();
280                    function(record, false)?;
281                    Ok(())
282                })
283            }
284            Source::None => {
285                let mut records = self.records.borrow_mut();
286                let mut first = true;
287                for (record, sep) in records.drain(..) {
288                    function(record, sep && !first)?;
289                    first = false;
290                }
291                Ok(())
292            }
293        }
294    }
295
296    pub fn store_records(&mut self) -> MyResult<()> {
297        match self.source.replace(Source::None) {
298            Source::Cursor(cursor) => {
299                fetch_cursor(cursor, 4096, |record| {
300                    self.push_record(record);
301                    Ok(())
302                })
303            }
304            Source::File(reader) => {
305                fetch_file(reader, |record| {
306                    self.push_record(record);
307                    Ok(())
308                })
309            }
310            Source::None => {
311                Ok(())
312            }
313        }
314    }
315
316    pub fn push_record(&mut self, record: Vec<Cow<str>>) {
317        let record = zip(record, &self.columns)
318            .map(|(value, (_, _, metadata))| measure_value(value, metadata))
319            .collect::<Vec<_>>();
320        self.records.borrow_mut().push((record, false));
321    }
322
323    pub fn get_widths(&self) -> Vec<Format> {
324        self.widths.borrow_mut().get_or_insert_with(|| {
325            // If we're formatting data from a CSV file, we do not know
326            // in advance whether each column will contain text or numbers,
327            // so we create the column metadata with no format template.
328            // As we read the file, we parse each value via a regex, and
329            // override the template on the first text value.  Thereafter,
330            // each value in that column will be parsed as text.
331            if self.auto {
332                let records = self.records.borrow_mut().drain(..)
333                    .map(|(record, sep)| (self.do_reformat(record), sep))
334                    .collect();
335                self.records.replace(records);
336            }
337            // Get the maximum text or numeric width over every value in
338            // each column.  At this stage, each column should contain
339            // consistently formatted values.  The resulting widths will
340            // optionally be compared against the column header widths.
341            let mut widths = [Format::Text(0)].repeat(self.columns.len());
342            for (record, _) in self.records.borrow().iter() {
343                widths = zip(record, &widths)
344                    .map(|((_, format), width)| Format::max(format, width))
345                    .collect();
346            }
347            widths
348        }).clone()
349    }
350
351    fn do_reformat(&self, record: Record) -> Record {
352        // For each value in the record, test whether the value is numeric,
353        // and the template in the column metadata is text.  If this is
354        // true, force the value format to text.
355        zip(record, &self.columns)
356            .map(|((value, format), (_, _, metadata))| (value, metadata.do_reformat(format)))
357            .collect()
358    }
359
360    pub fn measure_table(&self) -> usize {
361        let widths = self.get_widths();
362        let total = zip(&self.columns, &widths)
363            .map(|((_, format, _), width)| Format::max(format, width))
364            .map(|format| format.total())
365            .sum::<usize>();
366        total + (widths.len() * 2)
367    }
368
369    pub fn adjust_header(&self, header: TableHeader, interact: bool) -> TableHeader {
370        if interact || self.columns.len() >= 2 { header } else { TableHeader::Simple }
371    }
372
373    pub fn into_table(self, header: TableHeader, group: Option<&str>) -> TableLayout<'a, R> {
374        TableLayout::from_dataset(self, header, group)
375    }
376
377    pub fn into_flatten(self, verbose: FlattenVerbose) -> FlattenLayout<'a, R> {
378        FlattenLayout::from_dataset(self, verbose)
379    }
380}
381
382fn fetch_columns<C: Cursor>(cursor: &mut C) -> MyResult<Vec<Column>> {
383    let size = cursor.num_result_cols()? as u16;
384    let mut columns = Vec::with_capacity(size as usize);
385    for index in 1..=size {
386        let name = cursor.col_name(index)?;
387        let dtype = cursor.col_data_type(index)?;
388        let null = cursor.col_nullability(index)?;
389        let (name, format) = measure_text(name);
390        let metadata = Metadata::new(dtype, null);
391        columns.push((name, format, metadata));
392    }
393    Ok(columns)
394}
395
396fn fetch_cursor<C, F>(
397    mut cursor: C,
398    limit: usize,
399    mut function: F,
400) -> MyResult<()> where
401    C: Cursor,
402    F: FnMut(Vec<Cow<str>>) -> MyResult<()>,
403{
404    let rowset = TextRowSet::for_cursor(5000, &mut cursor, Some(limit))?;
405    let mut cursor = cursor.bind_buffer(rowset)?;
406    while let Some(batch) = cursor.fetch()? {
407        let rows = batch.num_rows();
408        let cols = batch.num_cols();
409        for row in 0..rows {
410            let record = (0..cols)
411                .map(|col| fetch_value(batch, col, row))
412                .collect();
413            function(record)?;
414        }
415    }
416    Ok(())
417}
418
419fn fetch_value(
420    batch: &TextRowSet,
421    col: usize,
422    row: usize,
423) -> Cow<'_, str> {
424    let value = batch
425        .at_as_str(col, row)
426        .unwrap_or_default()
427        .unwrap_or_default();
428    Cow::Borrowed(value)
429}
430
431fn fetch_file<R, F>(
432    mut reader: Reader<R>,
433    mut function: F,
434) -> MyResult<()> where
435    R: Read,
436    F: FnMut(Vec<Cow<str>>) -> MyResult<()>,
437{
438    for record in reader.records() {
439        let record = record?;
440        let record = record.iter().map(Cow::from).collect();
441        function(record)?;
442    }
443    Ok(())
444}
445
446fn measure_text<V: Into<String>>(value: V) -> (String, Format) {
447    let value = value.into();
448    let length = value.chars().count();
449    let format = Format::Text(length);
450    (value, format)
451}
452
453fn measure_value<V: Into<String>>(value: V, metadata: &Metadata) -> (String, Format) {
454    let value = value.into();
455    let length = value.chars().count();
456    let format = metadata.measure_value(&value, length);
457    (value, format)
458}
459
460fn measure_type(payload: Option<&Payload>) -> (String, Format) {
461    let dtype = payload.map(|p| p.dtype.as_str()).unwrap_or_default();
462    measure_text(dtype)
463}
464
465fn measure_null(payload: Option<&Payload>) -> (String, Format) {
466    let null = payload.map(|p| p.null).unwrap_or_default();
467    measure_text(format_null(&null))
468}
469
470fn measure_index(payload: Option<&Payload>, metadata: &Metadata) -> (String, Format) {
471    let index = payload.map(|p| p.index).unwrap_or_default();
472    measure_value(index.to_string(), metadata)
473}
474
475fn group_index(payload: Option<&Payload>) -> bool {
476    let index = payload.map(|p| p.index).unwrap_or_default();
477    index == 1
478}
479
480#[cfg(test)]
481mod tests {
482    use crate::core::case::Payload;
483    use crate::db::dataset::{Dataset, Record};
484    use crate::{cow_str, str_vec};
485    use odbc_api::Nullability::{NoNulls, Nullable};
486    use pretty_assertions::assert_eq;
487    use std::borrow::Cow;
488    use std::fs::File;
489
490    #[test]
491    fn test_dataset_is_created_from_keywords() {
492        let dataset = Dataset::<File>::from_keywords("keyword", vec![
493            "ADD",
494            "ALTER",
495            "AND",
496        ]);
497        assert_eq!(get_columns(&dataset), str_vec![
498            "keyword",
499        ]);
500        assert_eq!(get_records(&dataset), vec![
501            str_vec!["ADD"],
502            str_vec!["ALTER"],
503            str_vec!["AND"],
504        ]);
505    }
506
507    #[test]
508    fn test_dataset_is_created_from_tables_with_database_and_schema() {
509        let dataset = Dataset::<File>::from_tables(Some("database"), Some("schema"), "table", vec![
510            ("Archive", "Main", "Article"),
511            ("Archive", "Main", "Journal"),
512            ("Business", "Admin", "Server"),
513            ("Business", "Main", "Account"),
514            ("Business", "Main", "Person"),
515        ]);
516        assert_eq!(get_columns(&dataset), str_vec![
517            "database",
518            "schema",
519            "table",
520        ]);
521        assert_eq!(get_records(&dataset), vec![
522            str_vec!["Archive", "Main", "Article"],
523            str_vec!["Archive", "Main", "Journal"],
524            str_vec!["Business", "Admin", "Server"],
525            str_vec!["Business", "Main", "Account"],
526            str_vec!["Business", "Main", "Person"],
527        ]);
528    }
529
530    #[test]
531    fn test_dataset_is_created_from_tables_with_database_no_schema() {
532        let dataset = Dataset::<File>::from_tables(Some("database"), None, "table", vec![
533            ("Archive", "", "Article"),
534            ("Archive", "", "Journal"),
535            ("Business", "", "Account"),
536            ("Business", "", "Person"),
537        ]);
538        assert_eq!(get_columns(&dataset), str_vec![
539            "database",
540            "table",
541        ]);
542        assert_eq!(get_records(&dataset), vec![
543            str_vec!["Archive", "Article"],
544            str_vec!["Archive", "Journal"],
545            str_vec!["Business", "Account"],
546            str_vec!["Business", "Person"],
547        ]);
548    }
549
550    #[test]
551    fn test_dataset_is_created_from_tables_no_database_or_schema() {
552        let dataset = Dataset::<File>::from_tables(None, None, "table", vec![
553            ("", "", "Account"),
554            ("", "", "Person"),
555        ]);
556        assert_eq!(get_columns(&dataset), str_vec![
557            "table",
558        ]);
559        assert_eq!(get_records(&dataset), vec![
560            str_vec!["Account"],
561            str_vec!["Person"],
562        ]);
563    }
564
565    #[test]
566    fn test_dataset_is_created_from_columns_with_database_and_schema() {
567        let dataset = Dataset::<File>::from_columns(Some("database"), Some("schema"), vec![
568            ("Archive", "Main", "Article", "Journal", Some(&Payload::new(cow_str!("INTEGER"), NoNulls, 1))),
569            ("Archive", "Main", "Article", "Date", Some(&Payload::new(cow_str!("DATE"), NoNulls, 2))),
570            ("Archive", "Main", "Article", "Author", Some(&Payload::new(cow_str!("VARCHAR(20)"), NoNulls, 3))),
571            ("Archive", "Main", "Article", "Text", Some(&Payload::new(cow_str!("VARCHAR(4000)"), NoNulls, 4))),
572            ("Archive", "Main", "Article", "Appendix", Some(&Payload::new(cow_str!("VARCHAR(4000)"), Nullable, 5))),
573            ("Archive", "Main", "Journal", "Country", Some(&Payload::new(cow_str!("INTEGER"), NoNulls, 1))),
574            ("Archive", "Main", "Journal", "Price", Some(&Payload::new(cow_str!("DECIMAL(10, 2)"), NoNulls, 2))),
575            ("Archive", "Main", "Journal", "Available", Some(&Payload::new(cow_str!("BIT"), NoNulls, 3))),
576            ("Business", "Admin", "Server", "Name", Some(&Payload::new(cow_str!("VARCHAR(20)"), NoNulls, 1))),
577            ("Business", "Admin", "Server", "Location", Some(&Payload::new(cow_str!("VARCHAR(20)"), NoNulls, 2))),
578            ("Business", "Admin", "Server", "Hardware", Some(&Payload::new(cow_str!("VARCHAR(20)"), NoNulls, 3))),
579            ("Business", "Main", "Account", "Company", Some(&Payload::new(cow_str!("VARCHAR(20)"), NoNulls, 1))),
580            ("Business", "Main", "Account", "Branch", Some(&Payload::new(cow_str!("VARCHAR(20)"), NoNulls, 2))),
581            ("Business", "Main", "Account", "Amount", Some(&Payload::new(cow_str!("DECIMAL(10, 2)"), NoNulls, 3))),
582            ("Business", "Main", "Account", "Active", Some(&Payload::new(cow_str!("BIT"), NoNulls, 4))),
583            ("Business", "Main", "Person", "Forename", Some(&Payload::new(cow_str!("VARCHAR(20)"), NoNulls, 1))),
584            ("Business", "Main", "Person", "Surname", Some(&Payload::new(cow_str!("VARCHAR(20)"), NoNulls, 2))),
585            ("Business", "Main", "Person", "Address", Some(&Payload::new(cow_str!("VARCHAR(20)"), Nullable, 3))),
586            ("Business", "Main", "Person", "Birthday", Some(&Payload::new(cow_str!("DATE"), Nullable, 4))),
587        ]);
588        assert_eq!(get_columns(&dataset), str_vec![
589            "database",
590            "schema",
591            "table",
592            "column",
593            "type",
594            "nullable",
595            "index",
596        ]);
597        assert_eq!(get_records(&dataset), vec![
598            str_vec!["Archive", "Main", "Article", "Journal", "INTEGER", "NOT NULL", "1"],
599            str_vec!["Archive", "Main", "Article", "Date", "DATE", "NOT NULL", "2"],
600            str_vec!["Archive", "Main", "Article", "Author", "VARCHAR(20)", "NOT NULL", "3"],
601            str_vec!["Archive", "Main", "Article", "Text", "VARCHAR(4000)", "NOT NULL", "4"],
602            str_vec!["Archive", "Main", "Article", "Appendix", "VARCHAR(4000)", "NULL", "5"],
603            str_vec!["Archive", "Main", "Journal", "Country", "INTEGER", "NOT NULL", "1"],
604            str_vec!["Archive", "Main", "Journal", "Price", "DECIMAL(10, 2)", "NOT NULL", "2"],
605            str_vec!["Archive", "Main", "Journal", "Available", "BIT", "NOT NULL", "3"],
606            str_vec!["Business", "Admin", "Server", "Name", "VARCHAR(20)", "NOT NULL", "1"],
607            str_vec!["Business", "Admin", "Server", "Location", "VARCHAR(20)", "NOT NULL", "2"],
608            str_vec!["Business", "Admin", "Server", "Hardware", "VARCHAR(20)", "NOT NULL", "3"],
609            str_vec!["Business", "Main", "Account", "Company", "VARCHAR(20)", "NOT NULL", "1"],
610            str_vec!["Business", "Main", "Account", "Branch", "VARCHAR(20)", "NOT NULL", "2"],
611            str_vec!["Business", "Main", "Account", "Amount", "DECIMAL(10, 2)", "NOT NULL", "3"],
612            str_vec!["Business", "Main", "Account", "Active", "BIT", "NOT NULL", "4"],
613            str_vec!["Business", "Main", "Person", "Forename", "VARCHAR(20)", "NOT NULL", "1"],
614            str_vec!["Business", "Main", "Person", "Surname", "VARCHAR(20)", "NOT NULL", "2"],
615            str_vec!["Business", "Main", "Person", "Address", "VARCHAR(20)", "NULL", "3"],
616            str_vec!["Business", "Main", "Person", "Birthday", "DATE", "NULL", "4"],
617        ]);
618    }
619
620    #[test]
621    fn test_dataset_is_created_from_columns_with_database_no_schema() {
622        let dataset = Dataset::<File>::from_columns(Some("database"), None, vec![
623            ("Archive", "", "Article", "Journal", Some(&Payload::new(cow_str!("INTEGER"), NoNulls, 1))),
624            ("Archive", "", "Article", "Date", Some(&Payload::new(cow_str!("DATE"), NoNulls, 2))),
625            ("Archive", "", "Article", "Author", Some(&Payload::new(cow_str!("VARCHAR(20)"), NoNulls, 3))),
626            ("Archive", "", "Article", "Text", Some(&Payload::new(cow_str!("VARCHAR(4000)"), NoNulls, 4))),
627            ("Archive", "", "Article", "Appendix", Some(&Payload::new(cow_str!("VARCHAR(4000)"), Nullable, 5))),
628            ("Archive", "", "Journal", "Country", Some(&Payload::new(cow_str!("INTEGER"), NoNulls, 1))),
629            ("Archive", "", "Journal", "Price", Some(&Payload::new(cow_str!("DECIMAL(10, 2)"), NoNulls, 2))),
630            ("Archive", "", "Journal", "Available", Some(&Payload::new(cow_str!("BIT"), NoNulls, 3))),
631            ("Business", "", "Account", "Company", Some(&Payload::new(cow_str!("VARCHAR(20)"), NoNulls, 1))),
632            ("Business", "", "Account", "Branch", Some(&Payload::new(cow_str!("VARCHAR(20)"), NoNulls, 2))),
633            ("Business", "", "Account", "Amount", Some(&Payload::new(cow_str!("DECIMAL(10, 2)"), NoNulls, 3))),
634            ("Business", "", "Account", "Active", Some(&Payload::new(cow_str!("BIT"), NoNulls, 4))),
635            ("Business", "", "Person", "Forename", Some(&Payload::new(cow_str!("VARCHAR(20)"), NoNulls, 1))),
636            ("Business", "", "Person", "Surname", Some(&Payload::new(cow_str!("VARCHAR(20)"), NoNulls, 2))),
637            ("Business", "", "Person", "Address", Some(&Payload::new(cow_str!("VARCHAR(20)"), Nullable, 3))),
638            ("Business", "", "Person", "Birthday", Some(&Payload::new(cow_str!("DATE"), Nullable, 4))),
639        ]);
640        assert_eq!(get_columns(&dataset), str_vec![
641            "database",
642            "table",
643            "column",
644            "type",
645            "nullable",
646            "index",
647        ]);
648        assert_eq!(get_records(&dataset), vec![
649            str_vec!["Archive", "Article", "Journal", "INTEGER", "NOT NULL", "1"],
650            str_vec!["Archive", "Article", "Date", "DATE", "NOT NULL", "2"],
651            str_vec!["Archive", "Article", "Author", "VARCHAR(20)", "NOT NULL", "3"],
652            str_vec!["Archive", "Article", "Text", "VARCHAR(4000)", "NOT NULL", "4"],
653            str_vec!["Archive", "Article", "Appendix", "VARCHAR(4000)", "NULL", "5"],
654            str_vec!["Archive", "Journal", "Country", "INTEGER", "NOT NULL", "1"],
655            str_vec!["Archive", "Journal", "Price", "DECIMAL(10, 2)", "NOT NULL", "2"],
656            str_vec!["Archive", "Journal", "Available", "BIT", "NOT NULL", "3"],
657            str_vec!["Business", "Account", "Company", "VARCHAR(20)", "NOT NULL", "1"],
658            str_vec!["Business", "Account", "Branch", "VARCHAR(20)", "NOT NULL", "2"],
659            str_vec!["Business", "Account", "Amount", "DECIMAL(10, 2)", "NOT NULL", "3"],
660            str_vec!["Business", "Account", "Active", "BIT", "NOT NULL", "4"],
661            str_vec!["Business", "Person", "Forename", "VARCHAR(20)", "NOT NULL", "1"],
662            str_vec!["Business", "Person", "Surname", "VARCHAR(20)", "NOT NULL", "2"],
663            str_vec!["Business", "Person", "Address", "VARCHAR(20)", "NULL", "3"],
664            str_vec!["Business", "Person", "Birthday", "DATE", "NULL", "4"],
665        ]);
666    }
667
668    #[test]
669    fn test_dataset_is_created_from_columns_no_database_or_schema() {
670        let dataset = Dataset::<File>::from_columns(None, None, vec![
671            ("", "", "Account", "Company", Some(&Payload::new(cow_str!("VARCHAR(20)"), NoNulls, 1))),
672            ("", "", "Account", "Branch", Some(&Payload::new(cow_str!("VARCHAR(20)"), NoNulls, 2))),
673            ("", "", "Account", "Amount", Some(&Payload::new(cow_str!("DECIMAL(10, 2)"), NoNulls, 3))),
674            ("", "", "Account", "Active", Some(&Payload::new(cow_str!("BIT"), NoNulls, 4))),
675            ("", "", "Person", "Forename", Some(&Payload::new(cow_str!("VARCHAR(20)"), NoNulls, 1))),
676            ("", "", "Person", "Surname", Some(&Payload::new(cow_str!("VARCHAR(20)"), NoNulls, 2))),
677            ("", "", "Person", "Address", Some(&Payload::new(cow_str!("VARCHAR(20)"), Nullable, 3))),
678            ("", "", "Person", "Birthday", Some(&Payload::new(cow_str!("DATE"), Nullable, 4))),
679        ]);
680        assert_eq!(get_columns(&dataset), str_vec![
681            "table",
682            "column",
683            "type",
684            "nullable",
685            "index",
686        ]);
687        assert_eq!(get_records(&dataset), vec![
688            str_vec!["Account", "Company", "VARCHAR(20)", "NOT NULL", "1"],
689            str_vec!["Account", "Branch", "VARCHAR(20)", "NOT NULL", "2"],
690            str_vec!["Account", "Amount", "DECIMAL(10, 2)", "NOT NULL", "3"],
691            str_vec!["Account", "Active", "BIT", "NOT NULL", "4"],
692            str_vec!["Person", "Forename", "VARCHAR(20)", "NOT NULL", "1"],
693            str_vec!["Person", "Surname", "VARCHAR(20)", "NOT NULL", "2"],
694            str_vec!["Person", "Address", "VARCHAR(20)", "NULL", "3"],
695            str_vec!["Person", "Birthday", "DATE", "NULL", "4"],
696        ]);
697    }
698
699    fn get_columns(dataset: &Dataset<File>) -> Vec<String> {
700        dataset.columns.iter().map(|(x, _, _)| x.clone()).collect()
701    }
702
703    fn get_records(dataset: &Dataset<File>) -> Vec<Vec<String>> {
704        dataset.records.borrow().iter().map(|(x, _)| get_values(x)).collect()
705    }
706
707    fn get_values(record: &Record) -> Vec<String> {
708        record.iter().map(|(x, _)| x.clone()).collect()
709    }
710}