Skip to main content

zql_cli/
engine.rs

1use crate::core::catalog::Catalog;
2use crate::core::editor::{create_editor, CommandEditor};
3use crate::core::lexer::Lexer;
4use crate::core::pager::Pager;
5use crate::core::settings::{Layout, Settings};
6use crate::core::system::{FileSystem, System};
7use crate::db::database::Database;
8use crate::db::dataset::Dataset;
9use crate::db::driver::Driver;
10use crate::db::layout::flatten::FlattenVerbose;
11use crate::db::layout::table::TableHeader;
12use crate::error::{EngineError, MyError, MyResult};
13use crate::regex;
14use crate::util::painter::{Painter, Style};
15use crate::util::regex::get_match;
16use crate::util::text::join_strings;
17use csv::Writer;
18use odbc_api::Environment;
19use std::borrow::Cow;
20use std::collections::HashMap;
21use std::fs::File;
22use std::io;
23use std::io::{Read, Write};
24use std::rc::Rc;
25
26pub struct Engine<'a> {
27    driver: Driver,
28    database: Rc<Database<'a>>,
29    actions: HashMap<(String, String), fn(&mut Engine<'a>, &Catalog, &str) -> MyResult<()>>,
30    pager: Option<Pager>,
31    painter: Painter,
32    settings: Settings,
33}
34
35impl<'a> Engine<'a> {
36    pub fn new(
37        env: &'a Environment,
38        driver: Driver,
39        pager: Option<String>,
40        painter: Painter,
41        settings: Settings,
42    ) -> MyResult<Self> {
43        let driver = driver.with_password();
44        let database = Rc::new(Self::create_database(env, &driver)?);
45        let actions = HashMap::new();
46        let pager = pager.map(Pager::new);
47        let engine = Self { driver, database, actions, pager, painter, settings };
48        Ok(engine)
49    }
50
51    #[cfg(debug_assertions)]
52    fn create_database<'b>(env: &'b Environment, driver: &Driver) -> MyResult<Database<'b>> {
53        use crate::db::database::Sample;
54        if driver.is_memory() {
55            Database::new(env, driver)?.with_records(Sample::Hugos)
56        } else {
57            Database::new(env, driver)
58        }
59    }
60
61    #[cfg(not(debug_assertions))]
62    fn create_database<'b>(env: &'b Environment, driver: &Driver) -> MyResult<Database<'b>> {
63        Database::new(env, driver)
64    }
65
66    pub fn exec_sql(&mut self, query: &str) -> MyResult<()> {
67        let mut stdout = io::stdout();
68        let dataset = self.database.create_dataset::<File>(query)?;
69        if let Some(dataset) = dataset {
70            Self::print_dataset(&mut stdout, self.painter, dataset, &self.settings, false)?;
71        }
72        Ok(())
73    }
74
75    pub fn run_file<R: Read>(&mut self, reader: R) -> MyResult<()> {
76        let mut stdout = io::stdout();
77        self.iterate_queries(&mut stdout, reader, false)?;
78        Ok(())
79    }
80
81    pub fn exec_loop(&mut self, alias: String) -> MyResult<()> {
82        let system = FileSystem::new();
83        let catalog = self.create_catalog()?;
84        let mut editor = create_editor(self.database.clone(), catalog.clone())?;
85        system.load_history(&mut editor, &alias)?;
86        let result = self.interact_loop(&mut editor, &catalog);
87        system.save_history(&mut editor, &alias)?;
88        result
89    }
90
91    pub fn create_catalog(&mut self) -> MyResult<Rc<Catalog>> {
92        let mut catalog = self.database.create_catalog()?;
93        self.create_actions(&mut catalog);
94        Ok(Rc::new(catalog))
95    }
96
97    fn create_actions(&mut self, catalog: &mut Catalog) {
98        let mut insert = |verb: &str, noun: &str, action| {
99            let verb = Cow::Borrowed(verb);
100            let noun = Cow::Borrowed(noun);
101            catalog.insert_directive(verb.clone(), noun.clone());
102            self.actions.insert((verb.to_string(), noun.to_string()), action);
103        };
104        insert(":format", "table", Self::format_table);
105        insert(":format", "flatten", Self::format_flatten);
106        insert(":format", "verbose", Self::format_verbose);
107        insert(":show", "databases", Self::show_databases);
108        insert(":show", "tables", Self::show_tables);
109        insert(":show", "columns", Self::show_columns);
110        insert(":show", "procedures", Self::show_procedures);
111        insert(":show", "keywords", Self::show_keywords);
112        insert(":show", "functions", Self::show_functions);
113    }
114
115    fn interact_loop(
116        &mut self,
117        editor: &mut CommandEditor,
118        catalog: &Catalog,
119    ) -> MyResult<()> {
120        loop {
121            let context = self.database.query_context();
122            let prompt = self.driver.create_prompt(context);
123            let line = editor.readline(&prompt)?;
124            let line = line.trim();
125            if !line.is_empty() {
126                editor.add_history_entry(line)?;
127                self.interact_line(catalog, line);
128            }
129        }
130    }
131
132    fn interact_line(
133        &mut self,
134        catalog: &Catalog,
135        line: &str,
136    ) {
137        if let Err(error) = self.apply_action(catalog, line) {
138            let mut stdout = io::stdout();
139            let error = error.to_string();
140            for line in error.lines() {
141                let _ = self.painter.print_line(&mut stdout, Style::Type, line);
142            }
143        }
144    }
145
146    fn apply_action(
147        &mut self,
148        catalog: &Catalog,
149        line: &str,
150    ) -> MyResult<()> {
151        let action_regex = regex!(r"^(:\w+)(?:\s+(\w+))?(?:\s+(.+))?$");
152        if let Some(captures) = action_regex.captures(line) {
153            let verb = get_match(&captures, 1).to_string();
154            let noun = get_match(&captures, 2).to_string();
155            if let Some(action) = self.actions.get(&(verb, noun)) {
156                let line = get_match(&captures, 3);
157                action(self, catalog, line)?;
158                Ok(())
159            } else {
160                let verb = get_match(&captures, 1);
161                let noun = get_match(&captures, 2);
162                let directive = join_strings(verb, noun);
163                Err(MyError::Engine(EngineError::UnknownDirective(directive)))
164            }
165        } else {
166            self.page_queries(line)?;
167            Ok(())
168        }
169    }
170
171    fn format_table(&mut self, _catalog: &Catalog, _line: &str) -> MyResult<()> {
172        self.settings.layout = Layout::Table;
173        Ok(())
174    }
175
176    fn format_flatten(&mut self, _catalog: &Catalog, _line: &str) -> MyResult<()> {
177        self.settings.layout = Layout::Flatten;
178        Ok(())
179    }
180
181    fn format_verbose(&mut self, _catalog: &Catalog, _line: &str) -> MyResult<()> {
182        self.settings.verbose = FlattenVerbose::Verbose;
183        Ok(())
184    }
185
186    pub fn show_databases(&mut self, catalog: &Catalog, _line: &str) -> MyResult<()> {
187        let context = self.database.query_context();
188        let (database, schema) = context.and_then("database", "schema");
189        if database.is_some() || schema.is_some() {
190            let values = catalog.get_databases(context);
191            let dataset = Dataset::<File>::from_tables(database, schema, "active", values);
192            self.page_action(dataset)?;
193        }
194        Ok(())
195    }
196
197    pub fn show_tables(&mut self, catalog: &Catalog, line: &str) -> MyResult<()> {
198        let context = self.database.query_context();
199        let (database, schema) = context.and_then("database", "schema");
200        let values = catalog.get_tables(context, line);
201        let dataset = Dataset::<File>::from_tables(database, schema, "table", values);
202        self.page_action(dataset)?;
203        Ok(())
204    }
205
206    pub fn show_columns(&mut self, catalog: &Catalog, line: &str) -> MyResult<()> {
207        let context = self.database.query_context();
208        let (database, schema) = context.and_then("database", "schema");
209        let values = catalog.get_columns(context, line);
210        let dataset = Dataset::<File>::from_columns(database, schema, values);
211        self.page_action(dataset)?;
212        Ok(())
213    }
214
215    pub fn show_procedures(&mut self, catalog: &Catalog, line: &str) -> MyResult<()> {
216        let context = self.database.query_context();
217        let (database, schema) = context.and_then("database", "schema");
218        let values = catalog.get_procedures(context, line);
219        let dataset = Dataset::<File>::from_tables(database, schema, "procedure", values);
220        self.page_action(dataset)?;
221        Ok(())
222    }
223
224    pub fn show_keywords(&mut self, catalog: &Catalog, _line: &str) -> MyResult<()> {
225        let values = catalog.get_keywords();
226        let dataset = Dataset::<File>::from_keywords("keyword", values);
227        self.page_action(dataset)?;
228        Ok(())
229    }
230
231    pub fn show_functions(&mut self, catalog: &Catalog, _line: &str) -> MyResult<()> {
232        let values = catalog.get_functions();
233        let dataset = Dataset::<File>::from_keywords("function", values);
234        self.page_action(dataset)?;
235        Ok(())
236    }
237
238    fn page_action<R: Read>(&mut self, dataset: Dataset<R>) -> MyResult<()> {
239        let painter = self.painter;
240        let header = TableHeader::Include;
241        if let Some(pager) = self.pager.as_mut() {
242            if let Some(mut child) = pager.spawn_child() {
243                if let Some(stdin) = child.stdin.as_mut() {
244                    let result = Self::print_table(stdin, painter, dataset, header, None);
245                    let _ = child.wait_with_output();
246                    return result;
247                }
248            }
249        }
250        let mut stdout = io::stdout();
251        Self::print_table(&mut stdout, painter, dataset, header, None)?;
252        Ok(())
253    }
254
255    fn page_queries(&mut self, line: &str) -> MyResult<()> {
256        let reader = line.as_bytes();
257        if let Some(pager) = self.pager.as_mut() {
258            if let Some(mut child) = pager.spawn_child() {
259                if let Some(stdin) = child.stdin.as_mut() {
260                    let result = self.iterate_queries(stdin, reader, true);
261                    let _ = child.wait_with_output();
262                    return result;
263                }
264            }
265        }
266        let mut stdout = io::stdout();
267        self.iterate_queries(&mut stdout, reader, true)?;
268        Ok(())
269    }
270
271    fn iterate_queries<R: Read, W: Write>(
272        &mut self,
273        writer: &mut W,
274        reader: R,
275        interact: bool,
276    ) -> MyResult<()> {
277        let batch = self.database.get_batch();
278        let lexer = Lexer::new(reader, batch, interact)?;
279        for query in lexer {
280            let query = query?;
281            let dataset = self.database.create_dataset::<File>(&query)?;
282            if let Some(dataset) = dataset {
283                Self::print_dataset(writer, self.painter, dataset, &self.settings, interact)?;
284            }
285        }
286        Ok(())
287    }
288
289    pub fn print_dataset<R: Read, W: Write>(
290        writer: &mut W,
291        painter: Painter,
292        mut dataset: Dataset<R>,
293        settings: &Settings,
294        interact: bool,
295    ) -> MyResult<()> {
296        match settings.layout {
297            Layout::CSV => {
298                let header = dataset.adjust_header(settings.header, interact);
299                Self::print_csv(writer, painter, dataset, header)
300            }
301            Layout::Table => {
302                let header = dataset.adjust_header(settings.header, interact);
303                let group = settings.group.as_deref();
304                Self::print_table(writer, painter, dataset, header, group)
305            }
306            Layout::Flatten => {
307                Self::print_flatten(writer, painter, dataset, settings.verbose)
308            }
309            Layout::Choose => {
310                dataset.store_records()?;
311                if dataset.measure_table() < Self::measure_screen() {
312                    let header = dataset.adjust_header(TableHeader::Include, interact);
313                    let group = settings.group.as_deref();
314                    Self::print_table(writer, painter, dataset, header, group)
315                } else {
316                    Self::print_flatten(writer, painter, dataset, FlattenVerbose::Verbose)
317                }
318            }
319        }
320    }
321
322    fn measure_screen() -> usize {
323        termsize::get().map(|size| size.cols).unwrap_or_default() as usize
324    }
325
326    fn print_csv<R: Read, W: Write>(
327        writer: &mut W,
328        painter: Painter,
329        dataset: Dataset<R>,
330        header: TableHeader,
331    ) -> MyResult<()> {
332        let mut writer = Writer::from_writer(writer);
333        if let TableHeader::Include = header {
334            let columns = dataset.get_columns(painter);
335            writer.write_record(columns)?;
336        }
337        dataset.get_measured(4096, |record, _| {
338            let record = record
339                .iter()
340                .map(|(x, f)| painter.wrap_style(x, f.style()))
341                .collect::<Vec<_>>();
342            writer.write_record(record)?;
343            Ok(())
344        })
345    }
346
347    fn print_table<R: Read, W: Write>(
348        writer: &mut W,
349        painter: Painter,
350        mut dataset: Dataset<R>,
351        header: TableHeader,
352        group: Option<&str>,
353    ) -> MyResult<()> {
354        dataset.store_records()?;
355        dataset.into_table(header, group).print_dataset(writer, painter)?;
356        Ok(())
357    }
358
359    fn print_flatten<R: Read, W: Write>(
360        writer: &mut W,
361        painter: Painter,
362        mut dataset: Dataset<R>,
363        verbose: FlattenVerbose,
364    ) -> MyResult<()> {
365        dataset.store_records()?;
366        dataset.into_flatten(verbose).print_dataset(writer, painter)?;
367        Ok(())
368    }
369}
370
371#[cfg(test)]
372mod tests {
373    use crate::db::database::{Database, Sample};
374    use crate::db::dataset::Dataset;
375    use crate::db::driver::Driver;
376    use crate::db::layout::flatten::FlattenVerbose;
377    use crate::db::layout::table::TableHeader;
378    use crate::engine::Engine;
379    use crate::error::MyResult;
380    use crate::util::painter::Painter;
381    use odbc_api::Environment;
382    use pretty_assertions::assert_eq;
383    use std::fs::File;
384    use std::io::BufReader;
385
386    #[test]
387    fn test_text_records_are_printed_as_csv_with_header() -> MyResult<()> {
388        let expected = "\
389[COL]Year[RES],[COL]AuthorId[RES],[COL]Article[RES],[COL]Title[RES],[COL]Genre[RES]
390[NUM]1956[RES],[NUM]2[RES],,[VAL]Double Star[RES],[VAL]SF[RES]
391[NUM]1960[RES],[NUM]2[RES],,[VAL]Starship Troopers[RES],[VAL]SF[RES]
392[NUM]1962[RES],[NUM]2[RES],,[VAL]Stranger in a Strange Land[RES],[VAL]SF[RES]
393[NUM]1967[RES],[NUM]2[RES],[VAL]The[RES],[VAL]Moon Is a Harsh Mistress[RES],[VAL]SF[RES]
394";
395        let query = "SELECT * FROM Book WHERE AuthorId = 2";
396        let buffer = print_sql(Sample::Hugos, query, |writer, dataset| {
397            Engine::print_csv(writer, Painter::Test, dataset, TableHeader::Include)
398        })?;
399        assert_eq!(buffer, expected);
400        Ok(())
401    }
402
403    #[test]
404    fn test_text_records_are_printed_as_csv_no_header() -> MyResult<()> {
405        let expected = "\
406[NUM]1956[RES],[NUM]2[RES],,[VAL]Double Star[RES],[VAL]SF[RES]
407[NUM]1960[RES],[NUM]2[RES],,[VAL]Starship Troopers[RES],[VAL]SF[RES]
408[NUM]1962[RES],[NUM]2[RES],,[VAL]Stranger in a Strange Land[RES],[VAL]SF[RES]
409[NUM]1967[RES],[NUM]2[RES],[VAL]The[RES],[VAL]Moon Is a Harsh Mistress[RES],[VAL]SF[RES]
410";
411        let query = "SELECT * FROM Book WHERE AuthorId = 2";
412        let buffer = print_sql(Sample::Hugos, query, |writer, dataset| {
413            Engine::print_csv(writer, Painter::Test, dataset, TableHeader::Exclude)
414        })?;
415        assert_eq!(buffer, expected);
416        Ok(())
417    }
418
419    #[test]
420    fn test_text_column_is_printed_as_csv_no_header() -> MyResult<()> {
421        let expected = "\
422[VAL]Double Star[RES]
423[VAL]Starship Troopers[RES]
424[VAL]Stranger in a Strange Land[RES]
425[VAL]Moon Is a Harsh Mistress[RES]
426";
427        let query = "SELECT Title FROM Book WHERE AuthorId = 2";
428        let buffer = print_sql(Sample::Hugos, query, |writer, dataset| {
429            Engine::print_csv(writer, Painter::Test, dataset, TableHeader::Simple)
430        })?;
431        assert_eq!(buffer, expected);
432        Ok(())
433    }
434
435    #[test]
436    fn test_text_records_are_printed_as_table_short_columns() -> MyResult<()> {
437        let expected = "\
438[COL]Year[RES]  [COL]Title[RES]
439[COL]-----[RES] [COL]------[RES]
440[NUM]1956[RES]  [VAL]Double Star[RES]
441[NUM]1960[RES]  [VAL]Starship Troopers[RES]
442[NUM]1962[RES]  [VAL]Stranger in a Strange Land[RES]
443[NUM]1967[RES]  [VAL]Moon Is a Harsh Mistress[RES]
444";
445        let query = "SELECT Year, Title FROM Book WHERE AuthorId = 2";
446        let buffer = print_sql(Sample::Hugos, query, |writer, dataset| {
447            Engine::print_table(writer, Painter::Test, dataset, TableHeader::Include, None)
448        })?;
449        assert_eq!(buffer, expected);
450        Ok(())
451    }
452
453    #[test]
454    fn test_text_records_are_printed_as_table_long_columns() -> MyResult<()> {
455        let expected = "\
456[COL]Title[RES]                       [COL]Year[RES]
457[COL]---------------------------[RES] [COL]-----[RES]
458[VAL]Double Star[RES]                 [NUM]1956[RES]
459[VAL]Starship Troopers[RES]           [NUM]1960[RES]
460[VAL]Stranger in a Strange Land[RES]  [NUM]1962[RES]
461[VAL]Moon Is a Harsh Mistress[RES]    [NUM]1967[RES]
462";
463        let query = "SELECT Title, Year FROM Book WHERE AuthorId = 2";
464        let buffer = print_sql(Sample::Hugos, query, |writer, dataset| {
465            Engine::print_table(writer, Painter::Test, dataset, TableHeader::Include, None)
466        })?;
467        assert_eq!(buffer, expected);
468        Ok(())
469    }
470
471    #[test]
472    fn test_text_records_are_printed_as_table_no_records() -> MyResult<()> {
473        let expected = "\
474[COL]Year[RES]  [COL]AuthorId[RES]  [COL]Article[RES]  [COL]Title[RES]  [COL]Genre[RES]
475[COL]-----[RES] [COL]---------[RES] [COL]--------[RES] [COL]------[RES] [COL]------[RES]
476";
477        let query = "SELECT * FROM Book WHERE AuthorId = 999";
478        let buffer = print_sql(Sample::Hugos, query, |writer, dataset| {
479            Engine::print_table(writer, Painter::Test, dataset, TableHeader::Include, None)
480        })?;
481        assert_eq!(buffer, expected);
482        Ok(())
483    }
484
485    #[test]
486    fn test_text_records_are_printed_as_table_no_header() -> MyResult<()> {
487        let expected = "\
488[NUM]1956[RES]  [NUM]2[RES]  [COL]-[RES]    [VAL]Double Star[RES]                 [VAL]SF[RES]
489[NUM]1960[RES]  [NUM]2[RES]  [COL]-[RES]    [VAL]Starship Troopers[RES]           [VAL]SF[RES]
490[NUM]1962[RES]  [NUM]2[RES]  [COL]-[RES]    [VAL]Stranger in a Strange Land[RES]  [VAL]SF[RES]
491[NUM]1967[RES]  [NUM]2[RES]  [VAL]The[RES]  [VAL]Moon Is a Harsh Mistress[RES]    [VAL]SF[RES]
492";
493        let query = "SELECT * FROM Book WHERE AuthorId = 2";
494        let buffer = print_sql(Sample::Hugos, query, |writer, dataset| {
495            Engine::print_table(writer, Painter::Test, dataset, TableHeader::Exclude, None)
496        })?;
497        assert_eq!(buffer, expected);
498        Ok(())
499    }
500
501    #[test]
502    fn test_text_column_is_printed_as_table_no_header() -> MyResult<()> {
503        let expected = "\
504[VAL]Double Star[RES]
505[VAL]Starship Troopers[RES]
506[VAL]Stranger in a Strange Land[RES]
507[VAL]Moon Is a Harsh Mistress[RES]
508";
509        let query = "SELECT Title FROM Book WHERE AuthorId = 2";
510        let buffer = print_sql(Sample::Hugos, query, |writer, dataset| {
511            Engine::print_table(writer, Painter::Test, dataset, TableHeader::Simple, None)
512        })?;
513        assert_eq!(buffer, expected);
514        Ok(())
515    }
516
517    #[test]
518    fn test_text_records_are_printed_as_table_unicode_chars() -> MyResult<()> {
519        let expected = "\
520[COL]Forename[RES]     [COL]Surname[RES]
521[COL]------------[RES] [COL]--------[RES]
522[VAL]Alfred[RES]       [VAL]Bester[RES]
523[VAL]John[RES]         [VAL]Brunner[RES]
524[VAL]Philip José[RES]  [VAL]Farmer[RES]
525[VAL]Fritz[RES]        [VAL]Leiber[RES]
526[VAL]Walter M[RES]     [VAL]Miller[RES]
527";
528        let query = "SELECT Forename, Surname FROM Author WHERE Surname LIKE '%er' ORDER BY Surname";
529        let buffer = print_sql(Sample::Hugos, query, |writer, dataset| {
530            Engine::print_table(writer, Painter::Test, dataset, TableHeader::Include, None)
531        })?;
532        assert_eq!(buffer, expected);
533        Ok(())
534    }
535
536    #[test]
537    fn test_text_records_are_printed_as_table_with_group() -> MyResult<()> {
538        let expected = "\
539[COL]Year[RES]  [COL]Title[RES]                       [COL]Genre[RES]
540[COL]-----[RES] [COL]---------------------------[RES] [COL]------[RES]
541[NUM]1963[RES]  [VAL]Man in the High Castle[RES]      [VAL]F[RES]
542[COL]-----[RES] [COL]---------------------------[RES] [COL]------[RES]
543[NUM]1960[RES]  [VAL]Starship Troopers[RES]           [VAL]SF[RES]
544[NUM]1961[RES]  [VAL]Canticle for Leibowitz[RES]      [VAL]SF[RES]
545[NUM]1962[RES]  [VAL]Stranger in a Strange Land[RES]  [VAL]SF[RES]
546[NUM]1964[RES]  [VAL]Way Station[RES]                 [VAL]SF[RES]
547[NUM]1965[RES]  [VAL]Wanderer[RES]                    [VAL]SF[RES]
548[NUM]1967[RES]  [VAL]Moon Is a Harsh Mistress[RES]    [VAL]SF[RES]
549[NUM]1969[RES]  [VAL]Stand on Zanzibar[RES]           [VAL]SF[RES]
550[COL]-----[RES] [COL]---------------------------[RES] [COL]------[RES]
551[NUM]1966[RES]  [VAL]Dune[RES]                        [VAL]SF/F[RES]
552[NUM]1966[RES]  [VAL]And Call Me Conrad[RES]          [VAL]SF/F[RES]
553[NUM]1968[RES]  [VAL]Lord of Light[RES]               [VAL]SF/F[RES]
554";
555        let query = "SELECT Year, Title, Genre FROM Book WHERE Year BETWEEN 1960 AND 1969 ORDER BY Genre, Year";
556        let buffer = print_sql(Sample::Hugos, query, |writer, dataset| {
557            Engine::print_table(writer, Painter::Test, dataset, TableHeader::Include, Some("genre"))
558        })?;
559        assert_eq!(buffer, expected);
560        Ok(())
561    }
562
563    #[test]
564    fn test_text_records_are_printed_as_flatten_no_types() -> MyResult<()> {
565        let expected = "\
566[COL]column[RES]    [COL]value[RES]
567[COL]---------[RES] [COL]------[RES]
568[COL]Year[RES]      [NUM]1956[RES]
569[COL]AuthorId[RES]  [NUM]2[RES]
570[COL]Article[RES]   [COL]-[RES]
571[COL]Title[RES]     [VAL]Double Star[RES]
572[COL]Genre[RES]     [VAL]SF[RES]
573[COL]---------[RES] [COL]------[RES]
574[COL]Year[RES]      [NUM]1960[RES]
575[COL]AuthorId[RES]  [NUM]2[RES]
576[COL]Article[RES]   [COL]-[RES]
577[COL]Title[RES]     [VAL]Starship Troopers[RES]
578[COL]Genre[RES]     [VAL]SF[RES]
579[COL]---------[RES] [COL]------[RES]
580[COL]Year[RES]      [NUM]1962[RES]
581[COL]AuthorId[RES]  [NUM]2[RES]
582[COL]Article[RES]   [COL]-[RES]
583[COL]Title[RES]     [VAL]Stranger in a Strange Land[RES]
584[COL]Genre[RES]     [VAL]SF[RES]
585[COL]---------[RES] [COL]------[RES]
586[COL]Year[RES]      [NUM]1967[RES]
587[COL]AuthorId[RES]  [NUM]2[RES]
588[COL]Article[RES]   [VAL]The[RES]
589[COL]Title[RES]     [VAL]Moon Is a Harsh Mistress[RES]
590[COL]Genre[RES]     [VAL]SF[RES]
591";
592        let query = "SELECT * FROM Book WHERE AuthorId = 2";
593        let buffer = print_sql(Sample::Hugos, query, |writer, dataset| {
594            Engine::print_flatten(writer, Painter::Test, dataset, FlattenVerbose::Quiet)
595        })?;
596        assert_eq!(buffer, expected);
597        Ok(())
598    }
599
600    #[test]
601    fn test_text_records_are_printed_as_flatten_with_types() -> MyResult<()> {
602        let expected = "\
603[COL]column[RES]    [COL]type[RES]      [COL]nullable[RES]  [COL]value[RES]
604[COL]---------[RES] [COL]---------[RES] [COL]---------[RES] [COL]------[RES]
605[COL]Year[RES]      [TYP]INTEGER[RES]   [TYP]NOT NULL[RES]  [NUM]1956[RES]
606[COL]AuthorId[RES]  [TYP]INTEGER[RES]   [TYP]NOT NULL[RES]  [NUM]2[RES]
607[COL]Article[RES]   [TYP]CHAR(5)[RES]   [TYP]NULL[RES]      [COL]-[RES]
608[COL]Title[RES]     [TYP]CHAR(50)[RES]  [TYP]NOT NULL[RES]  [VAL]Double Star[RES]
609[COL]Genre[RES]     [TYP]CHAR(5)[RES]   [TYP]NOT NULL[RES]  [VAL]SF[RES]
610[COL]---------[RES] [COL]---------[RES] [COL]---------[RES] [COL]------[RES]
611[COL]Year[RES]      [TYP]INTEGER[RES]   [TYP]NOT NULL[RES]  [NUM]1960[RES]
612[COL]AuthorId[RES]  [TYP]INTEGER[RES]   [TYP]NOT NULL[RES]  [NUM]2[RES]
613[COL]Article[RES]   [TYP]CHAR(5)[RES]   [TYP]NULL[RES]      [COL]-[RES]
614[COL]Title[RES]     [TYP]CHAR(50)[RES]  [TYP]NOT NULL[RES]  [VAL]Starship Troopers[RES]
615[COL]Genre[RES]     [TYP]CHAR(5)[RES]   [TYP]NOT NULL[RES]  [VAL]SF[RES]
616[COL]---------[RES] [COL]---------[RES] [COL]---------[RES] [COL]------[RES]
617[COL]Year[RES]      [TYP]INTEGER[RES]   [TYP]NOT NULL[RES]  [NUM]1962[RES]
618[COL]AuthorId[RES]  [TYP]INTEGER[RES]   [TYP]NOT NULL[RES]  [NUM]2[RES]
619[COL]Article[RES]   [TYP]CHAR(5)[RES]   [TYP]NULL[RES]      [COL]-[RES]
620[COL]Title[RES]     [TYP]CHAR(50)[RES]  [TYP]NOT NULL[RES]  [VAL]Stranger in a Strange Land[RES]
621[COL]Genre[RES]     [TYP]CHAR(5)[RES]   [TYP]NOT NULL[RES]  [VAL]SF[RES]
622[COL]---------[RES] [COL]---------[RES] [COL]---------[RES] [COL]------[RES]
623[COL]Year[RES]      [TYP]INTEGER[RES]   [TYP]NOT NULL[RES]  [NUM]1967[RES]
624[COL]AuthorId[RES]  [TYP]INTEGER[RES]   [TYP]NOT NULL[RES]  [NUM]2[RES]
625[COL]Article[RES]   [TYP]CHAR(5)[RES]   [TYP]NULL[RES]      [VAL]The[RES]
626[COL]Title[RES]     [TYP]CHAR(50)[RES]  [TYP]NOT NULL[RES]  [VAL]Moon Is a Harsh Mistress[RES]
627[COL]Genre[RES]     [TYP]CHAR(5)[RES]   [TYP]NOT NULL[RES]  [VAL]SF[RES]
628";
629        let query = "SELECT * FROM Book WHERE AuthorId = 2";
630        let buffer = print_sql(Sample::Hugos, query, |writer, dataset| {
631            Engine::print_flatten(writer, Painter::Test, dataset, FlattenVerbose::Verbose)
632        })?;
633        assert_eq!(buffer, expected);
634        Ok(())
635    }
636
637    #[test]
638    fn test_text_records_are_printed_as_flatten_no_records() -> MyResult<()> {
639        let expected = "\
640[COL]column[RES]  [COL]type[RES]  [COL]nullable[RES]  [COL]value[RES]
641[COL]-------[RES] [COL]-----[RES] [COL]---------[RES] [COL]------[RES]
642";
643        let query = "SELECT * FROM Book WHERE AuthorId = 999";
644        let buffer = print_sql(Sample::Hugos, query, |writer, dataset| {
645            Engine::print_flatten(writer, Painter::Test, dataset, FlattenVerbose::Verbose)
646        })?;
647        assert_eq!(buffer, expected);
648        Ok(())
649    }
650
651    #[test]
652    fn test_numeric_records_are_printed_as_table_with_header() -> MyResult<()> {
653        let expected = "\
654[COL]Label[RES]   [COL]Int[RES]     [COL]Dec[RES]           [COL]LongIntegerValue[RES]  [COL]LongDecimalValue[RES]
655[COL]-------[RES] [COL]-------[RES] [COL]-------------[RES] [COL]-----------------[RES] [COL]-----------------[RES]
656[VAL]Large[RES]   [NUM]-99999[RES]  [NUM]-99999.99999[RES]            [NUM]-99999[RES]      [NUM]-99999.99999[RES]
657[VAL]Medium[RES]    [NUM]-555[RES]    [NUM]-555.555[RES]                [NUM]-555[RES]        [NUM]-555.555[RES]
658[VAL]Small[RES]       [NUM]-1[RES]      [NUM]-1.0[RES]                    [NUM]-1[RES]          [NUM]-1.0[RES]
659[VAL]Null[RES]         [COL]-[RES]       [COL]-[RES]                       [COL]-[RES]           [COL]-[RES]
660[VAL]Small[RES]        [NUM]1[RES]       [NUM]1.0[RES]                     [NUM]1[RES]           [NUM]1.0[RES]
661[VAL]Medium[RES]     [NUM]555[RES]     [NUM]555.555[RES]                 [NUM]555[RES]         [NUM]555.555[RES]
662[VAL]Large[RES]    [NUM]99999[RES]   [NUM]99999.99999[RES]             [NUM]99999[RES]       [NUM]99999.99999[RES]
663";
664        let query = "SELECT * FROM Number";
665        let buffer = print_sql(Sample::Numbers, query, |writer, dataset| {
666            Engine::print_table(writer, Painter::Test, dataset, TableHeader::Include, None)
667        })?;
668        assert_eq!(buffer, expected);
669        Ok(())
670    }
671
672    #[test]
673    fn test_numeric_records_are_printed_as_table_no_header() -> MyResult<()> {
674        let expected = "\
675[VAL]Large[RES]   [NUM]-99999[RES]  [NUM]-99999.99999[RES]  [NUM]-99999[RES]  [NUM]-99999.99999[RES]
676[VAL]Medium[RES]    [NUM]-555[RES]    [NUM]-555.555[RES]      [NUM]-555[RES]    [NUM]-555.555[RES]
677[VAL]Small[RES]       [NUM]-1[RES]      [NUM]-1.0[RES]          [NUM]-1[RES]      [NUM]-1.0[RES]
678[VAL]Null[RES]         [COL]-[RES]       [COL]-[RES]             [COL]-[RES]       [COL]-[RES]
679[VAL]Small[RES]        [NUM]1[RES]       [NUM]1.0[RES]           [NUM]1[RES]       [NUM]1.0[RES]
680[VAL]Medium[RES]     [NUM]555[RES]     [NUM]555.555[RES]       [NUM]555[RES]     [NUM]555.555[RES]
681[VAL]Large[RES]    [NUM]99999[RES]   [NUM]99999.99999[RES]   [NUM]99999[RES]   [NUM]99999.99999[RES]
682";
683        let query = "SELECT * FROM Number";
684        let buffer = print_sql(Sample::Numbers, query, |writer, dataset| {
685            Engine::print_table(writer, Painter::Test, dataset, TableHeader::Exclude, None)
686        })?;
687        assert_eq!(buffer, expected);
688        Ok(())
689    }
690
691    #[test]
692    fn test_numeric_column_is_printed_as_table_no_header() -> MyResult<()> {
693        let expected = "\
694[NUM]-99999.99999[RES]
695[NUM]-555.555[RES]
696[NUM]-1.0[RES]
697[COL]-[RES]
698[NUM]1.0[RES]
699[NUM]555.555[RES]
700[NUM]99999.99999[RES]
701";
702        let query = "SELECT LongDecimalValue FROM Number";
703        let buffer = print_sql(Sample::Numbers, query, |writer, dataset| {
704            Engine::print_table(writer, Painter::Test, dataset, TableHeader::Simple, None)
705        })?;
706        assert_eq!(buffer, expected);
707        Ok(())
708    }
709
710    #[test]
711    fn test_numeric_records_are_printed_as_flatten_no_types() -> MyResult<()> {
712        let expected = "\
713[COL]column[RES]            [COL]value[RES]
714[COL]-----------------[RES] [COL]------[RES]
715[COL]Label[RES]             [VAL]Large[RES]
716[COL]Int[RES]               [NUM]-99999[RES]
717[COL]Dec[RES]               [NUM]-99999.99999[RES]
718[COL]LongIntegerValue[RES]  [NUM]-99999[RES]
719[COL]LongDecimalValue[RES]  [NUM]-99999.99999[RES]
720[COL]-----------------[RES] [COL]------[RES]
721[COL]Label[RES]             [VAL]Medium[RES]
722[COL]Int[RES]               [NUM]-555[RES]
723[COL]Dec[RES]               [NUM]-555.555[RES]
724[COL]LongIntegerValue[RES]  [NUM]-555[RES]
725[COL]LongDecimalValue[RES]  [NUM]-555.555[RES]
726[COL]-----------------[RES] [COL]------[RES]
727[COL]Label[RES]             [VAL]Small[RES]
728[COL]Int[RES]               [NUM]-1[RES]
729[COL]Dec[RES]               [NUM]-1.0[RES]
730[COL]LongIntegerValue[RES]  [NUM]-1[RES]
731[COL]LongDecimalValue[RES]  [NUM]-1.0[RES]
732[COL]-----------------[RES] [COL]------[RES]
733[COL]Label[RES]             [VAL]Null[RES]
734[COL]Int[RES]               [COL]-[RES]
735[COL]Dec[RES]               [COL]-[RES]
736[COL]LongIntegerValue[RES]  [COL]-[RES]
737[COL]LongDecimalValue[RES]  [COL]-[RES]
738[COL]-----------------[RES] [COL]------[RES]
739[COL]Label[RES]             [VAL]Small[RES]
740[COL]Int[RES]               [NUM]1[RES]
741[COL]Dec[RES]               [NUM]1.0[RES]
742[COL]LongIntegerValue[RES]  [NUM]1[RES]
743[COL]LongDecimalValue[RES]  [NUM]1.0[RES]
744[COL]-----------------[RES] [COL]------[RES]
745[COL]Label[RES]             [VAL]Medium[RES]
746[COL]Int[RES]               [NUM]555[RES]
747[COL]Dec[RES]               [NUM]555.555[RES]
748[COL]LongIntegerValue[RES]  [NUM]555[RES]
749[COL]LongDecimalValue[RES]  [NUM]555.555[RES]
750[COL]-----------------[RES] [COL]------[RES]
751[COL]Label[RES]             [VAL]Large[RES]
752[COL]Int[RES]               [NUM]99999[RES]
753[COL]Dec[RES]               [NUM]99999.99999[RES]
754[COL]LongIntegerValue[RES]  [NUM]99999[RES]
755[COL]LongDecimalValue[RES]  [NUM]99999.99999[RES]
756";
757        let query = "SELECT * FROM Number";
758        let buffer = print_sql(Sample::Numbers, query, |writer, dataset| {
759            Engine::print_flatten(writer, Painter::Test, dataset, FlattenVerbose::Quiet)
760        })?;
761        assert_eq!(buffer, expected);
762        Ok(())
763    }
764
765    #[test]
766    fn test_numeric_records_are_printed_as_flatten_with_types() -> MyResult<()> {
767        let expected = "\
768[COL]column[RES]            [COL]type[RES]      [COL]nullable[RES]  [COL]value[RES]
769[COL]-----------------[RES] [COL]---------[RES] [COL]---------[RES] [COL]------[RES]
770[COL]Label[RES]             [TYP]CHAR(10)[RES]  [TYP]NOT NULL[RES]  [VAL]Large[RES]
771[COL]Int[RES]               [TYP]INTEGER[RES]   [TYP]NULL[RES]      [NUM]-99999[RES]
772[COL]Dec[RES]               [TYP]DOUBLE[RES]    [TYP]NULL[RES]      [NUM]-99999.99999[RES]
773[COL]LongIntegerValue[RES]  [TYP]INTEGER[RES]   [TYP]NULL[RES]      [NUM]-99999[RES]
774[COL]LongDecimalValue[RES]  [TYP]DOUBLE[RES]    [TYP]NULL[RES]      [NUM]-99999.99999[RES]
775[COL]-----------------[RES] [COL]---------[RES] [COL]---------[RES] [COL]------[RES]
776[COL]Label[RES]             [TYP]CHAR(10)[RES]  [TYP]NOT NULL[RES]  [VAL]Medium[RES]
777[COL]Int[RES]               [TYP]INTEGER[RES]   [TYP]NULL[RES]      [NUM]-555[RES]
778[COL]Dec[RES]               [TYP]DOUBLE[RES]    [TYP]NULL[RES]      [NUM]-555.555[RES]
779[COL]LongIntegerValue[RES]  [TYP]INTEGER[RES]   [TYP]NULL[RES]      [NUM]-555[RES]
780[COL]LongDecimalValue[RES]  [TYP]DOUBLE[RES]    [TYP]NULL[RES]      [NUM]-555.555[RES]
781[COL]-----------------[RES] [COL]---------[RES] [COL]---------[RES] [COL]------[RES]
782[COL]Label[RES]             [TYP]CHAR(10)[RES]  [TYP]NOT NULL[RES]  [VAL]Small[RES]
783[COL]Int[RES]               [TYP]INTEGER[RES]   [TYP]NULL[RES]      [NUM]-1[RES]
784[COL]Dec[RES]               [TYP]DOUBLE[RES]    [TYP]NULL[RES]      [NUM]-1.0[RES]
785[COL]LongIntegerValue[RES]  [TYP]INTEGER[RES]   [TYP]NULL[RES]      [NUM]-1[RES]
786[COL]LongDecimalValue[RES]  [TYP]DOUBLE[RES]    [TYP]NULL[RES]      [NUM]-1.0[RES]
787[COL]-----------------[RES] [COL]---------[RES] [COL]---------[RES] [COL]------[RES]
788[COL]Label[RES]             [TYP]CHAR(10)[RES]  [TYP]NOT NULL[RES]  [VAL]Null[RES]
789[COL]Int[RES]               [TYP]INTEGER[RES]   [TYP]NULL[RES]      [COL]-[RES]
790[COL]Dec[RES]               [TYP]DOUBLE[RES]    [TYP]NULL[RES]      [COL]-[RES]
791[COL]LongIntegerValue[RES]  [TYP]INTEGER[RES]   [TYP]NULL[RES]      [COL]-[RES]
792[COL]LongDecimalValue[RES]  [TYP]DOUBLE[RES]    [TYP]NULL[RES]      [COL]-[RES]
793[COL]-----------------[RES] [COL]---------[RES] [COL]---------[RES] [COL]------[RES]
794[COL]Label[RES]             [TYP]CHAR(10)[RES]  [TYP]NOT NULL[RES]  [VAL]Small[RES]
795[COL]Int[RES]               [TYP]INTEGER[RES]   [TYP]NULL[RES]      [NUM]1[RES]
796[COL]Dec[RES]               [TYP]DOUBLE[RES]    [TYP]NULL[RES]      [NUM]1.0[RES]
797[COL]LongIntegerValue[RES]  [TYP]INTEGER[RES]   [TYP]NULL[RES]      [NUM]1[RES]
798[COL]LongDecimalValue[RES]  [TYP]DOUBLE[RES]    [TYP]NULL[RES]      [NUM]1.0[RES]
799[COL]-----------------[RES] [COL]---------[RES] [COL]---------[RES] [COL]------[RES]
800[COL]Label[RES]             [TYP]CHAR(10)[RES]  [TYP]NOT NULL[RES]  [VAL]Medium[RES]
801[COL]Int[RES]               [TYP]INTEGER[RES]   [TYP]NULL[RES]      [NUM]555[RES]
802[COL]Dec[RES]               [TYP]DOUBLE[RES]    [TYP]NULL[RES]      [NUM]555.555[RES]
803[COL]LongIntegerValue[RES]  [TYP]INTEGER[RES]   [TYP]NULL[RES]      [NUM]555[RES]
804[COL]LongDecimalValue[RES]  [TYP]DOUBLE[RES]    [TYP]NULL[RES]      [NUM]555.555[RES]
805[COL]-----------------[RES] [COL]---------[RES] [COL]---------[RES] [COL]------[RES]
806[COL]Label[RES]             [TYP]CHAR(10)[RES]  [TYP]NOT NULL[RES]  [VAL]Large[RES]
807[COL]Int[RES]               [TYP]INTEGER[RES]   [TYP]NULL[RES]      [NUM]99999[RES]
808[COL]Dec[RES]               [TYP]DOUBLE[RES]    [TYP]NULL[RES]      [NUM]99999.99999[RES]
809[COL]LongIntegerValue[RES]  [TYP]INTEGER[RES]   [TYP]NULL[RES]      [NUM]99999[RES]
810[COL]LongDecimalValue[RES]  [TYP]DOUBLE[RES]    [TYP]NULL[RES]      [NUM]99999.99999[RES]
811";
812        let query = "SELECT * FROM Number";
813        let buffer = print_sql(Sample::Numbers, query, |writer, dataset| {
814            Engine::print_flatten(writer, Painter::Test, dataset, FlattenVerbose::Verbose)
815        })?;
816        assert_eq!(buffer, expected);
817        Ok(())
818    }
819
820    #[test]
821    fn test_csv_file_is_formatted_as_table_with_text() -> MyResult<()> {
822        let text = "\
823Text,Mixed,Number,Empty
824Aaa,1111.1,4444.4,
825Bbbb,2.2222,5.5555,
826Cc,Eee,,
827Ddddd,33.33,66.66,
828";
829        let expected = "\
830[COL]Text[RES]   [COL]Mixed[RES]   [COL]Number[RES]  [COL]Empty[RES]
831[COL]------[RES] [COL]-------[RES] [COL]-------[RES] [COL]------[RES]
832[VAL]Aaa[RES]    [VAL]1111.1[RES]  [VAL]4444.4[RES]  [COL]-[RES]
833[VAL]Bbbb[RES]   [VAL]2.2222[RES]  [VAL]5.5555[RES]  [COL]-[RES]
834[VAL]Cc[RES]     [VAL]Eee[RES]     [COL]-[RES]       [COL]-[RES]
835[VAL]Ddddd[RES]  [VAL]33.33[RES]   [VAL]66.66[RES]   [COL]-[RES]
836";
837        let buffer = print_csv(text, false, |writer, dataset| {
838            Engine::print_table(writer, Painter::Test, dataset, TableHeader::Include, None)
839        })?;
840        assert_eq!(buffer, expected);
841        Ok(())
842    }
843
844    #[test]
845    fn test_csv_file_is_formatted_as_table_with_auto() -> MyResult<()> {
846        let text = "\
847Text,Mixed,Number,Empty
848Aaa,1111.1,4444.4,
849Bbbb,2.2222,5.5555,
850Cc,Eee,,
851Ddddd,33.33,66.66,
852";
853        let expected = "\
854[COL]Text[RES]   [COL]Mixed[RES]   [COL]Number[RES]     [COL]Empty[RES]
855[COL]------[RES] [COL]-------[RES] [COL]----------[RES] [COL]------[RES]
856[VAL]Aaa[RES]    [VAL]1111.1[RES]  [NUM]4444.4[RES]     [COL]-[RES]
857[VAL]Bbbb[RES]   [VAL]2.2222[RES]     [NUM]5.5555[RES]  [COL]-[RES]
858[VAL]Cc[RES]     [VAL]Eee[RES]        [COL]-[RES]       [COL]-[RES]
859[VAL]Ddddd[RES]  [VAL]33.33[RES]     [NUM]66.66[RES]    [COL]-[RES]
860";
861        let buffer = print_csv(text, true, |writer, dataset| {
862            Engine::print_table(writer, Painter::Test, dataset, TableHeader::Include, None)
863        })?;
864        assert_eq!(buffer, expected);
865        Ok(())
866    }
867
868    fn print_sql<F>(sample: Sample, query: &str, function: F) -> MyResult<String> where
869        F: Fn(&mut Vec<u8>, Dataset<File>) -> MyResult<()>,
870    {
871        let env = Environment::new()?;
872        let driver = Driver::new("Driver=SQLite3;Database=:memory:")?;
873        let database = Database::new(&env, &driver)?.with_records(sample)?;
874        let dataset = database.create_dataset(query)?;
875        let mut buffer = Vec::new();
876        if let Some(dataset) = dataset {
877            function(&mut buffer, dataset)?;
878        }
879        Ok(to_string(buffer))
880    }
881
882    fn print_csv<F>(text: &str, auto: bool, function: F) -> MyResult<String> where
883        F: Fn(&mut Vec<u8>, Dataset<BufReader<&[u8]>>) -> MyResult<()>,
884    {
885        let reader = BufReader::new(text.as_bytes());
886        let dataset = Dataset::from_file(reader, auto)?;
887        let mut buffer = Vec::new();
888        if let Some(dataset) = dataset {
889            function(&mut buffer, dataset)?;
890        }
891        Ok(to_string(buffer))
892    }
893
894    fn to_string(buffer: Vec<u8>) -> String {
895        String::from_utf8(buffer).unwrap_or_default()
896    }
897}