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}