Skip to content

Commit

Permalink
Merge pull request #51 from CosmWasm/map-fixed-length-keys
Browse files Browse the repository at this point in the history
Map key redesign
  • Loading branch information
uint authored Sep 10, 2024
2 parents 099aa3e + 47e7b83 commit a0a2863
Show file tree
Hide file tree
Showing 9 changed files with 468 additions and 65 deletions.
4 changes: 2 additions & 2 deletions packages/cw-storey/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
//! This crate provides
//! - a [*CosmWasm*] storage backend for use with [`storey`] collections,
//! - a [*MessagePack*] encoding integration to be used for serializing and deserializing
//! values, and
//! values, and
//! - a set of container re-exports that remove the need to manually specify the
//! encoding, instead relying on the default [*MessagePack*] encoding.
//! encoding, instead relying on the default [*MessagePack*] encoding.
//!
//! [*CosmWasm*]: https://github.com/CosmWasm/cosmwasm
//! [*MessagePack*]: https://msgpack.org/
Expand Down
3 changes: 2 additions & 1 deletion packages/storey/src/containers/column.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::encoding::{DecodableWith, EncodableWith};
use crate::storage::{IterableStorage, StorageBranch};
use crate::storage::{Storage, StorageMut};

use super::{BoundFor, BoundedIterableAccessor, IterableAccessor, Storable};
use super::{BoundFor, BoundedIterableAccessor, IterableAccessor, NonTerminal, Storable};

