aboutsummaryrefslogtreecommitdiffstats
path: root/atuin-client/src/database.rs
diff options
context:
space:
mode:
authorEllie Huxtable <ellie@elliehuxtable.com>2023-03-20 09:26:54 +0000
committerGitHub <noreply@github.com>2023-03-20 09:26:54 +0000
commitdcd77749dd1fdf6b0c8183bfbdf4f97bf238ebe4 (patch)
tree97c623911eeb52da65c2b3fd80092f2c86f3dd18 /atuin-client/src/database.rs
parentskim-demo (#695) (diff)
downloadatuin-dcd77749dd1fdf6b0c8183bfbdf4f97bf238ebe4.zip
Add history deletion (#791)
* Drop events. I'd still like to do them, but differently * Start adding delete api stuff * Set mailmap * Delete delete delete * Fix tests * Make clippy happy
Diffstat (limited to 'atuin-client/src/database.rs')
-rw-r--r--atuin-client/src/database.rs98
1 files changed, 28 insertions, 70 deletions
diff --git a/atuin-client/src/database.rs b/atuin-client/src/database.rs
index b24020c0..a29dfa13 100644
--- a/atuin-client/src/database.rs
+++ b/atuin-client/src/database.rs
@@ -14,7 +14,6 @@ use sqlx::{
};
use super::{
- event::{Event, EventType},
history::History,
ordering,
settings::{FilterMode, SearchMode},
@@ -62,13 +61,14 @@ pub trait Database: Send + Sync {
async fn update(&self, h: &History) -> Result<()>;
async fn history_count(&self) -> Result<i64>;
- async fn event_count(&self) -> Result<i64>;
- async fn merge_events(&self) -> Result<i64>;
async fn first(&self) -> Result<History>;
async fn last(&self) -> Result<History>;
async fn before(&self, timestamp: chrono::DateTime<Utc>, count: i64) -> Result<Vec<History>>;
+ async fn delete(&self, mut h: History) -> Result<()>;
+ async fn deleted(&self) -> Result<Vec<History>>;
+
// Yes I know, it's a lot.
// Could maybe break it down to a searchparams struct or smth but that feels a little... pointless.
// Been debating maybe a DSL for search? eg "before:time limit:1 the query"
@@ -126,31 +126,10 @@ impl Sqlite {
Ok(())
}
- async fn save_event(tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>, e: &Event) -> Result<()> {
- let event_type = match e.event_type {
- EventType::Create => "create",
- EventType::Delete => "delete",
- };
-
- sqlx::query(
- "insert or ignore into events(id, timestamp, hostname, event_type, history_id)
- values(?1, ?2, ?3, ?4, ?5)",
- )
- .bind(e.id.as_str())
- .bind(e.timestamp.timestamp_nanos())
- .bind(e.hostname.as_str())
- .bind(event_type)
- .bind(e.history_id.as_str())
- .execute(tx)
- .await?;
-
- Ok(())
- }
-
async fn save_raw(tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>, h: &History) -> Result<()> {
sqlx::query(
- "insert or ignore into history(id, timestamp, duration, exit, command, cwd, session, hostname)
- values(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
+ "insert or ignore into history(id, timestamp, duration, exit, command, cwd, session, hostname, deleted_at)
+ values(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
)
.bind(h.id.as_str())
.bind(h.timestamp.timestamp_nanos())
@@ -160,6 +139,7 @@ impl Sqlite {
.bind(h.cwd.as_str())
.bind(h.session.as_str())
.bind(h.hostname.as_str())
+ .bind(h.deleted_at.map(|t|t.timestamp_nanos()))
.execute(tx)
.await?;
@@ -167,6 +147,8 @@ impl Sqlite {
}
fn query_history(row: SqliteRow) -> History {
+ let deleted_at: Option<i64> = row.get("deleted_at");
+
History {
id: row.get("id"),
timestamp: Utc.timestamp_nanos(row.get("timestamp")),
@@ -176,6 +158,7 @@ impl Sqlite {
cwd: row.get("cwd"),
session: row.get("session"),
hostname: row.get("hostname"),
+ deleted_at: deleted_at.map(|t| Utc.timestamp_nanos(t)),
}
}
}
@@ -184,11 +167,8 @@ impl Sqlite {
impl Database for Sqlite {
async fn save(&mut self, h: &History) -> Result<()> {
debug!("saving history to sqlite");
- let event = Event::new_create(h);
-
let mut tx = self.pool.begin().await?;
Self::save_raw(&mut tx, h).await?;
- Self::save_event(&mut tx, &event).await?;
tx.commit().await?;
Ok(())
@@ -200,9 +180,7 @@ impl Database for Sqlite {
let mut tx = self.pool.begin().await?;
for i in h {
- let event = Event::new_create(i);
Self::save_raw(&mut tx, i).await?;
- Self::save_event(&mut tx, &event).await?;
}
tx.commit().await?;
@@ -227,7 +205,7 @@ impl Database for Sqlite {
sqlx::query(
"update history
- set timestamp = ?2, duration = ?3, exit = ?4, command = ?5, cwd = ?6, session = ?7, hostname = ?8
+ set timestamp = ?2, duration = ?3, exit = ?4, command = ?5, cwd = ?6, session = ?7, hostname = ?8, deleted_at = ?9
where id = ?1",
)
.bind(h.id.as_str())
@@ -238,6 +216,7 @@ impl Database for Sqlite {
.bind(h.cwd.as_str())
.bind(h.session.as_str())
.bind(h.hostname.as_str())
+ .bind(h.deleted_at.map(|t|t.timestamp_nanos()))
.execute(&self.pool)
.await?;
@@ -338,49 +317,15 @@ impl Database for Sqlite {
Ok(res)
}
- async fn event_count(&self) -> Result<i64> {
- let res: i64 = sqlx::query_scalar("select count(1) from events")
- .fetch_one(&self.pool)
+ async fn deleted(&self) -> Result<Vec<History>> {
+ let res = sqlx::query("select * from history where deleted_at is not null")
+ .map(Self::query_history)
+ .fetch_all(&self.pool)
.await?;
Ok(res)
}
- // Ensure that we have correctly merged the event log
- async fn merge_events(&self) -> Result<i64> {
- // Ensure that we do not have more history locally than we do events.
- // We can think of history as the merged log of events. There should never be more history than
- // events, and the only time this could happen is if someone is upgrading from an old Atuin version
- // from before we stored events.
- let history_count = self.history_count().await?;
- let event_count = self.event_count().await?;
-
- if history_count > event_count {
- // pass an empty context, because with global listing we don't care
- let no_context = Context {
- cwd: String::from(""),
- session: String::from(""),
- hostname: String::from(""),
- };
-
- // We're just gonna load everything into memory here. That sucks, I know, sorry.
- // But also even if you have a LOT of history that should be fine, and we're only going to be doing this once EVER.
- let all_the_history = self
- .list(FilterMode::Global, &no_context, None, false)
- .await?;
-
- let mut tx = self.pool.begin().await?;
- for i in all_the_history.iter() {
- // A CREATE for every single history item is to be expected.
- let event = Event::new_create(i);
- Self::save_event(&mut tx, &event).await?;
- }
- tx.commit().await?;
- }
-
- Ok(0)
- }
-
async fn history_count(&self) -> Result<i64> {
let res: (i64,) = sqlx::query_as("select count(1) from history")
.fetch_one(&self.pool)
@@ -528,6 +473,18 @@ impl Database for Sqlite {
Ok(res)
}
+
+ // deleted_at doesn't mean the actual time that the user deleted it,
+ // but the time that the system marks it as deleted
+ async fn delete(&self, mut h: History) -> Result<()> {
+ let now = chrono::Utc::now();
+ h.command = String::from(""); // blank it
+ h.deleted_at = Some(now); // delete it
+
+ self.update(&h).await?; // save it
+
+ Ok(())
+ }
}
#[cfg(test)]
@@ -585,6 +542,7 @@ mod test {
1,
Some("beep boop".to_string()),
Some("booop".to_string()),
+ None,
);
db.save(&history).await
}