Implement append-only log store

This commit is contained in:
Kelvin Ly 2025-02-15 08:32:01 -05:00
parent b1bfeab645
commit 7dd74ac36c
7 changed files with 875 additions and 0 deletions

428
shroom-server/Cargo.lock generated Normal file
View File

@ -0,0 +1,428 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anyhow"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "bitflags"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
[[package]]
name = "bytes"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crc"
version = "3.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
dependencies = [
"crc-catalog",
]
[[package]]
name = "crc-catalog"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fixedbitset"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]]
name = "getrandom"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
dependencies = [
"cfg-if",
"libc",
"wasi",
"windows-targets",
]
[[package]]
name = "hashbrown"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "indexmap"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]]
name = "libc"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "log"
version = "0.4.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "multimap"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03"
[[package]]
name = "once_cell"
version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
[[package]]
name = "petgraph"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
dependencies = [
"fixedbitset",
"indexmap",
]
[[package]]
name = "prettyplease"
version = "0.2.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
[[package]]
name = "prost"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5"
dependencies = [
"bytes",
"prost-derive",
]
[[package]]
name = "prost-build"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf"
dependencies = [
"heck",
"itertools",
"log",
"multimap",
"once_cell",
"petgraph",
"prettyplease",
"prost",
"prost-types",
"regex",
"syn",
"tempfile",
]
[[package]]
name = "prost-derive"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
dependencies = [
"anyhow",
"itertools",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "prost-types"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16"
dependencies = [
"prost",
]
[[package]]
name = "quote"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustix"
version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "shroom-server"
version = "0.1.0"
dependencies = [
"crc",
"prost",
"prost-build",
"tempfile",
]
[[package]]
name = "syn"
version = "2.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91"
dependencies = [
"cfg-if",
"fastrand",
"getrandom",
"once_cell",
"rustix",
"windows-sys",
]
[[package]]
name = "unicode-ident"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
[[package]]
name = "wasi"
version = "0.13.3+wasi-0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wit-bindgen-rt"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [
"bitflags",
]

16
shroom-server/Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "shroom-server"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
crc = "3.2.1"
prost = "0.13.5"
[build-dependencies]
prost-build = "0.13.5"
[dev-dependencies]
tempfile = "3.16.0"

7
shroom-server/build.rs Normal file
View File

@ -0,0 +1,7 @@
use std::io::Result;
fn main() -> Result<()> {
println!("cargo:rerun-if-changed=entry.proto");
prost_build::compile_protos(&["entry.proto"], &["."])?;
Ok(())
}

11
shroom-server/entry.proto Normal file
View File

@ -0,0 +1,11 @@
syntax = "proto2";
package internalstore;
message Entry {
optional uint64 timestamp = 1;
repeated float humidity = 2;
optional float temp = 3;
repeated bool humidifer = 4;
optional bool fan = 5;
}

0
shroom-server/src/io.rs Normal file
View File

View File

@ -0,0 +1,8 @@
mod proto {
include!(concat!(env!("OUT_DIR"), "/internalstore.rs"));
}
mod store;
fn main() {
println!("Hello, world!");
}

405
shroom-server/src/store.rs Normal file
View File

@ -0,0 +1,405 @@
use std::fs::{File, OpenOptions};
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use std::path::Path;
use prost::Message;
pub use crate::proto::Entry;
const X25: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC);
// data is stored in append-only logs
// each data entry starts with a checksum (CRC16),
// followed by a one-byte length, and the
// actual entry encoded in protocol buffer
// upon opening a file, all the entries within it are
// read, any corrupted contents at the end truncated/overwritten
pub struct Store<T> {
contents: T,
entries: Vec<Entry>,
start_ts: Option<u64>,
end_ts: Option<u64>,
}
pub type Error = std::io::Error;
pub type Result<T> = std::result::Result<T, Error>;
pub fn open_read_only<P: AsRef<Path>>(p: P) -> Result<Store<Cursor<Vec<u8>>>> {
let data = std::fs::read(p)?;
Ok(Store::new(Cursor::new(data)))
}
pub fn open<P: AsRef<Path>>(p: P) -> Result<Store<File>> {
let mut f = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(p)?;
let _ = f.seek(SeekFrom::Start(0))?;
Ok(Store::new(f))
}
impl<T: Read + Seek> Store<T> {
pub fn new(mut t: T) -> Store<T> {
let mut entries = Vec::new();
let mut start_ts = None;
let mut end_ts = None;
// start of the next unread entry
let mut cur_pos = 0;
let mut data: Vec<u8> = Vec::new();
loop {
let mut crc16 = [0u8; 2];
let mut len = [0u8; 1];
if let Err(_) = t.read_exact(&mut crc16) {
break;
}
if let Err(_) = t.read_exact(&mut len) {
break;
}
data.resize(len[0] as usize, 0);
if let Err(_) = t.read_exact(&mut data) {
break;
}
// CRC check data
let calculated = X25.checksum(&data);
let crc = (crc16[0] as u16) | ((crc16[1] as u16) << 8);
if calculated != crc {
break;
}
// decode data into an entry
if let Ok(entry) = Entry::decode(&data[..]) {
cur_pos = t.stream_position().unwrap();
if start_ts.is_none() {
start_ts = entry.timestamp.clone();
}
if entry.timestamp.is_some() {
end_ts = entry.timestamp.clone();
}
entries.push(entry);
} else {
break;
}
}
let _ = t.seek(SeekFrom::Start(cur_pos)).unwrap();
Store {
contents: t,
entries,
start_ts,
end_ts,
}
}
pub fn lookup_range(&self, start: u64, end: Option<u64>) -> Vec<Entry> {
if let Some(start_ts) = self.start_ts {
if let Some(end_range) = end {
if end_range < start_ts {
return Vec::new();
}
}
}
if let Some(end_ts) = self.end_ts {
if start > end_ts {
return Vec::new();
}
}
let mut ret = Vec::new();
for e in &self.entries {
if let Some(ts) = e.timestamp {
if ts >= start {
if let Some(end_range) = end {
if ts > end_range {
continue;
}
}
ret.push(e.clone());
}
}
}
ret
}
}
impl<T: Write> Store<T> {
pub fn append(&mut self, e: &Entry) -> Result<()> {
let payload = e.encode_to_vec();
let len = payload.len();
let len_u8: [u8; 1] = if len < 256 {
[len as u8]
} else {
return Err(Error::new(std::io::ErrorKind::Other, "payload too large"));
};
// CRC check data
let calculated = X25.checksum(&payload);
let crc: [u8; 2] = [(calculated & 0xff) as u8, ((calculated >> 8) & 0xff) as u8];
self.contents.write(&crc)?;
self.contents.write(&len_u8)?;
self.contents.write(&payload)?;
self.entries.push(e.clone());
if self.start_ts.is_none() {
self.start_ts = e.timestamp;
}
if let Some(ts) = e.timestamp {
self.end_ts = Some(ts);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::Entry;
#[test]
fn append_read() {
let temp_file = tempfile::NamedTempFile::new().unwrap();
let entry = Entry {
timestamp: Some(7),
humidity: vec![0.95],
temp: Some(32.2),
fan: Some(false),
humidifer: vec![false],
};
{
let mut store = super::open(temp_file.path()).unwrap();
store.append(&entry).unwrap();
let entries = store.lookup_range(0, None);
assert_eq!(entries, vec![entry.clone()]);
}
{
let store = super::open_read_only(temp_file.path()).unwrap();
let entries = store.lookup_range(0, None);
assert_eq!(entries, vec![entry.clone()]);
}
}
#[test]
fn append2_read2() {
let temp_file = tempfile::NamedTempFile::new().unwrap();
let entry = Entry {
timestamp: Some(7),
humidity: vec![0.95],
temp: Some(32.2),
fan: Some(false),
humidifer: vec![false],
};
let entry2 = Entry {
timestamp: Some(16),
humidity: vec![0.92],
temp: Some(31.2),
fan: Some(true),
humidifer: vec![false],
};
{
let mut store = super::open(temp_file.path()).unwrap();
store.append(&entry).unwrap();
store.append(&entry2).unwrap();
let entries = store.lookup_range(0, None);
assert_eq!(entries, vec![entry.clone(), entry2.clone()]);
}
{
let store = super::open_read_only(temp_file.path()).unwrap();
let entries = store.lookup_range(0, None);
assert_eq!(entries, vec![entry.clone(), entry2.clone()]);
}
}
#[test]
fn append2_read2_interrupted() {
let temp_file = tempfile::NamedTempFile::new().unwrap();
let entry = Entry {
timestamp: Some(7),
humidity: vec![0.95],
temp: Some(32.2),
fan: Some(false),
humidifer: vec![false],
};
let entry2 = Entry {
timestamp: Some(16),
humidity: vec![0.92],
temp: Some(31.2),
fan: Some(true),
humidifer: vec![false],
};
{
let mut store = super::open(temp_file.path()).unwrap();
store.append(&entry).unwrap();
let entries = store.lookup_range(0, None);
assert_eq!(entries, vec![entry.clone()]);
}
{
let mut store = super::open(temp_file.path()).unwrap();
store.append(&entry2).unwrap();
let entries = store.lookup_range(0, None);
assert_eq!(entries, vec![entry.clone(), entry2.clone()]);
}
{
let store = super::open_read_only(temp_file.path()).unwrap();
let entries = store.lookup_range(0, None);
assert_eq!(entries, vec![entry.clone(), entry2.clone()]);
}
}
#[test]
fn append2_read2_with_corruption() {
let temp_file = tempfile::NamedTempFile::new().unwrap();
let entry = Entry {
timestamp: Some(7),
humidity: vec![0.95],
temp: Some(32.2),
fan: Some(false),
humidifer: vec![false],
};
let entry2 = Entry {
timestamp: Some(16),
humidity: vec![0.92],
temp: Some(31.2),
fan: Some(true),
humidifer: vec![false],
};
let entry3 = Entry {
timestamp: Some(17),
humidity: vec![0.95],
temp: Some(31.4),
fan: Some(false),
humidifer: vec![true],
};
{
use std::io::Write;
let mut store = super::open(temp_file.path()).unwrap();
store.append(&entry).unwrap();
store.append(&entry2).unwrap();
let entries = store.lookup_range(0, None);
assert_eq!(entries, vec![entry.clone(), entry2.clone()]);
store.contents.write(&[45, 34, 44]).unwrap();
store.append(&entry3).unwrap();
let entries = store.lookup_range(0, None);
assert_eq!(entries, vec![entry.clone(), entry2.clone(), entry3.clone()]);
}
{
let store = super::open_read_only(temp_file.path()).unwrap();
let entries = store.lookup_range(0, None);
assert_eq!(entries, vec![entry.clone(), entry2.clone()]);
}
}
#[test]
fn append2_with_corruption_write() {
let temp_file = tempfile::NamedTempFile::new().unwrap();
let entry = Entry {
timestamp: Some(7),
humidity: vec![0.95],
temp: Some(32.2),
fan: Some(false),
humidifer: vec![false],
};
let entry2 = Entry {
timestamp: Some(16),
humidity: vec![0.92],
temp: Some(31.2),
fan: Some(true),
humidifer: vec![false],
};
let entry3 = Entry {
timestamp: Some(17),
humidity: vec![0.95],
temp: Some(31.4),
fan: Some(false),
humidifer: vec![true],
};
{
use std::io::Write;
let mut store = super::open(temp_file.path()).unwrap();
store.append(&entry).unwrap();
store.append(&entry2).unwrap();
store.contents.write(&[45, 34, 44]).unwrap();
let entries = store.lookup_range(0, None);
assert_eq!(entries, vec![entry.clone(), entry2.clone()]);
}
{
let mut store = super::open(temp_file.path()).unwrap();
store.append(&entry3).unwrap();
}
{
let store = super::open_read_only(temp_file.path()).unwrap();
let entries = store.lookup_range(0, None);
assert_eq!(entries, vec![entry.clone(), entry2.clone(), entry3.clone()]);
}
}
#[test]
fn read_different_ranges() {
let temp_file = tempfile::NamedTempFile::new().unwrap();
let entry = Entry {
timestamp: Some(7),
humidity: vec![0.95],
temp: Some(32.2),
fan: Some(false),
humidifer: vec![false],
};
let entry2 = Entry {
timestamp: Some(16),
humidity: vec![0.92],
temp: Some(31.2),
fan: Some(true),
humidifer: vec![false],
};
let entry3 = Entry {
timestamp: Some(17),
humidity: vec![0.95],
temp: Some(31.4),
fan: Some(false),
humidifer: vec![true],
};
let mut store = super::open(temp_file.path()).unwrap();
store.append(&entry).unwrap();
store.append(&entry2).unwrap();
store.append(&entry3).unwrap();
let entries = store.lookup_range(4, Some(8));
assert_eq!(entries, vec![entry.clone()]);
let entries = store.lookup_range(4, Some(16));
assert_eq!(entries, vec![entry.clone(), entry2.clone()]);
let entries = store.lookup_range(8, Some(16));
assert_eq!(entries, vec![entry2.clone()]);
let entries = store.lookup_range(4, Some(17));
assert_eq!(entries, vec![entry.clone(), entry2.clone(), entry3.clone()]);
let entries = store.lookup_range(8, None);
assert_eq!(entries, vec![entry2.clone(), entry3.clone()]);
}
}