const META_LAST_IX: &[u8] = &[0];
const META_LEN: &[u8] = &[1];
Expand Down Expand Up @@ -85,6 +85,7 @@ where
E: Encoding,
T: EncodableWith<E> + DecodableWith<E>,
{
type Kind = NonTerminal;
type Accessor<S> = ColumnAccess<E, T, S>;
type Key = u32;
type KeyDecodeError = ColumnKeyDecodeError;
Expand Down
3 changes: 2 additions & 1 deletion packages/storey/src/containers/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::encoding::{DecodableWith, EncodableWith, Encoding};
use crate::storage::StorageBranch;
use crate::storage::{Storage, StorageMut};

use super::Storable;
use super::{Storable, Terminal};

/// A single item in the storage.
///
Expand Down Expand Up @@ -70,6 +70,7 @@ where
E: Encoding,
T: EncodableWith<E> + DecodableWith<E>,
{
type Kind = Terminal;
type Accessor<S> = ItemAccess<E, T, S>;
type Key = ();
type KeyDecodeError = ItemKeyDecodeError;
Expand Down
213 changes: 213 additions & 0 deletions packages/storey/src/containers/map/key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/// A key that can be used with a [`Map`](crate::Map).
pub trait Key {
/// The kind of key, meaning either fixed size or dynamic size.
type Kind: KeyKind;

/// Encode the key into a byte vector.
fn encode(&self) -> Vec<u8>;
}

/// An owned key that can be used with a [`Map`](crate::Map).
pub trait OwnedKey: Key {
/// The error type that can occur when decoding the key.
type Error;

/// Decode the key from a byte slice.
fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error>
where
Self: Sized;
}

impl Key for String {
type Kind = DynamicKey;

fn encode(&self) -> Vec<u8> {
self.as_bytes().to_vec()
}
}

impl Key for Box<str> {
type Kind = DynamicKey;

fn encode(&self) -> Vec<u8> {
self.as_bytes().to_vec()
}
}

impl Key for str {
type Kind = DynamicKey;

fn encode(&self) -> Vec<u8> {
self.as_bytes().to_vec()
}
}

/// An error type representing a failure to decode a UTF-8 string.
#[derive(Debug, PartialEq, Eq, Clone, Copy, thiserror::Error)]
#[error("invalid UTF8")]
pub struct InvalidUtf8;

impl OwnedKey for String {
type Error = InvalidUtf8;

fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error>
where
Self: Sized,
{
std::str::from_utf8(bytes)
.map(String::from)
.map_err(|_| InvalidUtf8)
}
}

impl OwnedKey for Box<str> {
type Error = InvalidUtf8;

fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error>
where
Self: Sized,
{
std::str::from_utf8(bytes)
.map(Box::from)
.map_err(|_| InvalidUtf8)
}
}

impl Key for Vec<u8> {
type Kind = DynamicKey;

fn encode(&self) -> Vec<u8> {
self.clone()
}
}

impl Key for Box<[u8]> {
type Kind = DynamicKey;

fn encode(&self) -> Vec<u8> {
self.to_vec()
}
}

impl Key for [u8] {
type Kind = DynamicKey;

fn encode(&self) -> Vec<u8> {
self.to_vec()
}
}

impl<const N: usize> Key for [u8; N] {
type Kind = FixedSizeKey<N>;

fn encode(&self) -> Vec<u8> {
self.to_vec()
}
}

impl OwnedKey for Vec<u8> {
type Error = ();

fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error>
where
Self: Sized,
{
Ok(bytes.to_vec())
}
}

impl OwnedKey for Box<[u8]> {
type Error = ();

fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error>
where
Self: Sized,
{
Ok(bytes.to_vec().into_boxed_slice())
}
}

/// An error type for decoding arrays.
pub enum ArrayDecodeError {
InvalidLength,
}

impl<const N: usize> OwnedKey for [u8; N] {
type Error = ArrayDecodeError;

fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error>
where
Self: Sized,
{
if bytes.len() != N {
return Err(ArrayDecodeError::InvalidLength);
}

let mut buf = [0; N];
buf.copy_from_slice(bytes);
Ok(buf)
}
}

/// A trait specifying the kind of key.
///
/// There are two kinds of keys: fixed-size keys and dynamic keys, which are
/// represented by the [`FixedSizeKey`] and [`DynamicKey`] types, respectively.
///
/// This trait is [sealed](https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits)
/// and cannot be implemented outside of this crate.
pub trait KeyKind: sealed::KeyKindSeal {}

/// A marker type representing a fixed-size key.
pub struct FixedSizeKey<const L: usize>;

/// A marker type representing a dynamic-size key.
pub struct DynamicKey;

impl<const L: usize> KeyKind for FixedSizeKey<L> {}
impl KeyKind for DynamicKey {}

mod sealed {
pub trait KeyKindSeal {}

impl<const L: usize> KeyKindSeal for super::FixedSizeKey<L> {}
impl KeyKindSeal for super::DynamicKey {}
}

/// An error type for decoding numeric keys.
pub enum NumericKeyDecodeError {
InvalidLength,
}

macro_rules! impl_key_for_numeric {
($($t:ty),*) => {
$(
impl Key for $t {
type Kind = FixedSizeKey<{(Self::BITS / 8) as usize}>;

fn encode(&self) -> Vec<u8> {
self.to_be_bytes().to_vec()
}
}

impl OwnedKey for $t {
type Error = NumericKeyDecodeError;

fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error>
where
Self: Sized,
{
if bytes.len() != std::mem::size_of::<Self>() {
return Err(NumericKeyDecodeError::InvalidLength);
}

let mut buf = [0; std::mem::size_of::<Self>()];
buf.copy_from_slice(bytes);
Ok(Self::from_be_bytes(buf))
}
}
)*
};
}

impl_key_for_numeric!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128);
45 changes: 45 additions & 0 deletions packages/storey/src/containers/map/key_encoding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use crate::containers::{NonTerminal, Terminal};

use super::key::{DynamicKey, FixedSizeKey};

/// A trait that specifies the encoding strategy for a key.
///
/// This trait is implemented on tuples of the form `(K, C)` where `K` is the key type (dynamic/fixed)
/// and `C` is the container type (terminal/nonterminal). Once we know these two properties, we can
/// determine the encoding strategy for the key.
///
/// Scenarios:
/// - If the key is dynamic and the container is nonterminal, then the key needs to be
/// length prefixed - otherwise, we would not know where the key ends and the key for the inner
/// container starts.
/// - If the key is dynamic and the container is terminal, then the key is the rest of the string.
/// - If the key is fixed size, then we statically provide the number of bytes to read/write.
pub trait KeyEncodingT {
const BEHAVIOR: KeyEncoding;
}

impl KeyEncodingT for (DynamicKey, NonTerminal) {
const BEHAVIOR: KeyEncoding = KeyEncoding::LenPrefix;
}

impl<const L: usize> KeyEncodingT for (FixedSizeKey<L>, Terminal) {
const BEHAVIOR: KeyEncoding = KeyEncoding::UseRest;
}

impl KeyEncodingT for (DynamicKey, Terminal) {
const BEHAVIOR: KeyEncoding = KeyEncoding::UseRest;
}

impl<const L: usize> KeyEncodingT for (FixedSizeKey<L>, NonTerminal) {
const BEHAVIOR: KeyEncoding = KeyEncoding::UseN(L);
}

/// The encoding strategy for a given key.
pub enum KeyEncoding {
/// The key needs to be length prefixed.
LenPrefix,
/// The key doesn't need to be length prefixed. The rest of the string is the key.
UseRest,
/// The key is of fixed size.
UseN(usize),
}
Loading

0 comments on commit a0a2863

Please sign in to comment.