Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ keywords = ["database", "sqlite"]
license-file = "LICENSE.md"
readme = "README.md"
repository = "https://github.com/ReactorScram/sql-hummus"
version = "0.1.3"
version = "0.1.4"

[dependencies]
anyhow = { version = "1.0.103", features = ["backtrace"] }
camino = "1.2.4"
camino = { version = "1.2.4", features = ["serde1"] }
chrono = "0.4.45"
clap = { version = "4.6.1", features = ["derive"] }
directories = "6.0.0"
Expand Down
3 changes: 3 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ pub(crate) enum Command {
cmd: LogCommand,
},

/// Quick paste to the default log file. Uses `wl-paste`
Paste,

/// Quick push to the default log file
Push { content: Option<String> },
}
Expand Down
72 changes: 72 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/// Public-facing opaque error
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct Error(#[from] ErrorRepr);

pub type Result<T> = std::result::Result<T, Error>;

// FIXME: I'm not 100% about this error setup. If you have gdb, it's redundant. If you don't have gdb, it's better than nothing.
// Unlike anyhow it's only safe Rust, and it's very simple, and it's my code.
#[derive(Debug, thiserror::Error)]
#[error("kind={kind}; cookies={cookies:?}")]
struct ErrorRepr {
cookies: Vec<&'static str>,
#[source]
kind: ErrorKind,
}

#[derive(Debug, thiserror::Error)]
pub(crate) enum ErrorKind {
#[error(transparent)]
FromPathBufError(#[from] camino::FromPathBufError),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Error: {0}")]
Str(&'static str),
#[error("SQLite error: {0}")]
Sqlite(#[from] sql_peas::Error),
#[error("ULID decode error: {0}")]
UlidDecode(#[from] ulid::DecodeError),
}

impl From<&'static str> for ErrorKind {
fn from(s: &'static str) -> Self {
Self::Str(s)
}
}

impl<T> From<T> for Error
where
ErrorKind: From<T>,
{
fn from(value: T) -> Self {
ErrorRepr {
cookies: vec![],
kind: ErrorKind::from(value),
}
.into()
}
}

/// Just like `anyhow::Context` but simpler
///
/// "Type 'cookie', you idiot." -- The Plague, Hackers (film)
pub(crate) trait Cookie<T> {
fn cookie(self, s: &'static str) -> Result<T>;
}

impl<T, E> Cookie<T> for std::result::Result<T, E>
where
Error: From<E>,
{
fn cookie(self, s: &'static str) -> Result<T> {
match self {
Ok(x) => Ok(x),
Err(e) => {
let mut e = Error::from(e);
e.0.cookies.push(s);
Err(e)
}
}
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod error;
pub mod kv;
pub mod log;

Expand Down
97 changes: 24 additions & 73 deletions src/log.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::error::{Cookie as _, Error, Result};
use std::time::SystemTime;

use camino::Utf8PathBuf;
use sql_peas::StatementHandle;

pub struct Log {
Expand All @@ -26,64 +28,6 @@ pub struct Element {
value: String,
}

#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct PublicError(#[from] ErrorRepr);

// FIXME: I'm not 100% about this error setup. If you have gdb, it's redundant. If you don't have gdb, it's nice.
#[derive(Debug, thiserror::Error)]
#[error("kind={kind}; cookies={cookies:?}")]
struct ErrorRepr {
cookies: Vec<&'static str>,
#[source]
kind: ErrorKind,
}

#[derive(Debug, thiserror::Error)]
pub enum ErrorKind {
#[error("SQLite error: {0}")]
Sqlite(#[from] sql_peas::Error),
#[error("Error: {0}")]
Other(&'static str),
#[error("ULID decode error: {0}")]
UlidDecode(#[from] ulid::DecodeError),
}

impl<T> From<T> for PublicError
where
ErrorKind: From<T>,
{
fn from(value: T) -> Self {
ErrorRepr {
cookies: vec![],
kind: ErrorKind::from(value),
}
.into()
}
}

trait Cookie<T> {
fn cookie(self, s: &'static str) -> Result<T>;
}

impl<T, E> Cookie<T> for std::result::Result<T, E>
where
PublicError: From<E>,
{
fn cookie(self, s: &'static str) -> Result<T> {
match self {
Ok(x) => Ok(x),
Err(e) => {
let mut e = PublicError::from(e);
e.0.cookies.push(s);
Err(e)
}
}
}
}

type Result<T> = std::result::Result<T, PublicError>;

impl Iterator for LogCursor<'_> {
type Item = Result<Element>;

Expand All @@ -108,6 +52,21 @@ const LOG_USER_VERSION_I64: i64 = LOG_USER_VERSION as i64;
const LOG_TABLE_NAME: &str = "sql_hummus_0_log";

impl Log {
/// Opens the default user-scope log.
///
/// Only use this as a result of user interaction.
///
/// The default log is shared among everything the user does, so it would get spammed easily if automated processes write to it.
pub fn open_default() -> Result<(Log, Utf8PathBuf)> {
let dirs = directories::ProjectDirs::from("", "ReactorScram", "sql-hummus")
.ok_or(Error::from("directories::ProjectDirs failed"))?;
let dir = dirs.data_local_dir();
std::fs::create_dir_all(dir).cookie("create_dir_all() for default data dir failed")?;
let path = dir.join("default-log.db").try_into()?;
let log = Log::new(&path)?;
Ok((log, path))
}

pub fn new<P: AsRef<std::path::Path>>(p: P) -> Result<Self> {
let mut inner = sql_peas::Connection::open(p)?;

Expand All @@ -120,16 +79,14 @@ impl Log {
let stmt = inner.borrow_statement(handle)?;
// FIXME: de-dupe single row read up into SQLite
let mut rows = stmt.iter();
let row = rows.next().ok_or(ErrorKind::Other(
"Expected one row from PRAGMA user_version",
))??;
let row = rows
.next()
.ok_or("Expected one row from PRAGMA user_version")??;
let needs_setup = match row.read(0) {
0 => true,
LOG_USER_VERSION_I64 => false,
_ => {
return Err(ErrorKind::Other(
"PRAGMA user_version looks like a non-KV file",
))?;
return Err("PRAGMA user_version looks like a non-KV file")?;
}
};

Expand Down Expand Up @@ -176,9 +133,7 @@ impl Log {
return Ok(None);
}
if index != stmt.read(0)? {
Err(ErrorKind::Other(
"Log::get didn't get the same index back from the DB",
))?;
Err("Log::get didn't get the same index back from the DB")?;
}
let ulid = ulid::Ulid::from_string(&stmt.read::<String, _>(1)?)?;
let value = stmt.read(2)?;
Expand All @@ -199,15 +154,11 @@ impl Log {
stmt.bind((1, ts)).cookie("7RMQCY4N")?;
stmt.bind((2, value)).cookie("AGRZOZBD")?;
if stmt.next().cookie("WEMNOGO7")? != sql_peas::State::Row {
Err(ErrorKind::Other(
"We didn't get State::Row during Log::insert",
))?;
Err("We didn't get State::Row during Log::insert")?;
}
let index = stmt.read(0).cookie("MEJYJBDW")?;
if stmt.next().cookie("QOV7GD47")? != sql_peas::State::Done {
Err(ErrorKind::Other(
"We didn't get sql_peas::State::Done during Log::insert",
))?;
Err("We didn't get State::Done during Log::insert")?;
}
Ok(index)
}
Expand Down
Loading