♻️ cli, vanth: Implement CLI commands and enhance store

- Added `write`, `get`, `get-all`, `delete`, and `delete-all` subcommands to the CLI.
- Refactored store operations to support raw type handling and removed `Vanth` trait requirement where possible.
- Implemented tracing-based logging with console output for improved observability.
- Improved SQLite store error handling with specific cases for missing tables.
- Added new dependencies including `tracing`, `tracing-subscriber`, `nu-ansi-term`, `sharded-slab`, and `valuable`.
- Removed `.zed/settings.json` editor configuration file.
This commit is contained in:
Markus Scully 2025-08-07 16:11:54 +03:00
parent 9e7979931c
commit a91d92b6ff
Signed by: mascully
GPG key ID: 93CA5814B698101C
10 changed files with 361 additions and 77 deletions

View file

@ -1,11 +0,0 @@
{
"lsp": {
"rust-analyzer": {
"initialization_options": {
"rustfmt": {
"overrideCommand": ["leptosfmt", "--stdin", "--rustfmt"]
}
}
}
}
}

84
Cargo.lock generated
View file

@ -1166,6 +1166,16 @@ version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51"
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]] [[package]]
name = "num-bigint-dig" name = "num-bigint-dig"
version = "0.8.4" version = "0.8.4"
@ -1225,6 +1235,12 @@ version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]] [[package]]
name = "parking" name = "parking"
version = "2.2.1" version = "2.2.1"
@ -1536,6 +1552,15 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]] [[package]]
name = "shlex" name = "shlex"
version = "1.3.0" version = "1.3.0"
@ -1946,6 +1971,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
dependencies = [
"nu-ansi-term",
"sharded-slab",
"smallvec",
"thread_local",
"tracing-core",
"tracing-log",
] ]
[[package]] [[package]]
@ -2028,6 +2079,12 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "valuable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]] [[package]]
name = "vanth" name = "vanth"
version = "0.1.0" version = "0.1.0"
@ -2041,6 +2098,7 @@ dependencies = [
"serde_json", "serde_json",
"sqlx", "sqlx",
"tempfile", "tempfile",
"tracing",
"vanth_derive", "vanth_derive",
] ]
@ -2049,6 +2107,10 @@ name = "vanth_cli"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"serde_json",
"tempfile",
"tracing",
"tracing-subscriber",
"vanth", "vanth",
] ]
@ -2211,6 +2273,28 @@ dependencies = [
"wasite", "wasite",
] ]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.48.0"

View file

@ -23,3 +23,5 @@ rusqlite = { version = "0.32.1", features = ["bundled"] }
tempfile = "3.12.0" tempfile = "3.12.0"
rand_core = "0.6.4" rand_core = "0.6.4"
rand_chacha = { version = "0.3.1", features = ["serde1"] } rand_chacha = { version = "0.3.1", features = ["serde1"] }
tracing = "0.1.41"
tracing-subscriber = "0.3.19"

View file

@ -5,4 +5,10 @@ edition.workspace = true
[dependencies] [dependencies]
clap.workspace = true clap.workspace = true
serde_json.workspace = true
vanth = { path = "../vanth" } vanth = { path = "../vanth" }
tracing.workspace = true
tracing-subscriber.workspace = true
[dev-dependencies]
tempfile = { workspace = true }

View file

