#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
pub mod ffi {
#![allow(dead_code)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}
mod db;
mod record;
mod rset;
mod selection_expression;
#[cfg(feature = "arrow")]
pub mod arrow;
pub use db::Db;
pub use record::{FieldRef, Fields, Record, RecordRef};
pub use rset::{OwnedRset, Records, Rset};
pub use selection_expression::SelectionExpression;
use std::ffi::CString;
use std::fmt;
use std::sync::Once;
#[derive(Debug)]
pub struct Error(String);
impl Error {
pub(crate) fn new(msg: impl Into<String>) -> Self {
Error(msg.into())
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for Error {}
pub(crate) fn cstring(s: &str, what: &str) -> Result<CString, Error> {
CString::new(s).map_err(|_| Error::new(format!("{what} contains an interior NUL byte")))
}
pub(crate) fn ensure_init() {
static ONCE: Once = Once::new();
ONCE.call_once(|| unsafe { ffi::rec_init() });
}
#[cfg(test)]
mod tests {
use super::*;
const SAMPLE: &str = "\
%rec: Book
Title: Refactoring
Author: Martin Fowler
Title: Domain-Driven Design
Author: Eric Evans
";
#[test]
fn parse_and_count() {
let mut db = Db::parse_str(SAMPLE).unwrap();
assert_eq!(db.num_rsets(), 1);
let rset = db.rset_by_type("Book").unwrap();
assert_eq!(rset.num_records(), 2);
}
#[test]
fn selection_expression_filters() {
let mut db = Db::parse_str(SAMPLE).unwrap();
let rset = db.rset_by_type("Book").unwrap();
let selection_expression = SelectionExpression::compile("Author = 'Eric Evans'", false).unwrap();
let hits = rset.records().filter(|r| selection_expression.matches(r)).count();
assert_eq!(hits, 1);
}
#[test]
fn set_field_updates_matching() {
let mut db = Db::parse_str(SAMPLE).unwrap();
let selection_expression = SelectionExpression::compile("Author = 'Eric Evans'", false).unwrap();
let rset = db.rset_by_type("Book").unwrap();
let mut updated = 0;
for mut r in rset.records().filter(|r| selection_expression.matches(r)) {
assert!(r.set_field("Author", "Evans, Eric").unwrap());
updated += 1;
}
assert_eq!(updated, 1);
let s = db.to_rec_string().unwrap();
assert!(s.contains("Evans, Eric"));
assert!(!s.contains("Author: Eric Evans"));
}
#[test]
fn remove_matching_drops_records() {
let mut db = Db::parse_str(SAMPLE).unwrap();
let selection_expression = SelectionExpression::compile("Author = 'Martin Fowler'", false).unwrap();
let removed = {
let mut rset = db.rset_by_type("Book").unwrap();
rset.remove_matching(|r| selection_expression.matches(r))
};
assert_eq!(removed, 1);
assert_eq!(db.rset_by_type("Book").unwrap().num_records(), 1);
}
#[test]
fn build_db_from_scratch() {
let mut db = Db::new();
let mut rset = OwnedRset::new();
let mut desc = Record::new();
desc.append_field("%rec", "Book").unwrap();
desc.append_field("%type", "Year int").unwrap();
desc.append_field("%mandatory", "Title").unwrap();
rset.set_descriptor(desc);
let mut r = Record::new();
r.append_field("Title", "TDD").unwrap();
r.append_field("Year", "2002").unwrap();
rset.append_record(r).unwrap();
db.append_rset(rset).unwrap();
let text = db.to_rec_string().unwrap();
assert!(text.contains("%rec: Book"));
assert!(text.contains("%type: Year int"));
assert!(text.contains("%mandatory: Title"));
assert!(text.contains("Title: TDD"));
let mut db2 = Db::parse_str(&text).unwrap();
let rset2 = db2.rset_by_type("Book").unwrap();
assert_eq!(rset2.num_records(), 1);
}
#[test]
fn append_round_trip() {
let mut db = Db::parse_str(SAMPLE).unwrap();
{
let mut rset = db.rset_by_type("Book").unwrap();
let mut rec = Record::new();
rec.append_field("Title", "TDD").unwrap();
rec.append_field("Author", "Kent Beck").unwrap();
rset.append_record(rec).unwrap();
}
let serialized = db.to_rec_string().unwrap();
assert!(serialized.contains("Kent Beck"));
let mut db2 = Db::parse_str(&serialized).unwrap();
assert_eq!(db2.rset_by_type("Book").unwrap().num_records(), 3);
}
}