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