atuin/src/local/database.rs

195 lines
4.9 KiB
Rust
Raw Normal View History

use chrono::Utc;
use std::path::Path;
use eyre::Result;
use rusqlite::{params, Connection};
2021-02-14 10:18:02 -07:00
use rusqlite::{Transaction, NO_PARAMS};
2021-02-14 10:18:02 -07:00
use super::history::History;
pub trait Database {
2021-02-14 10:18:02 -07:00
fn save(&mut self, h: &History) -> Result<()>;
2021-02-14 08:15:26 -07:00
fn save_bulk(&mut self, h: &[History]) -> Result<()>;
2021-02-13 10:02:52 -07:00
fn load(&self, id: &str) -> Result<History>;
fn list(&self) -> Result<Vec<History>>;
fn since(&self, date: chrono::DateTime<Utc>) -> Result<Vec<History>>;
2021-02-14 10:18:02 -07:00
fn update(&self, h: &History) -> Result<()>;
}
// Intended for use on a developer machine and not a sync server.
// TODO: implement IntoIterator
2021-02-14 08:15:26 -07:00
pub struct Sqlite {
conn: Connection,
}
2021-02-14 08:15:26 -07:00
impl Sqlite {
pub fn new(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
debug!("opening sqlite database at {:?}", path);
let create = !path.exists();
if create {
if let Some(dir) = path.parent() {
std::fs::create_dir_all(dir)?;
}
}
let conn = Connection::open(path)?;
if create {
Self::setup_db(&conn)?;
}
2021-02-14 08:15:26 -07:00
Ok(Self { conn })
}
fn setup_db(conn: &Connection) -> Result<()> {
debug!("running sqlite database setup");
conn.execute(
"create table if not exists history (
2021-02-13 10:02:52 -07:00
id text primary key,
timestamp integer not null,
2021-02-13 10:02:52 -07:00
duration integer not null,
exit integer not null,
command text not null,
2021-02-13 12:37:00 -07:00
cwd text not null,
2021-02-13 13:21:49 -07:00
session text not null,
hostname text not null,
2021-02-13 12:37:00 -07:00
unique(timestamp, cwd, command)
)",
NO_PARAMS,
)?;
Ok(())
}
2021-02-14 10:18:02 -07:00
fn save_raw(tx: &Transaction, h: &History) -> Result<()> {
tx.execute(
"insert or ignore into history (
id,
timestamp,
duration,
exit,
command,
cwd,
session,
hostname
) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
params![
h.id,
h.timestamp,
h.duration,
h.exit,
h.command,
h.cwd,
h.session,
h.hostname
],
)?;
Ok(())
}
}
2021-02-14 08:15:26 -07:00
impl Database for Sqlite {
2021-02-14 10:18:02 -07:00
fn save(&mut self, h: &History) -> Result<()> {
debug!("saving history to sqlite");
2021-02-14 10:18:02 -07:00
let tx = self.conn.transaction()?;
Self::save_raw(&tx, h)?;
tx.commit()?;
Ok(())
2021-02-13 10:02:52 -07:00
}
2021-02-14 08:15:26 -07:00
fn save_bulk(&mut self, h: &[History]) -> Result<()> {
2021-02-13 12:37:00 -07:00
debug!("saving history to sqlite");
let tx = self.conn.transaction()?;
for i in h {
2021-02-14 10:18:02 -07:00
Self::save_raw(&tx, i)?
2021-02-13 12:37:00 -07:00
}
tx.commit()?;
Ok(())
}
2021-02-13 10:02:52 -07:00
fn load(&self, id: &str) -> Result<History> {
debug!("loading history item");
let mut stmt = self.conn.prepare(
2021-02-13 13:21:49 -07:00
"select id, timestamp, duration, exit, command, cwd, session, hostname from history
2021-02-13 10:02:52 -07:00
where id = ?1",
)?;
let history = stmt.query_row(params![id], |row| {
2021-02-14 10:18:02 -07:00
history_from_sqlite_row(Some(id.to_string()), row)
2021-02-13 10:02:52 -07:00
})?;
Ok(history)
2021-02-13 10:02:52 -07:00
}
2021-02-14 10:18:02 -07:00
fn update(&self, h: &History) -> Result<()> {
2021-02-13 10:02:52 -07:00
debug!("updating sqlite history");
self.conn.execute(
"update history
2021-02-13 13:21:49 -07:00
set timestamp = ?2, duration = ?3, exit = ?4, command = ?5, cwd = ?6, session = ?7, hostname = ?8
2021-02-13 10:02:52 -07:00
where id = ?1",
2021-02-13 13:21:49 -07:00
params![h.id, h.timestamp, h.duration, h.exit, h.command, h.cwd, h.session, h.hostname],
)?;
Ok(())
}
fn list(&self) -> Result<Vec<History>> {
debug!("listing history");
let mut stmt = self
.conn
.prepare("SELECT * FROM history order by timestamp asc")?;
2021-02-13 10:02:52 -07:00
2021-02-14 10:18:02 -07:00
let history_iter = stmt.query_map(params![], |row| history_from_sqlite_row(None, row))?;
2021-02-14 10:18:02 -07:00
Ok(history_iter.filter_map(Result::ok).collect())
}
fn since(&self, date: chrono::DateTime<Utc>) -> Result<Vec<History>> {
2021-02-14 10:18:02 -07:00
debug!("listing history since {:?}", date);
let mut stmt = self.conn.prepare(
"SELECT distinct command FROM history where timestamp > ?1 order by timestamp asc",
)?;
let history_iter = stmt.query_map(params![date.timestamp_nanos()], |row| {
2021-02-14 10:18:02 -07:00
history_from_sqlite_row(None, row)
})?;
2021-02-14 10:18:02 -07:00
Ok(history_iter.filter_map(Result::ok).collect())
}
}
2021-02-14 10:18:02 -07:00
fn history_from_sqlite_row(
id: Option<String>,
row: &rusqlite::Row,
) -> Result<History, rusqlite::Error> {
let id = match id {
Some(id) => id,
None => row.get(0)?,
};
Ok(History {
id,
timestamp: row.get(1)?,
duration: row.get(2)?,
exit: row.get(3)?,
command: row.get(4)?,
cwd: row.get(5)?,
session: row.get(6)?,
hostname: row.get(7)?,
})
}