1 use super::*;
2 use failure::*;
3 use lazy_static::lazy_static;
4 use log::*;
5 use rusqlite::types::ValueRef;
6 use rusqlite::*;
7 use std::convert::TryInto;
8 
9 static EXTENSIONS: &[&str] = &["db", "db3", "sqlite", "sqlite3"];
10 
11 lazy_static! {
12     static ref METADATA: AdapterMeta = AdapterMeta {
13         name: "sqlite".to_owned(),
14         version: 1,
15         description:
16             "Uses sqlite bindings to convert sqlite databases into a simple plain text format"
17                 .to_owned(),
18         recurses: false, // set to true if we decide to make sqlite blobs searchable (gz blob in db is kinda common I think)
19         fast_matchers: EXTENSIONS
20             .iter()
21             .map(|s| FastMatcher::FileExtension(s.to_string()))
22             .collect(),
23         slow_matchers: Some(vec![SlowMatcher::MimeType(
24             "application/x-sqlite3".to_owned()
25         )])
26     };
27 }
28 
29 #[derive(Default)]
30 pub struct SqliteAdapter;
31 
32 impl SqliteAdapter {
new() -> SqliteAdapter33     pub fn new() -> SqliteAdapter {
34         SqliteAdapter
35     }
36 }
37 impl GetMetadata for SqliteAdapter {
metadata(&self) -> &AdapterMeta38     fn metadata(&self) -> &AdapterMeta {
39         &METADATA
40     }
41 }
42 
format_blob(b: ValueRef) -> String43 fn format_blob(b: ValueRef) -> String {
44     use ValueRef::*;
45     match b {
46         Null => "NULL".to_owned(),
47         Integer(i) => format!("{}", i),
48         Real(i) => format!("{}", i),
49         Text(i) => format!("'{}'", String::from_utf8_lossy(i).replace("'", "''")),
50         Blob(b) => format!(
51             "[blob {}B]",
52             size_format::SizeFormatterSI::new(
53                 // can't be larger than 2GB anyways
54                 b.len().try_into().unwrap()
55             )
56         ),
57     }
58 }
59 
60 impl FileAdapter for SqliteAdapter {
adapt(&self, ai: AdaptInfo, _detection_reason: &SlowMatcher) -> Fallible<()>61     fn adapt(&self, ai: AdaptInfo, _detection_reason: &SlowMatcher) -> Fallible<()> {
62         let AdaptInfo {
63             is_real_file,
64             filepath_hint,
65             oup,
66             line_prefix,
67             ..
68         } = ai;
69         if !is_real_file {
70             // db is in an archive
71             // todo: read to memory and then use that blob if size < max
72             writeln!(oup, "{}[rga: skipping sqlite in archive]", line_prefix,)?;
73             return Ok(());
74         }
75         let inp_fname = filepath_hint;
76 
77         let conn = Connection::open_with_flags(inp_fname, OpenFlags::SQLITE_OPEN_READ_ONLY)?;
78         let tables: Vec<String> = conn
79             .prepare("select name from sqlite_master where type='table'")?
80             .query_map(NO_PARAMS, |r| r.get::<_, String>(0))?
81             .filter_map(|e| e.ok())
82             .collect();
83         debug!("db has {} tables", tables.len());
84         for table in tables {
85             // can't use query param at that position
86             let mut sel = conn.prepare(&format!(
87                 "select * from {}",
88                 rusqlite::vtab::escape_double_quote(&table)
89             ))?;
90             let mut z = sel.query(NO_PARAMS)?;
91             let col_names: Vec<String> = z
92                 .column_names()
93                 .ok_or_else(|| format_err!("no column names"))?
94                 .into_iter()
95                 .map(|e| e.to_owned())
96                 .collect();
97             // writeln!(oup, "{}: {}", table, cols.join(", "))?;
98 
99             // kind of shitty (lossy) output. maybe output real csv or something?
100             while let Some(row) = z.next()? {
101                 writeln!(
102                     oup,
103                     "{}{}: {}",
104                     line_prefix,
105                     table,
106                     col_names
107                         .iter()
108                         .enumerate()
109                         .map(|(i, e)| format!("{}={}", e, format_blob(row.get_raw(i))))
110                         .collect::<Vec<String>>()
111                         .join(", ")
112                 )?;
113             }
114         }
115         Ok(())
116     }
117 }
118