Start testing

This commit is contained in:
Ellie Huxtable 2023-06-01 21:33:31 +01:00
parent 5e79c02f78
commit 4b602c6fdb
4 changed files with 99 additions and 18 deletions

View File

@ -6,5 +6,9 @@ create table if not exists records (
tag text not null,
version text not null,
data blob not null,
data blob not null
);
create index host_idx on records (host);
create index tag_idx on records (tag);
create index host_tag_idx on records (host, tag);

View File

@ -2,23 +2,15 @@
// Multiple stores of multiple types are all stored in one chonky table (for now), and we just index
// by tag/host
yo tomorrow morning me
drink that coffee
then wrap up this interface
you will need to
- make sure the records use string IDs
- add the version in
- write some tests with a memory sqlite
use std::path::Path;
use std::str::FromStr;
use async_trait::async_trait;
use eyre::Result;
use fs_err as fs;
use sqlx::{
sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePool, SqlitePoolOptions, SqliteRow},
Result, Row,
Row,
};
use atuin_common::record::Record;
@ -60,11 +52,94 @@ impl SqliteStore {
Ok(())
}
async fn save_raw(tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>, r: &Record) -> Result<()> {
// In sqlite, we are "limited" to i64. But that is still fine, until 2262.
sqlx::query(
"insert or ignore into records(id, host, timestamp, tag, version, data)
values(?1, ?2, ?3, ?4, ?5, ?6)",
)
.bind(r.id.as_str())
.bind(r.host.as_str())
.bind(r.timestamp as i64)
.bind(r.tag.as_str())
.bind(r.version.as_str())
.bind(r.data.as_slice())
.execute(tx)
.await?;
Ok(())
}
fn query_row(row: SqliteRow) -> Record {
let timestamp: i64 = row.get("timestamp");
Record {
id: row.get("id"),
host: row.get("host"),
timestamp: timestamp as u64,
tag: row.get("tag"),
version: row.get("version"),
data: row.get("data"),
}
}
}
#[async_trait]
impl Store for SqliteStore {
async fn push(record: Record) -> Result<Record> {
async fn push(&self, record: Record) -> Result<Record> {
// TODO: batch inserts
let mut tx = self.pool.begin().await?;
Self::save_raw(&mut tx, &record).await?;
Ok(record)
}
async fn get(&self, id: String) -> Result<Record> {
let res = sqlx::query("select * from records where id = ?1")
.bind(id.as_str())
.map(Self::query_row)
.fetch_one(&self.pool)
.await?;
Ok(res)
}
async fn len(&self, host: String, tag: String) -> Result<u64> {
let res: (i64,) =
sqlx::query_as("select count(1) from records where host = ?1 and tag = ?2")
.bind(host.as_str())
.bind(tag.as_str())
.fetch_one(&self.pool)
.await?;
Ok(res.0 as u64)
}
}
#[cfg(test)]
mod tests {
use super::SqliteStore;
#[tokio::test]
async fn create_db() {
let db = SqliteStore::new(":memory:").await;
assert!(
db.is_ok(),
"db could not be created, {:?}",
db.err().unwrap()
);
}
#[tokio::test]
async fn push_record() {
let db = SqliteStore::new(":memory:").await;
assert!(
db.is_ok(),
"db could not be created, {:?}",
db.err().unwrap()
);
}
}

View File

@ -9,7 +9,7 @@ use atuin_common::record::Record;
/// be shell history, kvs, etc.
#[async_trait]
pub trait Store {
async fn push(record: Record) -> Result<Record>;
async fn get(id: String) -> Result<Record>;
async fn len(host: String, tag: String) -> Result<usize>;
async fn push(&self, record: Record) -> Result<Record>;
async fn get(&self, id: String) -> Result<Record>;
async fn len(&self, host: String, tag: String) -> Result<u64>;
}

View File

@ -1,13 +1,15 @@
/// A single record stored inside of our local database
pub struct Record {
pub id: i64,
pub id: String,
pub host: String,
pub timestamp: u64,
/// The type of data we are storing here. It is probably useful to also
/// include some sort of version. For example, history.v2
// However we want to track versions for this tag, eg v2
pub version: String,
/// The type of data we are storing here. Eg, "history"
pub tag: String,
/// Some data. This can be anything you wish to store. Use the tag field to know how to handle it.