#![doc = include_str!("../README.md")]
pub mod maps;
pub mod accessor;
use accessor::{Accessor, TsvText};
use maps::{MemoryMap, SafeMemoryMap};
pub struct Table<Map: MemoryMap, Access : Accessor> {
accessor: Access,
map: Map,
}
pub struct Line<'a, Map: MemoryMap, Access : Accessor> {
textdb: &'a Table<Map, Access>,
line: &'a [u8],
}
impl Table<SafeMemoryMap, TsvText> {
pub fn text_tsv_from_string(text: String) -> Self {
let accessor = accessor::TsvText::<0>::default();
let map = maps::SafeMemoryMap::from_string(text);
Table::new(map, accessor)
}
pub fn text_tsv_from_str<S : AsRef<str>>(text: S) -> Self {
let accessor = accessor::TsvText::<0>::default();
let map = maps::SafeMemoryMap::from_str(text.as_ref());
Table::new(map, accessor)
}
}
impl<Access : Accessor, Map: MemoryMap> Table<Map, Access> {
pub fn new(map: Map, accessor: Access) -> Self {
Self {
map,
accessor
}
}
pub fn is_sorted(&self) -> anyhow::Result<bool> {
let bytes = self.map.bytes();
let mut iter = bytes.split(|b| *b == b'\n');
let mut prev_line = iter.next().unwrap_or_default();
for line in iter {
if self.accessor.compare_lines(prev_line, line) == std::cmp::Ordering::Greater {
return Ok(false);
}
prev_line = line;
}
Ok(true)
}
pub fn keys(&self) -> impl Iterator<Item=Result<&str, std::str::Utf8Error>> {
self.map.bytes().split(|b| *b == b'\n').map(|line| {
std::str::from_utf8(self.accessor.key(line))
})
}
pub fn cols(&self, i: usize) -> impl Iterator<Item=Result<&str, std::str::Utf8Error>> {
self.map.bytes().split(|b| *b == b'\n').map(move |line| {
std::str::from_utf8(self.accessor.col(line, i))
})
}
fn find_line_at(bytes: &[u8], min: usize, max: usize, pos: usize) -> (usize, usize, &[u8]) {
let start = bytes[min..pos].iter().rposition(|b| *b == b'\n').map(|p| min + p + 1).unwrap_or(min);
let end = bytes[pos..max].iter().position(|b| *b == b'\n').map(|p| pos + p + 1).unwrap_or(max);
assert!(start >= min);
assert!(end <= max);
assert!(end >= start);
let line_end = if end != 0 && bytes[end-1] == b'\n' { end-1 } else { end };
let line = &bytes[start..line_end];
(start, end, line)
}
pub fn get_matching_lines(&self, key: &Access::KeyType) -> impl Iterator<Item=Line<Map, Access>> {
let bytes = self.map.bytes();
let mut min = 0;
let mut max = bytes.len();
loop {
let mid = min + (max - min) / 2;
let (start, end, line) = Self::find_line_at(bytes, min, max, mid);
#[cfg(test)]
{
let cmp = self.accessor.compare_key(line, key);
let range = std::str::from_utf8(&bytes[min..max]).unwrap();
println!("min={min} mid={mid} max={max} line ={:?} cmp={cmp:?} r={range:?}", std::str::from_utf8(line).unwrap());
}
match self.accessor.compare_key(line, key) {
std::cmp::Ordering::Less => {
assert!(min != end);
min = end;
}
std::cmp::Ordering::Equal => {
let (_start, end, line) = Self::find_line_at(bytes, min, max, min);
#[cfg(test)]
{
assert_eq!(start, min);
let range = std::str::from_utf8(&bytes[min..max]).unwrap();
let cmp = self.accessor.compare_key(line, key);
println!("=min min={min} mid={mid} max={max} line ={:?} cmp={cmp:?} r={range:?}", std::str::from_utf8(line).unwrap());
}
let mut min_is_equal = false;
match self.accessor.compare_key(line, key) {
std::cmp::Ordering::Less => {
assert!(min != end);
min = end;
}
std::cmp::Ordering::Equal => {
min_is_equal = true;
}
std::cmp::Ordering::Greater => {
max = min;
break;
}
}
let (start, _end, line) = Self::find_line_at(bytes, min, max, max-1);
#[cfg(test)]
{
assert_eq!(_end, max);
let range = std::str::from_utf8(&bytes[min..max]).unwrap();
let cmp = self.accessor.compare_key(line, key);
println!("=max min={min} mid={mid} max={max} line ={:?} cmp={cmp:?} r={range:?}", std::str::from_utf8(line).unwrap());
}
match self.accessor.compare_key(line, key) {
std::cmp::Ordering::Less => {
max = min;
break;
}
std::cmp::Ordering::Equal => {
if min_is_equal {
max = if max != 0 && bytes[max-1] == b'\n' { max-1 } else { max };
break;
}
}
std::cmp::Ordering::Greater => {
assert!(max != start);
max = start;
}
}
}
std::cmp::Ordering::Greater => {
assert!(max != start);
max = start;
}
}
}
bytes[min..max].split(|b| *b == b'\n').map(|line| {
Line {
textdb: self,
line,
}
})
}
}
impl<'a, Access : Accessor, Map: MemoryMap> Line<'a, Map, Access> {
pub fn key(&self) -> Result<&str, std::str::Utf8Error> {
std::str::from_utf8(self.textdb.accessor.key(self.line))
}
pub fn col(&self, i: usize) -> Result<&str, std::str::Utf8Error> {
std::str::from_utf8(self.textdb.accessor.col(self.line, i))
}
pub fn line(&self) -> Result<&str, std::str::Utf8Error> {
std::str::from_utf8(self.line)
}
}