Implement IntegerStore.

This allows us to pass primitive integers -- with size checks -- to
store APIs. It's not perfect, but it's safe.
without.crypto
Richard Newman 6 years ago
parent 580c2d13e7
commit 4248f408ca
  1. 2
      Cargo.toml
  2. 164
      src/integer.rs
  3. 16
      src/lib.rs

@ -11,5 +11,7 @@ lmdb = "0.7"
ordered-float = "0.5"
uuid = "0.5"
serde = "1.0"
[dev-dependencies]
tempdir = "0.3"

@ -0,0 +1,164 @@
// Copyright 2018 Mozilla
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
use std::marker::{
PhantomData,
};
use bincode::{
Infinite,
serialize,
};
use lmdb::{
Database,
RoTransaction,
};
use serde::{
Serialize,
};
use error::{
DataError,
StoreError,
};
use value::{
Value,
};
use readwrite::{
Reader,
Store,
Writer,
};
use ::Kista;
pub trait EncodableKey {
fn to_bytes(&self) -> Result<Vec<u8>, DataError>;
}
pub trait PrimitiveInt: EncodableKey {}
impl PrimitiveInt for u32 {}
impl<T> EncodableKey for T where T: Serialize {
fn to_bytes(&self) -> Result<Vec<u8>, DataError> {
serialize(self, Infinite) // TODO: limited key length.
.map_err(|e| e.into())
}
}
struct Key<K> {
bytes: Vec<u8>,
phantom: PhantomData<K>,
}
impl<K> AsRef<[u8]> for Key<K> where K: EncodableKey {
fn as_ref(&self) -> &[u8] {
self.bytes.as_ref()
}
}
impl<K> Key<K> where K: EncodableKey {
fn new(k: K) -> Result<Key<K>, DataError> {
Ok(Key {
bytes: k.to_bytes()?,
phantom: PhantomData,
})
}
}
pub struct IntegerStore<K> where K: PrimitiveInt {
inner: Store<Key<K>>,
}
pub struct IntegerReader<'env, K> where K: PrimitiveInt {
inner: Reader<'env, Key<K>>,
}
impl<'env, K> IntegerReader<'env, K> where K: PrimitiveInt {
pub fn get<'s>(&'s self, k: K) -> Result<Option<Value<'s>>, StoreError> {
self.inner.get(Key::new(k)?)
}
pub fn abort(self) {
self.inner.abort();
}
}
pub struct IntegerWriter<'env, K> where K: PrimitiveInt {
inner: Writer<'env, Key<K>>,
}
impl<'env, K> IntegerWriter<'env, K> where K: PrimitiveInt {
pub fn get<'s>(&'s self, k: K) -> Result<Option<Value<'s>>, StoreError> {
self.inner.get(Key::new(k)?)
}
pub fn put<'s>(&'s mut self, k: K, v: &Value) -> Result<(), StoreError> {
self.inner.put(Key::new(k)?, v)
}
fn abort(self) {
self.inner.abort();
}
}
impl<K> IntegerStore<K> where K: PrimitiveInt {
pub fn new(db: Database) -> IntegerStore<K> {
IntegerStore {
inner: Store::new(db),
}
}
pub fn read<'env>(&self, env: &'env Kista) -> Result<IntegerReader<'env, K>, StoreError> {
Ok(IntegerReader {
inner: self.inner.read(env)?,
})
}
pub fn write<'env>(&mut self, env: &'env Kista) -> Result<IntegerWriter<'env, K>, StoreError> {
Ok(IntegerWriter {
inner: self.inner.write(env)?,
})
}
pub fn get<'env, 'tx>(&self, tx: &'tx RoTransaction<'env>, k: K) -> Result<Option<Value<'tx>>, StoreError> {
let key = Key::new(k)?;
self.inner.get(tx, key)
}
}
#[cfg(test)]
mod test {
extern crate tempdir;
use self::tempdir::TempDir;
use std::fs;
use super::*;
#[test]
fn test_integer_keys() {
let root = TempDir::new("test_integer_keys").expect("tempdir");
fs::create_dir_all(root.path()).expect("dir created");
let k = Kista::new(root.path()).expect("new succeeded");
let mut s: IntegerStore<u32> = k.create_or_open_integer("s").expect("open");
let mut writer = s.write(&k).expect("writer");
writer.put(123, &Value::Str("hello!")).expect("write");
assert_eq!(writer.get(123).expect("read"), Some(Value::Str("hello!")));
}
}

@ -16,6 +16,7 @@
extern crate bincode;
extern crate lmdb;
extern crate ordered_float;
extern crate serde; // So we can specify trait bounds. Everything else is bincode.
extern crate uuid;
use std::os::raw::{
@ -43,12 +44,18 @@ pub use lmdb::{
pub mod value;
pub mod error;
mod readwrite;
mod integer;
pub use error::{
DataError,
StoreError,
};
pub use integer::{
IntegerStore,
PrimitiveInt,
};
pub use value::{
Value,
};
@ -111,6 +118,15 @@ impl Kista {
self.create_or_open_with_flags(name, flags)
}
pub fn create_or_open_integer<'s, T, K>(&self, name: T) -> Result<IntegerStore<K>, StoreError>
where T: Into<Option<&'s str>>,
K: PrimitiveInt {
let mut flags = DatabaseFlags::empty();
flags.toggle(lmdb::INTEGER_KEY);
let db = self.env.create_db(name.into(), flags).map_err(StoreError::LmdbError)?;
Ok(IntegerStore::new(db))
}
pub fn create_or_open_with_flags<'s, T, K>(&self, name: T, flags: DatabaseFlags) -> Result<Store<K>, StoreError>
where T: Into<Option<&'s str>>,
K: AsRef<[u8]> {

Loading…
Cancel
Save