@ -1,4 +1,9 @@
use clap::{Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use std::io::{self, Read};
use std::path::PathBuf;
use std::process;
use vanth::{ContentHash, store::Store, Ty};
use vanth::hash as vanth_hash;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(name = "vanth")] #[command(name = "vanth")]
@ -10,38 +15,187 @@ pub struct Cli {
#[derive(Subcommand, Debug)] #[derive(Subcommand, Debug)]
pub enum Commands { pub enum Commands {
#[command(about = "File system operations")] #[command(about = "Write a value to the store")]
Fs { Write(WriteArgs),
#[command(subcommand)] #[command(about = "Get a value from the store")]
command: FsCommands, Get(GetArgs),
}, #[command(about = "Get all values of a type from the store")]
GetAll(GetAllArgs),
#[command(about = "Delete a value by hash from all types")]
Delete(DeleteArgs),
#[command(about = "Delete all values of a type")]
DeleteAll(DeleteAllArgs),
} }
#[derive(Subcommand, Debug)] #[derive(Args, Debug)]
pub enum FsCommands { pub struct WriteArgs {
#[command(about = "Read from path and print JSON to stdout")] #[arg(long, help = "Database file path")]
Open { db: PathBuf,
#[arg(help = "Path to read from")] #[arg(long, help = "Type name, e.g., path::to::Type")]
path: String, ty: String,
}, #[arg(long, help = "JSON value to write, optional (read from stdin if omitted)")]
#[command(about = "Take JSON from stdin and write to path")] value: Option<String>,
Save { }
#[arg(help = "Path to write to")]
path: String, #[derive(Args, Debug)]
}, pub struct GetArgs {
#[arg(long, help = "Database file path")]
db: PathBuf,
#[arg(long, help = "Type name, e.g., path::to::Type")]
ty: String,
#[arg(help = "Content hash as 64-character hex string")]
content_hash: String,
}
#[derive(Args, Debug)]
pub struct GetAllArgs {
#[arg(long, help = "Database file path")]
db: PathBuf,
#[arg(long, help = "Type name, e.g., path::to::Type")]
ty: String,
}
#[derive(Args, Debug)]
pub struct DeleteArgs {
#[arg(long, help = "Database file path")]
db: PathBuf,
#[arg(help = "Content hash as 64-character hex string")]
content_hash: String,
}
#[derive(Args, Debug)]
pub struct DeleteAllArgs {
#[arg(long, help = "Database file path")]
db: PathBuf,
#[arg(long, help = "Type name, e.g., path::to::Type")]
ty: String,
} }
pub fn execute(cli: Cli) { pub fn execute(cli: Cli) {
match cli.command { match cli.command {
Commands::Fs { command } => match command { Commands::Write(args) => handle_write(&args),
FsCommands::Open { path } => { Commands::Get(args) => handle_get(&args),
// TODO: implement fs open functionality Commands::GetAll(args) => handle_get_all(&args),
println!("fs open: {}", path); Commands::Delete(args) => handle_delete(&args),
} Commands::DeleteAll(args) => handle_delete_all(&args),
FsCommands::Save { path } => {
// TODO: implement fs save functionality
println!("fs save: {}", path);
}
},
} }
} }
fn parse_ty(s: &str) -> Ty {
Ty {
path: s.split("::").map(|p| p.to_string()).collect(),
}
}
fn parse_hash(s: &str) -> ContentHash {
if s.len() != 64 {
eprintln!("Hash must be exactly 64 hexadecimal characters");
process::exit(1);
}
let mut hash = [0u8; 32];
for (i, byte) in hash.iter_mut().enumerate() {
let hex_slice = &s[i * 2..i * 2 + 2];
*byte = u8::from_str_radix(hex_slice, 16).unwrap_or_else(|_| {
eprintln!("Invalid hexadecimal in hash: {}", hex_slice);
process::exit(1);
});
}
ContentHash { hash }
}
fn handle_write(args: &WriteArgs) {
let mut store = Store::sqlite_from_path(args.db.clone()).unwrap_or_else(|e| {
eprintln!("Error opening store: {:?}", e);
process::exit(1);
});
let ty = parse_ty(&args.ty);
let mut content = String::new();
if let Some(val) = &args.value {
content = val.clone();
} else {
io::stdin().read_to_string(&mut content).unwrap_or_else(|e| {
eprintln!("Error reading from stdin: {}", e);
process::exit(1);
});
}
let value: serde_json::Value = serde_json::from_str(&content).unwrap_or_else(|e| {
eprintln!("Invalid JSON: {}", e);
process::exit(1);
});
let data = serde_json::to_vec(&value).unwrap();
let content_hash = vanth_hash(&value);
store.write_raw(ty, content_hash, data).unwrap_or_else(|e| {
eprintln!("Error writing to store: {:?}", e);
process::exit(1);
});
}
fn handle_get(args: &GetArgs) {
let mut store = Store::sqlite_from_path(args.db.clone()).unwrap_or_else(|e| {
eprintln!("Error opening store: {:?}", e);
process::exit(1);
});
let ty = parse_ty(&args.ty);
let content_hash = parse_hash(&args.content_hash);
let raw = store.get_from_hash_raw(ty, content_hash).unwrap_or_else(|e| {
eprintln!("Error getting from store: {:?}", e);
process::exit(1);
});
match raw {
Some(data) => {
let output = String::from_utf8(data).unwrap_or_else(|e| {
eprintln!("Invalid UTF-8 in data: {}", e);
process::exit(1);
});
println!("{}", output);
}
None => {
process::exit(1);
}
}
}
fn handle_get_all(args: &GetAllArgs) {
let mut store = Store::sqlite_from_path(args.db.clone()).unwrap_or_else(|e| {
eprintln!("Error opening store: {:?}", e);
process::exit(1);
});
let ty = parse_ty(&args.ty);
let items = store.get_all_of_type_raw(ty).unwrap_or_else(|e| {
eprintln!("Error getting all from store: {:?}", e);
process::exit(1);
});
for (_, data) in items {
let output = String::from_utf8(data).unwrap_or_else(|e| {
eprintln!("Invalid UTF-8 in data: {}", e);
process::exit(1);
});
println!("{}", output);
}
}
fn handle_delete(args: &DeleteArgs) {
let mut store = Store::sqlite_from_path(args.db.clone()).unwrap_or_else(|e| {
eprintln!("Error opening store: {:?}", e);
process::exit(1);
});
let content_hash = parse_hash(&args.content_hash);
}
fn handle_delete_all(args: &DeleteAllArgs) {
let mut store = Store::sqlite_from_path(args.db.clone()).unwrap_or_else(|e| {
eprintln!("Error opening store: {:?}", e);
process::exit(1);
});
let ty = parse_ty(&args.ty);
store.delete_all_raw(ty).unwrap_or_else(|e| {
eprintln!("Error deleting all from store: {:?}", e);
process::exit(1);
});
}

View file

@ -2,9 +2,13 @@ use clap::Parser;
mod cli; mod cli;
use cli::Cli; pub use cli::*;
fn main() { fn main() {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::TRACE)
.init();
let cli = Cli::parse(); let cli = Cli::parse();
cli::execute(cli); cli::execute(cli);
} }

