Page MenuHomePhabricator

namespace.rs
No OneTemporary

namespace.rs

// This module implements "namespaces" which are buckets of unique values
// that maps to keys elsewhere in storage.
//
// the `names` tree is a k/v of `name` -> `id` where `id` is a u64 from `generate_id`.
// the `spaces` tree is a k/v of `id` -> `name` where `id` is a u64 the `names` tree id and `name` is a str.
use crate::Result;
use crate::Error;
use sled::Transactional;
#[derive(Debug, Clone)]
pub struct NamespaceStore {
pub(crate) names: sled::Tree,
pub(crate) spaces: sled::Tree,
}
impl NamespaceStore {
pub fn open(db: &sled::Db) -> Result<Self> {
Ok(NamespaceStore {
names: db.open_tree("namespaces/1/names")?,
spaces: db.open_tree("namespaces/1/spaces")?,
})
}
/// Reserve a key in a namespace within an existing transaction context
pub fn reserve_in_transaction(
&self,
names: &sled::transaction::TransactionalTree,
spaces: &sled::transaction::TransactionalTree,
namespace: &str,
key: &str,
value: &str,
) -> sled::transaction::ConflictableTransactionResult<bool, ()> {
// Get ID of the namespace from `names`
let namespace_id = match names.get(namespace)? {
Some(id_bytes) => {
let id_array: [u8; 8] = id_bytes.as_ref().try_into()
.map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
u64::from_be_bytes(id_array)
}
None => return Err(sled::transaction::ConflictableTransactionError::Abort(())),
};
// Create composite key: namespace_id + key
let mut composite_key = Vec::new();
composite_key.extend_from_slice(&namespace_id.to_be_bytes());
composite_key.extend_from_slice(key.as_bytes());
// Check if key already exists
if spaces.get(&composite_key)?.is_some() {
return Err(sled::transaction::ConflictableTransactionError::Abort(()));
}
// Insert the key-value pair
spaces.insert(composite_key, value.as_bytes())?;
Ok(true)
}
/// Get a value from a namespace within an existing transaction context
pub fn get_in_transaction(
&self,
names: &sled::transaction::TransactionalTree,
spaces: &sled::transaction::TransactionalTree,
namespace: &str,
key: &str,
) -> sled::transaction::ConflictableTransactionResult<Option<String>, ()> {
// Get ID of the namespace from `names`
let namespace_id = match names.get(namespace)? {
Some(id_bytes) => {
let id_array: [u8; 8] = id_bytes.as_ref().try_into()
.map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
u64::from_be_bytes(id_array)
}
None => return Ok(None), // namespace doesn't exist
};
// Create composite key: namespace_id + key
let mut composite_key = Vec::new();
composite_key.extend_from_slice(&namespace_id.to_be_bytes());
composite_key.extend_from_slice(key.as_bytes());
// Get value from spaces
match spaces.get(&composite_key)? {
Some(value_bytes) => {
let value = String::from_utf8(value_bytes.to_vec())
.map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
Ok(Some(value))
}
None => Ok(None),
}
}
/// Remove a key from a namespace within an existing transaction context
pub fn remove_in_transaction(
&self,
names: &sled::transaction::TransactionalTree,
spaces: &sled::transaction::TransactionalTree,
namespace: &str,
key: &str,
) -> sled::transaction::ConflictableTransactionResult<bool, ()> {
// Get ID of the namespace from `names`
let namespace_id = match names.get(namespace)? {
Some(id_bytes) => {
let id_array: [u8; 8] = id_bytes.as_ref().try_into()
.map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
u64::from_be_bytes(id_array)
}
None => return Ok(false), // namespace doesn't exist
};
// Create composite key: namespace_id + key
let mut composite_key = Vec::new();
composite_key.extend_from_slice(&namespace_id.to_be_bytes());
composite_key.extend_from_slice(key.as_bytes());
// Remove the key-value pair
Ok(spaces.remove(composite_key)?.is_some())
}
/// Update a key in a namespace within an existing transaction context
/// Returns true if the key existed and was updated, false if it didn't exist
pub fn update_in_transaction(
&self,
names: &sled::transaction::TransactionalTree,
spaces: &sled::transaction::TransactionalTree,
namespace: &str,
key: &str,
value: &str,
) -> sled::transaction::ConflictableTransactionResult<bool, ()> {
// Get ID of the namespace from `names`
let namespace_id = match names.get(namespace)? {
Some(id_bytes) => {
let id_array: [u8; 8] = id_bytes.as_ref().try_into()
.map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
u64::from_be_bytes(id_array)
}
None => return Ok(false), // namespace doesn't exist
};
// Create composite key: namespace_id + key
let mut composite_key = Vec::new();
composite_key.extend_from_slice(&namespace_id.to_be_bytes());
composite_key.extend_from_slice(key.as_bytes());
// Check if key exists and update it
match spaces.get(&composite_key)? {
Some(_) => {
spaces.insert(composite_key, value.as_bytes())?;
Ok(true)
}
None => Ok(false),
}
}
/// Check if a namespace exists within an existing transaction context
pub fn namespace_exists_in_transaction(
&self,
names: &sled::transaction::TransactionalTree,
namespace: &str,
) -> sled::transaction::ConflictableTransactionResult<bool, ()> {
Ok(names.get(namespace)?.is_some())
}
/// Check if a key exists in a namespace within an existing transaction context
pub fn key_exists_in_transaction(
&self,
names: &sled::transaction::TransactionalTree,
spaces: &sled::transaction::TransactionalTree,
namespace: &str,
key: &str,
) -> sled::transaction::ConflictableTransactionResult<bool, ()> {
// Get ID of the namespace from `names`
let namespace_id = match names.get(namespace)? {
Some(id_bytes) => {
let id_array: [u8; 8] = id_bytes.as_ref().try_into()
.map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
u64::from_be_bytes(id_array)
}
None => return Ok(false), // namespace doesn't exist
};
// Create composite key: namespace_id + key
let mut composite_key = Vec::new();
composite_key.extend_from_slice(&namespace_id.to_be_bytes());
composite_key.extend_from_slice(key.as_bytes());
Ok(spaces.get(&composite_key)?.is_some())
}
// define a namespace.
// inserts a key `namespace` into `names`, where the value is a random u64.
pub fn define(&self, namespace: &str) -> Result<()> {
self.names.transaction(|db| {
match db.get(namespace)? {
Some(_) => Ok(()),
None => {
let id = db.generate_id()?;
db.insert(namespace, &id.to_be_bytes())?;
Ok(())
}
}
})?;
Ok(())
}
pub fn resolve(&self, namespace: &str, key: &str) -> Result<Option<String>> {
let result = (&self.names, &self.spaces).transaction(|(names, spaces)| {
self.get_in_transaction(names, spaces, namespace, key)
}).map_err(|e| match e {
sled::transaction::TransactionError::Abort(()) => Error::UndefinedNamespace(namespace.to_string()),
sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
})?;
Ok(result)
}
pub fn reserve(&self, namespace: &str, key: &str, value: &str) -> Result<bool> {
let result = (&self.names, &self.spaces).transaction(|(names, spaces)| {
self.reserve_in_transaction(names, spaces, namespace, key, value)
}).map_err(|e| match e {
sled::transaction::TransactionError::Abort(()) => Error::NamespaceKeyReserved(namespace.to_string(), key.to_string()),
sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
})?;
Ok(result)
}
pub fn get(&self, namespace: &str, key: &str) -> Result<Option<String>> {
let result = (&self.names, &self.spaces).transaction(|(names, spaces)| {
self.get_in_transaction(names, spaces, namespace, key)
}).map_err(|e| match e {
sled::transaction::TransactionError::Abort(()) => Error::StoreError(sled::Error::Unsupported("Transaction aborted".to_string())),
sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
})?;
Ok(result)
}
pub fn remove(&self, namespace: &str, key: &str) -> Result<bool> {
let result = (&self.names, &self.spaces).transaction(|(names, spaces)| {
self.remove_in_transaction(names, spaces, namespace, key)
}).map_err(|e| match e {
sled::transaction::TransactionError::Abort(()) => Error::StoreError(sled::Error::Unsupported("Transaction aborted".to_string())),
sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
})?;
Ok(result)
}
/// Update a key in a namespace
pub fn update(&self, namespace: &str, key: &str, value: &str) -> Result<bool> {
let result = (&self.names, &self.spaces).transaction(|(names, spaces)| {
self.update_in_transaction(names, spaces, namespace, key, value)
}).map_err(|e| match e {
sled::transaction::TransactionError::Abort(()) => Error::UndefinedNamespace(namespace.to_string()),
sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
})?;
Ok(result)
}
/// Check if a namespace exists
pub fn namespace_exists(&self, namespace: &str) -> Result<bool> {
Ok(self.names.get(namespace)?.is_some())
}
/// Check if a key exists in a namespace
pub fn key_exists(&self, namespace: &str, key: &str) -> Result<bool> {
let result = (&self.names, &self.spaces).transaction(|(names, spaces)| {
self.key_exists_in_transaction(names, spaces, namespace, key)
}).map_err(|e| match e {
sled::transaction::TransactionError::Abort(()) => Error::UndefinedNamespace(namespace.to_string()),
sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
})?;
Ok(result)
}
/// List all defined namespaces
pub fn list_namespaces(&self) -> Result<Vec<String>> {
let mut namespaces = Vec::new();
for result in self.names.iter() {
let (key, _) = result?;
let name = String::from_utf8(key.to_vec())?;
namespaces.push(name);
}
namespaces.sort();
Ok(namespaces)
}
/// List all keys in a namespace
pub fn list_keys(&self, namespace: &str) -> Result<Vec<String>> {
// Get namespace ID first
let namespace_id = match self.names.get(namespace)? {
Some(id_bytes) => {
let id_array: [u8; 8] = id_bytes.as_ref().try_into()
.map_err(|_| Error::StoreError(sled::Error::Unsupported("Invalid namespace ID".to_string())))?;
u64::from_be_bytes(id_array)
}
None => return Ok(Vec::new()),
};
let mut keys = Vec::new();
let prefix = namespace_id.to_be_bytes();
for result in self.spaces.scan_prefix(&prefix) {
let (key, _) = result?;
if key.len() > 8 {
let key_part = &key[8..];
let key_str = String::from_utf8(key_part.to_vec())?;
keys.push(key_str);
}
}
keys.sort();
Ok(keys)
}
/// List all key-value pairs in a namespace
pub fn list_all(&self, namespace: &str) -> Result<Vec<(String, String)>> {
// Get namespace ID first
let namespace_id = match self.names.get(namespace)? {
Some(id_bytes) => {
let id_array: [u8; 8] = id_bytes.as_ref().try_into()
.map_err(|_| Error::StoreError(sled::Error::Unsupported("Invalid namespace ID".to_string())))?;
u64::from_be_bytes(id_array)
}
None => return Ok(Vec::new()),
};
let mut pairs = Vec::new();
let prefix = namespace_id.to_be_bytes();
for result in self.spaces.scan_prefix(&prefix) {
let (key, value) = result?;
if key.len() > 8 {
let key_part = &key[8..];
let key_str = String::from_utf8(key_part.to_vec())?;
let value_str = String::from_utf8(value.to_vec())?;
pairs.push((key_str, value_str));
}
}
pairs.sort_by(|a, b| a.0.cmp(&b.0));
Ok(pairs)
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
fn create_test_store() -> Result<NamespaceStore> {
let temp_dir = tempdir().unwrap();
let db = sled::open(temp_dir.path())?;
NamespaceStore::open(&db)
}
#[test]
fn test_define_namespace() -> Result<()> {
let store = create_test_store()?;
// Define a namespace
store.define("test_namespace")?;
// Defining the same namespace again should not error
store.define("test_namespace")?;
// Check that namespace exists
assert!(store.namespace_exists("test_namespace")?);
assert!(!store.namespace_exists("nonexistent")?);
Ok(())
}
#[test]
fn test_reserve_and_get() -> Result<()> {
let store = create_test_store()?;
// Define namespace first
store.define("test_namespace")?;
// Reserve a key
let success = store.reserve("test_namespace", "test_key", "test_value")?;
assert!(success);
// Get the value
let value = store.get("test_namespace", "test_key")?;
assert_eq!(value, Some("test_value".to_string()));
// Check key exists
assert!(store.key_exists("test_namespace", "test_key")?);
assert!(!store.key_exists("test_namespace", "nonexistent")?);
Ok(())
}
#[test]
fn test_reserve_duplicate_key() -> Result<()> {
let store = create_test_store()?;
// Define namespace first
store.define("test_namespace")?;
// Reserve a key
store.reserve("test_namespace", "test_key", "test_value")?;
// Try to reserve the same key again - should fail
let result = store.reserve("test_namespace", "test_key", "other_value");
assert!(result.is_err());
Ok(())
}
#[test]
fn test_update() -> Result<()> {
let store = create_test_store()?;
// Define namespace first
store.define("test_namespace")?;
// Reserve a key
store.reserve("test_namespace", "test_key", "original_value")?;
// Update the key
let updated = store.update("test_namespace", "test_key", "new_value")?;
assert!(updated);
// Verify the update
let value = store.get("test_namespace", "test_key")?;
assert_eq!(value, Some("new_value".to_string()));
// Try to update a non-existent key
let not_updated = store.update("test_namespace", "nonexistent", "value")?;
assert!(!not_updated);
Ok(())
}
#[test]
fn test_remove() -> Result<()> {
let store = create_test_store()?;
// Define namespace first
store.define("test_namespace")?;
// Reserve a key
store.reserve("test_namespace", "test_key", "test_value")?;
// Remove the key
let removed = store.remove("test_namespace", "test_key")?;
assert!(removed);
// Try to get the removed key
let value = store.get("test_namespace", "test_key")?;
assert_eq!(value, None);
// Check key no longer exists
assert!(!store.key_exists("test_namespace", "test_key")?);
// Try to remove a non-existent key
let removed_again = store.remove("test_namespace", "test_key")?;
assert!(!removed_again);
Ok(())
}
#[test]
fn test_list_operations() -> Result<()> {
let store = create_test_store()?;
// Define namespaces
store.define("namespace1")?;
store.define("namespace2")?;
// Add some data
store.reserve("namespace1", "key1", "value1")?;
store.reserve("namespace1", "key2", "value2")?;
store.reserve("namespace1", "key3", "value3")?;
store.reserve("namespace2", "key1", "different_value")?;
// Test list_namespaces
let namespaces = store.list_namespaces()?;
assert!(namespaces.contains(&"namespace1".to_string()));
assert!(namespaces.contains(&"namespace2".to_string()));
assert_eq!(namespaces.len(), 2);
// Test list_keys
let keys = store.list_keys("namespace1")?;
assert_eq!(keys, vec!["key1", "key2", "key3"]);
let keys2 = store.list_keys("namespace2")?;
assert_eq!(keys2, vec!["key1"]);
// Test list_all
let pairs = store.list_all("namespace1")?;
assert_eq!(pairs, vec![
("key1".to_string(), "value1".to_string()),
("key2".to_string(), "value2".to_string()),
("key3".to_string(), "value3".to_string()),
]);
Ok(())
}
#[test]
fn test_undefined_namespace() -> Result<()> {
let store = create_test_store()?;
// Try to get from undefined namespace
let value = store.get("undefined_namespace", "test_key")?;
assert_eq!(value, None);
// Try to remove from undefined namespace
let removed = store.remove("undefined_namespace", "test_key")?;
assert!(!removed);
// Try to update in undefined namespace
let updated = store.update("undefined_namespace", "test_key", "value")?;
assert!(!updated);
// Check key exists returns false for undefined namespace
assert!(!store.key_exists("undefined_namespace", "test_key")?);
Ok(())
}
#[test]
fn test_transaction_methods() -> Result<()> {
let store = create_test_store()?;
// Define namespace
store.define("test_namespace")?;
// Test transaction methods
let result = (&store.names, &store.spaces).transaction(|(names, spaces)| {
// Reserve in transaction
let reserved = store.reserve_in_transaction(names, spaces, "test_namespace", "key1", "value1")?;
assert_eq!(reserved, true);
// Get in transaction
let value = store.get_in_transaction(names, spaces, "test_namespace", "key1")?;
assert_eq!(value, Some("value1".to_string()));
// Update in transaction
let updated = store.update_in_transaction(names, spaces, "test_namespace", "key1", "new_value")?;
assert_eq!(updated, true);
// Check key exists in transaction
let exists = store.key_exists_in_transaction(names, spaces, "test_namespace", "key1")?;
assert_eq!(exists, true);
// Check key exists after update
let exists_after_update = store.key_exists_in_transaction(names, spaces, "test_namespace", "key1")?;
assert_eq!(exists_after_update, true);
// Check namespace exists in transaction
let ns_exists = store.namespace_exists_in_transaction(names, "test_namespace")?;
assert_eq!(ns_exists, true);
// Remove in transaction
let removed = store.remove_in_transaction(names, spaces, "test_namespace", "key1")?;
assert_eq!(removed, true);
Ok(())
}).map_err(|e| match e {
sled::transaction::TransactionError::Abort(()) => Error::StoreError(sled::Error::Unsupported("Transaction aborted".to_string())),
sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
})?;
Ok(result)
}
#[test]
fn test_transaction_rollback() -> Result<()> {
let store = create_test_store()?;
// Define namespace and add initial data
store.define("test_namespace")?;
store.reserve("test_namespace", "existing_key", "existing_value")?;
// Attempt a transaction that should fail
let result = (&store.names, &store.spaces).transaction(|(names, spaces)| {
// This should succeed
let _reserved = store.reserve_in_transaction(names, spaces, "test_namespace", "new_key", "new_value")?;
// This should fail (key already exists)
let _duplicate = store.reserve_in_transaction(names, spaces, "test_namespace", "existing_key", "different_value")?;
Ok(())
}).map_err(|e| match e {
sled::transaction::TransactionError::Abort(()) => Error::NamespaceKeyReserved("test_namespace".to_string(), "existing_key".to_string()),
sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
});
// Transaction should have failed
assert!(result.is_err());
// Verify rollback - new_key should not exist
assert!(!store.key_exists("test_namespace", "new_key")?);
// Existing key should still have original value
let value = store.get("test_namespace", "existing_key")?;
assert_eq!(value, Some("existing_value".to_string()));
Ok(())
}
#[test]
fn test_multiple_namespaces() -> Result<()> {
let store = create_test_store()?;
// Define multiple namespaces
store.define("users")?;
store.define("products")?;
store.define("orders")?;
// Add data to each namespace
store.reserve("users", "alice", "Alice Smith")?;
store.reserve("users", "bob", "Bob Jones")?;
store.reserve("products", "laptop", "MacBook Pro")?;
store.reserve("products", "mouse", "Magic Mouse")?;
store.reserve("orders", "001", "Alice's laptop order")?;
// Test isolation between namespaces
assert_eq!(store.get("users", "alice")?, Some("Alice Smith".to_string()));
assert_eq!(store.get("products", "laptop")?, Some("MacBook Pro".to_string()));
assert_eq!(store.get("orders", "001")?, Some("Alice's laptop order".to_string()));
// Keys with same name in different namespaces should be separate
store.reserve("users", "test", "user_test")?;
store.reserve("products", "test", "product_test")?;
assert_eq!(store.get("users", "test")?, Some("user_test".to_string()));
assert_eq!(store.get("products", "test")?, Some("product_test".to_string()));
// Verify namespace listing
let namespaces = store.list_namespaces()?;
assert!(namespaces.contains(&"users".to_string()));
assert!(namespaces.contains(&"products".to_string()));
assert!(namespaces.contains(&"orders".to_string()));
Ok(())
}
}

File Metadata

Mime Type
text/plain
Expires
Sun, Jun 8, 7:17 AM (14 h, 58 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
47566
Default Alt Text
namespace.rs (24 KB)

Event Timeline