use a binary ledger: newline-separated, newline-escaped entries instead of json

This commit is contained in:
Rob Walker 2018-07-28 10:45:10 -07:00 committed by Grimes
parent 0c33c9e0d7
commit 552d4adff5
1 changed files with 66 additions and 26 deletions

View File

@ -3,9 +3,9 @@
//! stdout, and then sends the Entry to its output channel.
use bank::Bank;
use bincode;
use entry::Entry;
use serde_json;
use std::io::{self, BufRead, Cursor, Error, ErrorKind, Write};
use std::io::{self, BufRead, Error, ErrorKind, Write};
pub struct EntryWriter<'a, W> {
bank: &'a Bank,
@ -19,8 +19,25 @@ impl<'a, W: Write> EntryWriter<'a, W> {
}
fn write_entry(writer: &mut W, entry: &Entry) -> io::Result<()> {
let serialized = serde_json::to_string(entry).unwrap();
writeln!(writer, "{}", serialized)?;
let raw =
bincode::serialize(&entry).map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
// TODO quote new lines...
trace!("write_entry raw = {:?}", raw);
let mut escaped = Vec::with_capacity(raw.len());
for mut c in raw {
if c == b'\\' || c == b'\n' {
trace!("escaping '{}' to {}", c, c + b'\\');
escaped.push(b'\\');
c += b'\\';
}
escaped.push(c);
}
trace!("write_entry escaped = {:?}", escaped);
writer.write(&escaped[..])?;
writer.write(&[b'\n'])?;
writer.flush()
}
@ -50,28 +67,34 @@ impl<'a, W: Write> EntryWriter<'a, W> {
}
}
/// Parse a string containing an Entry.
pub fn read_entry(s: &str) -> io::Result<Entry> {
serde_json::from_str(s).map_err(|e| Error::new(ErrorKind::Other, e.to_string()))
/// Parse an escaped byte slice containing an Entry.
fn read_entry(escaped: &[u8]) -> io::Result<Entry> {
let mut raw = Vec::with_capacity(escaped.len());
trace!("read_entry escaped = {:?}", escaped);
let mut back = false;
for pc in escaped {
let mut c = *pc;
if back {
trace!("de-escaping '{}' to '{}'", c, c - b'\\');
back = false;
c -= b'\\';
} else if c == b'\\' {
back = true;
continue;
}
raw.push(c);
}
trace!("read_entry raw = {:?}", raw);
bincode::deserialize(&raw[..]).map_err(|e| Error::new(ErrorKind::Other, e.to_string()))
}
/// Return an iterator for all the entries in the given file.
pub fn read_entries<R: BufRead>(reader: R) -> impl Iterator<Item = io::Result<Entry>> {
reader.lines().map(|s| read_entry(&s?))
}
/// Same as read_entries() but returning a vector. Handy for debugging short logs.
pub fn read_entries_to_vec<R: BufRead>(reader: R) -> io::Result<Vec<Entry>> {
let mut result = vec![];
for x in read_entries(reader) {
result.push(x?);
}
Ok(result)
}
/// Same as read_entries() but parsing a string and returning a vector.
pub fn read_entries_from_str(s: &str) -> io::Result<Vec<Entry>> {
read_entries_to_vec(Cursor::new(s))
reader.split(b'\n').map(|s| read_entry(&s?))
}
#[cfg(test)]
@ -81,7 +104,7 @@ mod tests {
use mint::Mint;
use packet::BLOB_DATA_SIZE;
use signature::{KeyPair, KeyPairUtil};
use std::str;
use std::io::Cursor;
use transaction::Transaction;
#[test]
@ -114,12 +137,29 @@ mod tests {
assert_eq!(bank.last_id(), entries[1].id);
}
/// Same as read_entries() but parsing a buffer and returning a vector.
fn read_entries_from_buf(s: &[u8]) -> io::Result<Vec<Entry>> {
let mut result = vec![];
let reader = Cursor::new(s);
for x in read_entries(reader) {
trace!("entry... {:?}", x);
result.push(x?);
}
Ok(result)
}
#[test]
fn test_read_entries_from_str() {
let mint = Mint::new(1);
fn test_read_entries_from_buf() {
let mint = Mint::new(b'\\' as i64); // guarantee we have a backslash in the buffer
let mut buf = vec![];
EntryWriter::write_entries(&mut buf, mint.create_entries()).unwrap();
let entries = read_entries_from_str(str::from_utf8(&buf).unwrap()).unwrap();
let entries = read_entries_from_buf(&buf).unwrap();
assert_eq!(entries, mint.create_entries());
let mint = Mint::new(b'\n' as i64); // guarantee we have a newline in the buffer
let mut buf = vec![];
EntryWriter::write_entries(&mut buf, mint.create_entries()).unwrap();
let entries = read_entries_from_buf(&buf).unwrap();
assert_eq!(entries, mint.create_entries());
}
}