View file

View file

@ -15,7 +15,8 @@ serde_json.workspace = true
blake3.workspace = true blake3.workspace = true
vanth_derive = { path = "../vanth_derive" } vanth_derive = { path = "../vanth_derive" }
sqlx.workspace = true sqlx.workspace = true
rusqlite = { workspace = true } rusqlite.workspace = true
tracing.workspace = true
[dev-dependencies] [dev-dependencies]
tempfile = { workspace = true } tempfile = { workspace = true }

View file

@ -4,6 +4,7 @@ use rusqlite::{Connection, named_params, params};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use serde::{Deserialize, Serialize, de::DeserializeOwned}; use serde::{Deserialize, Serialize, de::DeserializeOwned};
use tracing::trace;
use crate::{ComponentContents, ContentHash, Ty, Vanth, hash}; use crate::{ComponentContents, ContentHash, Ty, Vanth, hash};
@ -17,7 +18,8 @@ type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub enum Error { pub enum Error {
Serializiation(String), Serializiation(String),
Database(String), SqliteTableDoesNotExist { table_name: String },
SqliteUnknown(String),
} }
impl From<serde_json::Error> for Error { impl From<serde_json::Error> for Error {
@ -28,13 +30,36 @@ impl From<serde_json::Error> for Error {
impl From<rusqlite::Error> for Error { impl From<rusqlite::Error> for Error {
fn from(err: rusqlite::Error) -> Self { fn from(err: rusqlite::Error) -> Self {
Error::Database(err.to_string()) if let rusqlite::Error::SqliteFailure(_, Some(ref message)) = err
&& let Some(table_name) = message.strip_prefix("no such table: ")
{
return Error::SqliteTableDoesNotExist {
table_name: table_name.into(),
};
}
Error::SqliteUnknown(err.to_string())
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct StoreParams {
pub create_if_not_exists: bool,
pub read_only: bool,
}
impl Default for StoreParams {
fn default() -> Self {
Self {
create_if_not_exists: true,
read_only: false,
}
} }
} }
impl Store { impl Store {
/// Use an SQLite backend with a database file at the provided path. /// Use an SQLite backend with a database file at the provided path.
pub fn from_path(path: PathBuf) -> Result<Self> { // TODO: Params
pub fn sqlite_from_path(path: PathBuf) -> Result<Self> {
Ok(Self { Ok(Self {
backend: Box::new(Sqlite::new(path)?), backend: Box::new(Sqlite::new(path)?),
}) })
@ -48,7 +73,7 @@ impl Store {
} }
pub fn get_from_hash<T: Vanth + DeserializeOwned>(&mut self, content_hash: ContentHash) -> Result<Option<T>> { pub fn get_from_hash<T: Vanth + DeserializeOwned>(&mut self, content_hash: ContentHash) -> Result<Option<T>> {
let Some(raw) = self.get_from_hash_raw::<T>(content_hash)? else { let Some(raw) = self.get_from_hash_raw(T::ty(), content_hash)? else {
return Ok(None); return Ok(None);
}; };
@ -56,8 +81,8 @@ impl Store {
Ok(Some(deserialized)) Ok(Some(deserialized))
} }
pub fn get_from_hash_raw<T: Vanth>(&mut self, content_hash: ContentHash) -> Result<Option<Vec<u8>>> { pub fn get_from_hash_raw(&mut self, ty: Ty, content_hash: ContentHash) -> Result<Option<Vec<u8>>> {
self.backend.get_from_hash(T::ty(), content_hash) self.backend.get_from_hash(ty, content_hash)
} }
pub fn get_all_of_type<T: Vanth>(&mut self) -> Result<Vec<ComponentContents<T>>> { pub fn get_all_of_type<T: Vanth>(&mut self) -> Result<Vec<ComponentContents<T>>> {
@ -79,8 +104,8 @@ impl Store {
self.backend.write(T::ty(), content_hash, data) self.backend.write(T::ty(), content_hash, data)
} }
pub fn write_raw<T: Vanth>(&mut self, content_hash: ContentHash, content: Vec<u8>) -> Result<()> { pub fn write_raw(&mut self, ty: Ty, content_hash: ContentHash, content: Vec<u8>) -> Result<()> {
self.backend.write(T::ty(), content_hash, content) self.backend.write(ty, content_hash, content)
} }
pub fn delete<T: Vanth>(&mut self, content_hash: ContentHash) -> Result<()> { pub fn delete<T: Vanth>(&mut self, content_hash: ContentHash) -> Result<()> {
@ -90,6 +115,18 @@ impl Store {
pub fn delete_all<T: Vanth>(&mut self) -> Result<()> { pub fn delete_all<T: Vanth>(&mut self) -> Result<()> {
self.backend.delete_all_of_ty(T::ty()) self.backend.delete_all_of_ty(T::ty())
} }
pub fn get_all_of_type_raw(&mut self, ty: Ty) -> Result<Vec<(ContentHash, Vec<u8>)>> {
self.backend.get_all_of_ty(ty)
}
pub fn delete_raw(&mut self, ty: Ty, content_hash: ContentHash) -> Result<()> {
self.backend.delete_by_hash(ty, content_hash)
}
pub fn delete_all_raw(&mut self, ty: Ty) -> Result<()> {
self.backend.delete_all_of_ty(ty)
}
} }
#[derive(Debug, Deserialize, Component, Serialize)] #[derive(Debug, Deserialize, Component, Serialize)]
@ -113,26 +150,18 @@ pub trait Backend: std::fmt::Debug {
/// One table per type. Keys and values are both blobs. /// One table per type. Keys and values are both blobs.
#[derive(Debug)] #[derive(Debug)]
pub struct Sqlite { pub struct Sqlite {
conn: Connection, connection: Connection,
} }
impl Sqlite { impl Sqlite {
pub fn new(path: PathBuf) -> Result<Self> { pub fn new(path: PathBuf) -> Result<Self> {
let conn = Connection::open(path)?; use rusqlite::OpenFlags;
Ok(Self { conn }) // Remove the `SQLITE_OPEN_CREATE` flag because we do not want to create databases if they don't exist.
} let connection = Connection::open_with_flags(
path,
fn ensure_table_exists(&self, ty: &Ty) -> Result<()> { OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_URI | OpenFlags::SQLITE_OPEN_NO_MUTEX,
let table_name = Self::table_name(ty); )?;
let query = format!( Ok(Self { connection })
"CREATE TABLE IF NOT EXISTS \"{}\" (
content_hash BLOB PRIMARY KEY,
content BLOB NOT NULL
)",
table_name
);
self.conn.execute(&query, [])?;
Ok(())
} }
fn table_name(ty: &Ty) -> String { fn table_name(ty: &Ty) -> String {
@ -142,12 +171,11 @@ impl Sqlite {
impl Backend for Sqlite { impl Backend for Sqlite {
fn get_from_hash(&mut self, ty: Ty, content_hash: ContentHash) -> Result<Option<Vec<u8>>> { fn get_from_hash(&mut self, ty: Ty, content_hash: ContentHash) -> Result<Option<Vec<u8>>> {
self.ensure_table_exists(&ty)?;
let table_name = Self::table_name(&ty); let table_name = Self::table_name(&ty);
let query = format!("SELECT content FROM \"{}\" WHERE content_hash = :hash", table_name); let query = format!("SELECT content FROM \"{}\" WHERE content_hash = :hash", table_name);
match self match self
.conn .connection
.query_row(&query, named_params! {":hash": content_hash.hash.as_slice()}, |row| { .query_row(&query, named_params! {":hash": content_hash.hash.as_slice()}, |row| {
row.get::<_, Vec<u8>>(0) row.get::<_, Vec<u8>>(0)
}) { }) {
@ -158,12 +186,18 @@ impl Backend for Sqlite {
} }
fn get_all_of_ty(&mut self, ty: Ty) -> Result<Vec<(ContentHash, Vec<u8>)>> { fn get_all_of_ty(&mut self, ty: Ty) -> Result<Vec<(ContentHash, Vec<u8>)>> {
self.ensure_table_exists(&ty)?; let transaction = self.connection.transaction()?;
let table_name = Self::table_name(&ty); let table_name = Self::table_name(&ty);
let query = format!("SELECT content_hash, content FROM \"{}\"", table_name); let query = format!("SELECT content_hash, content FROM \"{}\"", table_name);
let mut stmt = self.conn.prepare(&query)?; trace!("Reading table {}", table_name);
let rows = stmt.query_map([], |row| {
let mut statement = match transaction.prepare(&query).map_err(Into::into) {
Err(Error::SqliteTableDoesNotExist { .. }) => return Ok(Vec::new()),
other => other?,
};
let rows = statement.query_map([], |row| {
let hash_bytes: Vec<u8> = row.get(0)?; let hash_bytes: Vec<u8> = row.get(0)?;
let content: Vec<u8> = row.get(1)?; let content: Vec<u8> = row.get(1)?;
let mut hash_array = [0u8; 32]; let mut hash_array = [0u8; 32];
@ -175,17 +209,28 @@ impl Backend for Sqlite {
for row in rows { for row in rows {
results.push(row?); results.push(row?);
} }
Ok(results)
drop(statement);
transaction.commit()?;
Ok(Vec::new())
} }
fn write(&mut self, ty: Ty, content_hash: ContentHash, content: Vec<u8>) -> Result<()> { fn write(&mut self, ty: Ty, content_hash: ContentHash, content: Vec<u8>) -> Result<()> {
self.ensure_table_exists(&ty)?;
let table_name = Self::table_name(&ty); let table_name = Self::table_name(&ty);
let create_table_query = format!(
"CREATE TABLE IF NOT EXISTS \"{}\" (
content_hash BLOB PRIMARY KEY,
content BLOB NOT NULL
)",
table_name
);
self.connection.execute(&create_table_query, [])?;
let query = format!( let query = format!(
"INSERT OR REPLACE INTO \"{}\" (content_hash, content) VALUES (:hash, :content)", "INSERT OR REPLACE INTO \"{}\" (content_hash, content) VALUES (:hash, :content)",
table_name table_name
); );
self.conn.execute( self.connection.execute(
&query, &query,
named_params! {":hash": content_hash.hash.as_slice(), ":content": content}, named_params! {":hash": content_hash.hash.as_slice(), ":content": content},
)?; )?;
@ -193,10 +238,9 @@ impl Backend for Sqlite {
} }
fn delete_by_hash(&mut self, ty: Ty, content_hash: ContentHash) -> Result<()> { fn delete_by_hash(&mut self, ty: Ty, content_hash: ContentHash) -> Result<()> {
self.ensure_table_exists(&ty)?;
let table_name = Self::table_name(&ty); let table_name = Self::table_name(&ty);
let query = format!("DELETE FROM \"{}\" WHERE content_hash = :hash", table_name); let query = format!("DELETE FROM \"{}\" WHERE content_hash = :hash", table_name);
self.conn self.connection
.execute(&query, named_params! {":hash": content_hash.hash.as_slice()})?; .execute(&query, named_params! {":hash": content_hash.hash.as_slice()})?;
Ok(()) Ok(())
} }
@ -204,7 +248,7 @@ impl Backend for Sqlite {
fn delete_all_of_ty(&mut self, ty: Ty) -> Result<()> { fn delete_all_of_ty(&mut self, ty: Ty) -> Result<()> {
let table_name = Self::table_name(&ty); let table_name = Self::table_name(&ty);
let query = format!("DROP TABLE IF EXISTS \"{}\"", table_name); let query = format!("DROP TABLE IF EXISTS \"{}\"", table_name);
self.conn.execute(&query, [])?; self.connection.execute(&query, [])?;
Ok(()) Ok(())
} }
} }

View file

@ -17,7 +17,7 @@ struct Bar {
fn test_sqlite_store() { fn test_sqlite_store() {
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
let path = dir.path().join("test.db"); let path = dir.path().join("test.db");
let mut store = Store::from_path(path.clone()).unwrap(); let mut store = Store::sqlite_from_path(path.clone()).unwrap();
let foo_1 = Foo { inner: 1 }; let foo_1 = Foo { inner: 1 };
let foo_2 = Foo { inner: 2 }; let foo_2 = Foo { inner: 2 };