ripgrep-all/src/adapters/sqlite.rs

151 lines
4.6 KiB
Rust
Raw Normal View History

2019-06-07 14:57:11 +00:00
use super::*;
use anyhow::Result;
2019-06-07 14:57:11 +00:00
use lazy_static::lazy_static;
2019-06-18 10:14:09 +00:00
use log::*;
2019-06-07 18:12:24 +00:00
use rusqlite::types::ValueRef;
2019-06-07 14:57:11 +00:00
use rusqlite::*;
use std::convert::TryInto;
2020-06-11 21:09:31 +00:00
use writing::{WritingFileAdapter, WritingFileAdapterTrait};
2019-06-07 14:57:11 +00:00
static EXTENSIONS: &[&str] = &["db", "db3", "sqlite", "sqlite3"];
lazy_static! {
static ref METADATA: AdapterMeta = AdapterMeta {
name: "sqlite".to_owned(),
version: 1,
2019-06-07 22:04:48 +00:00
description:
"Uses sqlite bindings to convert sqlite databases into a simple plain text format"
.to_owned(),
2019-06-16 10:19:01 +00:00
recurses: false, // set to true if we decide to make sqlite blobs searchable (gz blob in db is kinda common I think)
2019-06-11 11:34:04 +00:00
fast_matchers: EXTENSIONS
2019-06-07 14:57:11 +00:00
.iter()
2020-06-17 09:45:06 +00:00
.map(|s| FastFileMatcher::FileExtension(s.to_string()))
2019-06-07 14:57:11 +00:00
.collect(),
2020-06-17 09:45:06 +00:00
slow_matchers: Some(vec![FileMatcher::MimeType(
2019-06-11 11:34:04 +00:00
"application/x-sqlite3".to_owned()
)]),
2020-09-10 15:18:11 +00:00
keep_fast_matchers_if_accurate: false,
disabled_by_default: false
2019-06-07 14:57:11 +00:00
};
}
2020-06-11 21:09:31 +00:00
#[derive(Default, Clone)]
2019-06-07 14:57:11 +00:00
pub struct SqliteAdapter;
impl SqliteAdapter {
2020-06-11 21:09:31 +00:00
pub fn new() -> WritingFileAdapter {
WritingFileAdapter::new(Box::new(SqliteAdapter {}))
2019-06-07 14:57:11 +00:00
}
}
impl GetMetadata for SqliteAdapter {
fn metadata(&self) -> &AdapterMeta {
&METADATA
}
}
fn format_blob(b: ValueRef) -> String {
use ValueRef::*;
match b {
Null => "NULL".to_owned(),
Integer(i) => format!("{}", i),
Real(i) => format!("{}", i),
Text(i) => format!("'{}'", String::from_utf8_lossy(i).replace("'", "''")),
2019-06-07 14:57:11 +00:00
Blob(b) => format!(
"[blob {}B]",
size_format::SizeFormatterSI::new(
// can't be larger than 2GB anyways
b.len().try_into().unwrap()
)
),
}
}
2020-06-11 21:09:31 +00:00
impl WritingFileAdapterTrait for SqliteAdapter {
fn adapt_write(
&self,
ai: AdaptInfo,
2020-06-17 09:45:06 +00:00
_detection_reason: &FileMatcher,
2020-06-11 21:09:31 +00:00
oup: &mut dyn Write,
) -> Result<()> {
2019-06-07 14:57:11 +00:00
let AdaptInfo {
is_real_file,
filepath_hint,
line_prefix,
..
} = ai;
if !is_real_file {
2019-06-13 14:26:03 +00:00
// db is in an archive
2019-06-07 14:57:11 +00:00
// todo: read to memory and then use that blob if size < max
writeln!(oup, "{}[rga: skipping sqlite in archive]", line_prefix,)?;
return Ok(());
}
let inp_fname = filepath_hint;
let conn = Connection::open_with_flags(inp_fname, OpenFlags::SQLITE_OPEN_READ_ONLY)?;
let tables: Vec<String> = conn
.prepare("select name from sqlite_master where type='table'")?
.query_map(NO_PARAMS, |r| r.get::<_, String>(0))?
.filter_map(|e| e.ok())
.collect();
2019-06-18 10:14:09 +00:00
debug!("db has {} tables", tables.len());
2019-06-07 14:57:11 +00:00
for table in tables {
// can't use query param at that position
let mut sel = conn.prepare(&format!(
"select * from {}",
rusqlite::vtab::escape_double_quote(&table)
))?;
let mut z = sel.query(NO_PARAMS)?;
let col_names: Vec<String> = z
.column_names()
.ok_or_else(|| format_err!("no column names"))?
.into_iter()
.map(|e| e.to_owned())
.collect();
// writeln!(oup, "{}: {}", table, cols.join(", "))?;
// kind of shitty (lossy) output. maybe output real csv or something?
while let Some(row) = z.next()? {
writeln!(
oup,
2019-06-12 15:23:30 +00:00
"{}{}: {}",
line_prefix,
2019-06-07 14:57:11 +00:00
table,
col_names
.iter()
.enumerate()
.map(|(i, e)| format!("{}={}", e, format_blob(row.get_raw(i))))
.collect::<Vec<String>>()
.join(", ")
)?;
}
}
Ok(())
}
}
2020-06-11 21:09:31 +00:00
#[cfg(test)]
mod test {
use super::*;
2020-06-17 09:45:06 +00:00
use crate::test_utils::*;
use std::fs::File;
2020-06-11 21:09:31 +00:00
#[test]
fn simple() -> Result<()> {
let adapter: Box<dyn FileAdapter> = Box::new(SqliteAdapter::new());
let fname = test_data_dir().join("hello.sqlite3");
let rd = File::open(&fname)?;
let (a, d) = simple_adapt_info(&fname, Box::new(rd));
let mut res = adapter.adapt(a, &d)?;
let mut buf = Vec::new();
res.read_to_end(&mut buf)?;
assert_eq!(
String::from_utf8(buf)?,
"PREFIX:tbl: greeting='hello', from='sqlite database!'\nPREFIX:tbl2: x=123, y=456.789\n",
);
Ok(())
}
}