Skip to content

Commit

Permalink
add set_raw fn that allows setting data directly from bytes and add t…
Browse files Browse the repository at this point in the history
…his functionality to the cli
  • Loading branch information
zeerooth committed Nov 13, 2024
1 parent 9204417 commit c6a8436
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 56 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,11 @@ Command-line program to manage yamabiko collections
Usage: ymbk [OPTIONS] <REPO> <COMMAND>
Commands:
get Get data under selected key
get Get data under the selected key
set Set the data under the selected key
indexes Operations on indexes
revert-n-commits
revert-to-commit
revert-n-commits Reverts a specified number of commits back
revert-to-commit Reverts back to the specified commit
help Print this message or the help of the given subcommand(s)
Arguments:
Expand All @@ -136,7 +137,10 @@ Options:
Examples:
[Output the value stored under the key in the specified collection]
ymbk ./collection get key1
ymbk ./collection get key1
[Set a value to be stored under the key in the specified collection (json is used by default, use --format to change that)]
ymbk ./collection set key1 '{"a":2222}'
[Add a numeric index on the field 'number' in the specified collection]
ymbk ./collection indexes add --field addr --kind numeric
Expand Down
3 changes: 3 additions & 0 deletions yamabiko/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,6 @@ impl From<GitErr> for TransactionError {
pub enum KeyError {
NotHashable(GitErr),
}

#[derive(Debug, PartialEq, Eq)]
pub struct InvalidDataFormatError;
113 changes: 72 additions & 41 deletions yamabiko/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use rand::distributions::Alphanumeric;
use rand::prelude::*;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serialization::DataFormat;
use std::{collections::HashMap, path::Path};

use crate::field::Field;
Expand Down Expand Up @@ -195,15 +196,17 @@ impl Collection {
Ok(None)
}

pub fn set_batch<S, I, T>(
fn set_batch_with_indexing_fn<S, I, T, F>(
&self,
items: I,
target: OperationTarget,
mut indexing_fn: F,
) -> Result<(), error::SetObjectError>
where
S: Serialize,
I: IntoIterator<Item = (T, S)>,
T: AsRef<str>,
F: FnMut(&DataFormat, S, &mut HashMap<&crate::index::Index, Option<Field>>) -> String,
{
let indexes = self.index_list();
let repo = &self.repository;
Expand All @@ -212,49 +215,55 @@ impl Collection {
OperationTarget::Transaction(t) => t,
};
let commit = Collection::current_commit(repo, branch)?;
{
let mut root_tree = commit.tree()?;
let mut counter = 0;
for (key, value) in items {
counter += 1;
debug!("set #{} key '{}'", counter, key.as_ref());
let mut index_values = HashMap::new();
for index in indexes.iter() {
index_values.insert(index, None);
}
let blob = repo.blob(
self.data_format
.serialize_with_indexes(value, &mut index_values)
.as_bytes(),
)?;
let hash = Oid::hash_object(ObjectType::Blob, key.as_ref().as_bytes())?;
let trees =
Collection::make_tree(repo, hash.as_bytes(), &root_tree, key.as_ref(), blob)?;
root_tree = repo.find_tree(trees)?;
for (index, value) in index_values {
if let Some(val) = value {
index.create_entry(repo, hash, &val);
} else {
index.delete_entry(repo, hash);
}

let mut root_tree = commit.tree()?;
let mut counter = 0;
for (key, value) in items {
counter += 1;
debug!("set #{} key '{}'", counter, key.as_ref());
let mut index_values = HashMap::new();
for index in indexes.iter() {
index_values.insert(index, None);
}
let blob =
repo.blob(indexing_fn(&self.data_format, value, &mut index_values).as_bytes())?;
let hash = Oid::hash_object(ObjectType::Blob, key.as_ref().as_bytes())?;
let trees =
Collection::make_tree(repo, hash.as_bytes(), &root_tree, key.as_ref(), blob)?;
root_tree = repo.find_tree(trees)?;
for (index, value) in index_values {
if let Some(val) = value {
index.create_entry(repo, hash, &val);
} else {
index.delete_entry(repo, hash);
}
}
let signature = Self::signature();
let commit_msg = format!("set {} items on {}", counter, branch);
let new_commit = repo.commit_create_buffer(
&signature,
&signature,
&commit_msg,
&root_tree,
&[&commit],
)?;
// unwrap: commit_create_buffer should never create an invalid UTF-8
let commit_obj = repo.commit_signed(str::from_utf8(&new_commit).unwrap(), "", None)?;
let mut branch_ref = repo
.find_branch(branch, BranchType::Local)
.map_err(|_| error::SetObjectError::InvalidOperationTarget)?;
branch_ref.get_mut().set_target(commit_obj, &commit_msg)?;
}
let signature = Self::signature();
let commit_msg = format!("set {} items on {}", counter, branch);
let new_commit =
repo.commit_create_buffer(&signature, &signature, &commit_msg, &root_tree, &[&commit])?;
// unwrap: commit_create_buffer should never create an invalid UTF-8
let commit_obj = repo.commit_signed(str::from_utf8(&new_commit).unwrap(), "", None)?;
let mut branch_ref = repo
.find_branch(branch, BranchType::Local)
.map_err(|_| error::SetObjectError::InvalidOperationTarget)?;
branch_ref.get_mut().set_target(commit_obj, &commit_msg)?;

Ok(())
}

pub fn set_batch<S, I, T>(
&self,
items: I,
target: OperationTarget,
) -> Result<(), error::SetObjectError>
where
S: Serialize,
I: IntoIterator<Item = (T, S)>,
T: AsRef<str>,
{
self.set_batch_with_indexing_fn(items, target, DataFormat::serialize_with_indexes)?;
Ok(())
}

Expand All @@ -270,6 +279,28 @@ impl Collection {
self.set_batch([(key, value)], target)
}

pub fn set_batch_raw<'a, I, T>(
&self,
items: I,
target: OperationTarget,
) -> Result<(), error::SetObjectError>
where
I: IntoIterator<Item = (T, &'a [u8])>,
T: AsRef<str>,
{
self.set_batch_with_indexing_fn(items, target, DataFormat::serialize_with_indexes_raw)?;
Ok(())
}

pub fn set_raw(
&self,
key: &str,
value: &[u8],
target: OperationTarget,
) -> Result<(), error::SetObjectError> {
self.set_batch_raw([(key, value)], target)
}

pub fn new_transaction(&self, name: Option<&str>) -> Result<String, git2::Error> {
let repo = &self.repository;
// unwrap: HEAD has to exist and point at something
Expand Down
25 changes: 20 additions & 5 deletions yamabiko/src/serialization.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::{collections::HashMap, str::FromStr};

use crate::error::InvalidDataFormatError;
use crate::field::Field;

#[derive(Debug, Clone, Copy)]
Expand All @@ -10,6 +11,20 @@ pub enum DataFormat {
Yaml,
}

impl FromStr for DataFormat {
type Err = InvalidDataFormatError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let normalized_str = s.to_lowercase();
match normalized_str.as_str() {
"json" => Ok(Self::Json),
#[cfg(feature = "yaml")]
"yaml" => Ok(Self::Yaml),
_ => Err(InvalidDataFormatError),
}
}
}

impl DataFormat {
pub fn extract_indexes_json(
data: &serde_json::Value,
Expand All @@ -33,7 +48,7 @@ impl DataFormat {
) -> String {
match self {
Self::Json => {
let v: serde_json::Value = serde_json::from_slice(&data).unwrap();
let v: serde_json::Value = serde_json::from_slice(data).unwrap();
DataFormat::extract_indexes_json(&v, indexes);
serde_json::to_string_pretty(&v).unwrap()
}
Expand Down Expand Up @@ -70,7 +85,7 @@ impl DataFormat {
) -> bool {
match self {
Self::Json => {
let v: serde_json::Value = serde_json::from_slice(&data).unwrap();
let v: serde_json::Value = serde_json::from_slice(data).unwrap();
match v.get(field) {
Some(res) => value.partial_cmp(res) == Some(comparison),
None => false,
Expand All @@ -86,9 +101,9 @@ impl DataFormat {
T: Deserialize<'a>,
{
match self {
Self::Json => serde_json::from_str(&data).unwrap(),
Self::Json => serde_json::from_str(data).unwrap(),
#[cfg(feature = "yaml")]
Self::Yaml => serde_yaml::from_str(&data).unwrap(),
Self::Yaml => serde_yaml::from_str(data).unwrap(),
}
}
}
26 changes: 20 additions & 6 deletions ymbk/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::path::Path;
use std::{path::Path, str::FromStr};

use clap::{builder::TypedValueParser, Parser, Subcommand};
use git2::Oid;
Expand All @@ -7,7 +7,10 @@ use yamabiko::{serialization::DataFormat, Collection, OperationTarget};
static ADDITIONAL_HELP_TEXT: &str = color_print::cstr!(
r#"<bold><underline>Examples:</underline></bold>
[Output the value stored under the key in the specified collection]
<bold>ymbk ./collection get key1</bold>
<bold>ymbk ./collection get key1</bold>
[Set a value to be stored under the key in the specified collection (json is used by default, use --format to change that)]
<bold>ymbk ./collection set key1 '{"a":2222}'</bold>
[Add a numeric index on the field 'number' in the specified collection]
<bold>ymbk ./collection indexes add --field addr --kind numeric</bold>"#);
Expand All @@ -29,20 +32,24 @@ struct Args {

#[derive(Subcommand, Debug)]
enum Command {
/// Get data under selected key
/// Get data under the selected key
Get { key: String },
/// Set the data under the selected key
Set { key: String, data: String },
/// Operations on indexes
Indexes {
#[command(subcommand)]
command: IndexCommand,
},
/// Reverts a specified number of commits back
RevertNCommits {
number: usize,
#[clap(long, short, default_value = "main")]
target: String,
#[clap(long, action)]
keep_history: bool
},
/// Reverts back to the specified commit
RevertToCommit {
commit: String,
#[clap(long, action)]
Expand All @@ -68,8 +75,9 @@ enum IndexCommand {
fn main() {
let args = Args::parse();
let repo_path = Path::new(&args.repo);
let data_format = DataFormat::from_str(args.format.as_str()).expect("Invalid data format");
let collection =
Collection::initialize(repo_path, DataFormat::Json).expect("Failed to load collection");
Collection::initialize(repo_path, data_format).expect("Failed to load collection");
match args.command {
Command::Get { key } => {
match collection
Expand All @@ -79,7 +87,13 @@ fn main() {
Some(data) => println!("{}", data),
None => eprintln!("Not found"),
}
}
},
Command::Set { key, data } => {
match collection.set_raw(key.as_str(), data.as_bytes(), OperationTarget::Main) {
Ok(_) => println!("ok"),
Err(err) => eprintln!("Error: {:?}", err),
}
},
Command::Indexes { command } => match command {
IndexCommand::List => {
for index in collection.index_list() {
Expand All @@ -103,6 +117,6 @@ fn main() {
}
Err(_err) => eprintln!("Invalid commit Oid format")
}
},
},
}
}

0 comments on commit c6a8436

Please sign in to comment.