use std::{
collections::{BTreeMap, HashMap, HashSet}, fmt::{self, Debug, Display}, fs::File, io::{Read, Write}, num::{ParseFloatError, ParseIntError}
};
use fnv::{FnvHashMap, FnvHasher};
use crate::{ezql::Inserts, networking_utilities::*};
use crate::PATH_SEP;
#[derive(Clone, Copy, Hash)]
pub struct KeyString {
inner: [u8;64],
}
impl fmt::Debug for KeyString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("KeyString").field("inner", &self.as_str()).finish()
}
}
impl fmt::Display for KeyString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let text = bytes_to_str(&self.inner).expect("Should always be valid utf8");
write!(f, "{}", text)
}
}
impl Default for KeyString {
fn default() -> Self {
Self { inner: [0;64] }
}
}
impl From<&str> for KeyString {
fn from(s: &str) -> Self {
let mut inner = [0u8;64];
for i in 0..std::cmp::min(s.len(), 63) {
inner[i] = s.as_bytes()[i];
}
KeyString {
inner
}
}
}
impl From<&[u8]> for KeyString {
fn from(s: &[u8]) -> Self {
let mut inner = [0u8;64];
for i in 0..std::cmp::min(s.len(), 63) {
inner[i] = s[i];
}
KeyString {
inner
}
}
}
impl PartialEq for KeyString {
fn eq(&self, other: &Self) -> bool {
if self.as_bytes() == other.as_bytes() {
true
} else {
false
}
}
}
impl Eq for KeyString {}
impl PartialOrd for KeyString {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.as_str().partial_cmp(other.as_str())
}
}
impl Ord for KeyString {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
impl KeyString {
pub fn new() -> Self {
KeyString {
inner: [0u8; 64]
}
}
pub fn len(&self) -> usize {
let mut output = 0;
for byte in self.inner {
match byte {
0 => break,
_ => output += 1,
}
}
output
}
pub fn push(&mut self, s: &str) -> Result<(), StrictError> {
if self.len() + s.len() > 64 {
return Err(StrictError::TooLongKeyString)
}
let mut end_index = 0;
for (index, byte) in self.inner.iter().enumerate() {
if byte == &0 {
end_index = index+1;
}
}
for (index, byte) in s.as_bytes().iter().enumerate() {
self.inner[index+end_index] = *byte;
}
Ok(())
}
pub fn as_str(&self) -> &str {
unsafe { &std::str::from_utf8_unchecked(&self.inner[0..self.len()]) }
}
pub fn as_bytes(&self) -> &[u8] {
&self.inner[0..self.len()]
}
pub fn raw(&self) -> &[u8] {
&self.inner
}
pub fn to_i32(&self) -> i32 {
self.as_str().parse::<i32>().unwrap()
}
pub fn to_f32(&self) -> f32 {
self.as_str().parse::<f32>().unwrap()
}
pub fn to_i32_checked(&self) -> Result<i32, ParseIntError> {
self.as_str().parse::<i32>()
}
pub fn to_f32_checked(&self) -> Result<f32, ParseFloatError> {
self.as_str().parse::<f32>()
}
}
#[derive(Debug)]
pub enum StrictError {
Copy(String),
MoreItemsThanHeader(usize),
FewerItemsThanHeader(usize),
RepeatingHeader(usize, usize),
FloatPrimaryKey,
Empty,
Update(String),
Io(std::io::Error),
MissingType,
TooManyHeaderFields,
WrongType,
Parse(usize),
TooManyPrimaryKeys,
WrongKey,
NonUniquePrimaryKey(usize),
BinaryRead(String),
Query(String),
TooLongKeyString,
}
impl fmt::Display for StrictError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
StrictError::Copy(s) => write!(f, "There has been a Copy error:\n{}", s),
StrictError::MoreItemsThanHeader(n) => write!(
f,
"There are more items in line {} than in the header.\n",
n
),
StrictError::FewerItemsThanHeader(n) => write!(
f,
"There are less items in line {} than in the header.\n",
n
),
StrictError::RepeatingHeader(n, m) => {
write!(f, "Item {} and {} are repeated in the header.\n", n, m)
}
StrictError::FloatPrimaryKey => write!(
f,
"Primary key can't be a floating point number. Must be an integer or string."
),
StrictError::Empty => write!(f, "Don't pass an empty string."),
StrictError::Update(s) => write!(f, "Failed to update because:\n{s}"),
StrictError::Io(e) => write!(f, "Failed to write to disk because: \n--> {e}"),
StrictError::MissingType => write!(f, "Missing type from header"),
StrictError::TooManyHeaderFields => write!(f, "Too many fields in header"),
StrictError::WrongType => write!(f, "Wrong type specified in header"),
StrictError::Parse(i) => write!(f, "Item in line {i} cannot be parsed"),
StrictError::TooManyPrimaryKeys => {
write!(f, "There can only be one primary key column")
}
StrictError::WrongKey => write!(f, "The type of the primary key is wrong"),
StrictError::NonUniquePrimaryKey(i) => write!(
f,
"The primary key at position {i} in the sorted table is repeated"
),
StrictError::BinaryRead(s) => write!(f, "{}", s),
StrictError::Query(s) => write!(f, "Query item {s} is incorrectly formatted"),
StrictError::TooLongKeyString => write!(f, "KeyStrings can only be 64 bytes long"),
}
}
}
impl From<std::io::Error> for StrictError {
fn from(e: std::io::Error) -> Self {
StrictError::Io(e)
}
}
#[derive(PartialEq, PartialOrd, Clone, Debug)]
pub struct Metadata {
pub last_access: u64,
pub times_accessed: u64,
pub created_by: KeyString,
size_of_table: usize,
size_of_row: usize,
}
impl fmt::Display for Metadata {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut printer = String::new();
printer.push_str(&format!("last_access:{}\n", self.last_access));
printer.push_str(&format!("times_accessed:{}\n", self.times_accessed));
printer.push_str(&format!("created_by:{}", self.created_by));
writeln!(f, "{}", printer)
}
}
impl Metadata {
pub fn new(client: &str) -> Metadata {
Metadata {
last_access: get_current_time(),
times_accessed: 0,
created_by: KeyString::from(client),
size_of_table: 0,
size_of_row: 0,
}
}
pub fn from_table(client: &str, header: &Vec<HeaderItem>, table: &BTreeMap<KeyString, DbColumn>) -> Metadata {
let size_of_row = header.iter().fold(0, |acc: usize, x| {
match x.kind {
DbType::Float => acc + 4,
DbType::Int => acc +4,
DbType::Text => acc + 64,
}
});
let size_of_table = table.iter().fold(0, |acc: usize, x| {
match x.1 {
DbColumn::Ints(v) => acc + v.len() * 4,
DbColumn::Floats(v) => acc + v.len() * 4,
DbColumn::Texts(v) => acc + v.len() * 64,
}
});
Metadata {
last_access: get_current_time(),
times_accessed: 0,
created_by: KeyString::from(client),
size_of_table,
size_of_row,
}
}
#[inline]
pub fn update_size(&mut self, header: &Vec<HeaderItem>, table: &BTreeMap<KeyString, DbColumn>) {
self.size_of_row = header.iter().fold(0, |acc: usize, x| {
match x.kind {
DbType::Float => acc + 4,
DbType::Int => acc +4,
DbType::Text => acc + 64,
}
});
self.size_of_table = table.iter().fold(0, |acc: usize, x| {
match x.1 {
DbColumn::Ints(v) => acc + v.len() * 4,
DbColumn::Floats(v) => acc + v.len() * 4,
DbColumn::Texts(v) => acc + v.len() * 64,
}
});
}
pub fn size_of_table(&self) -> usize {
self.size_of_table
}
pub fn size_of_row(&self) -> usize {
self.size_of_row
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum DbType {
Int,
Float,
Text,
}
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub enum DbColumn {
Ints(Vec<i32>),
Texts(Vec<KeyString>),
Floats(Vec<f32>),
}
impl Display for DbColumn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DbColumn::Ints(v) => write!(f, "{:?}", v),
DbColumn::Floats(v) => write!(f, "{:?}", v),
DbColumn::Texts(v) => write!(f, "{:?}", v),
}
}
}
impl DbColumn {
pub fn len(&self) -> usize {
match self {
DbColumn::Floats(v) => v.len(),
DbColumn::Ints(v) => v.len(),
DbColumn::Texts(v) => v.len(),
}
}
pub fn get_i32_col(&self) -> &Vec<i32> {
match self {
DbColumn::Ints(col) => col,
_ => panic!("Never call this function unless you are sure its an i32 column"),
}
}
pub fn get_f32_col(&self) -> &Vec<f32> {
match self {
DbColumn::Floats(col) => col,
_ => panic!("Never call this function unless you are sure its an f32 column"),
}
}
pub fn get_text_col(&self) -> &Vec<KeyString> {
match self {
DbColumn::Texts(col) => col,
_ => panic!("Never call this function unless you are sure its a KeyString column"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct HeaderItem {
pub name: KeyString,
pub kind: DbType,
pub key: TableKey,
}
impl Display for HeaderItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut printer = String::new();
printer.push_str(self.name.as_str());
printer.push(',');
match self.kind {
DbType::Float => printer.push('f'),
DbType::Int => printer.push('i'),
DbType::Text => printer.push('t'),
}
match &self.key {
TableKey::Primary => printer.push_str("-P"),
TableKey::Foreign => printer.push_str("-F"),
TableKey::None => printer.push_str("-N"),
}
write!(f, "{}", printer)
}
}
impl HeaderItem {
pub fn new() -> HeaderItem {
HeaderItem {
name: KeyString::from("default_name"),
kind: DbType::Text,
key: TableKey::None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum TableKey {
Primary,
None,
Foreign,
}
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct EZTable {
pub metadata: Metadata,
pub name: KeyString,
pub header: Vec<HeaderItem>,
pub columns: BTreeMap<KeyString, DbColumn>,
}
impl Display for EZTable {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut printer = String::new();
for item in &self.header {
printer.push_str(&item.to_string());
printer.push(';');
}
printer.pop();
printer.push('\n');
for i in 0..(self.len()) {
for vec in self.columns.values() {
match vec {
DbColumn::Floats(col) => {
printer.push_str(&col[i].to_string());
printer.push(';');
}
DbColumn::Ints(col) => {
printer.push_str(&col[i].to_string());
printer.push(';');
}
DbColumn::Texts(col) => {
printer.push_str(col[i].as_str());
printer.push(';');
}
}
}
printer.pop();
printer.push('\n');
}
printer.pop();
write!(f, "{}", printer)
}
}
impl EZTable {
pub fn blank(header: &Vec<HeaderItem>, name: KeyString, created_by: &str) -> EZTable {
let mut columns = BTreeMap::new();
for head in header {
match head.kind {
DbType::Int => columns.insert(head.name, DbColumn::Ints(Vec::new())),
DbType::Float => columns.insert(head.name, DbColumn::Floats(Vec::new())),
DbType::Text => columns.insert(head.name, DbColumn::Texts(Vec::new())),
};
}
let mut new_header = header.clone();
new_header.sort_by_key(|n| n.name);
EZTable {
metadata: Metadata::new(created_by),
name: name,
header: new_header,
columns,
}
}
pub fn from_csv_string(
s: &str,
table_name: &str,
created_by: &str,
) -> Result<EZTable, StrictError> {
if s.is_empty() {
return Err(StrictError::Empty);
}
let mut header = Vec::new();
let mut primary_key_set = false;
let first_line: Vec<&str> = s
.split('\n')
.next()
.expect("confirmed to exist because of earlier check")
.split(';')
.collect();
for item in first_line {
let temp: Vec<&str> = item.split(',').collect();
let mut header_item = HeaderItem::new();
if temp.is_empty() {
return Err(StrictError::MissingType);
} else if temp.len() == 1 {
header_item.kind = DbType::Text;
} else if temp.len() > 2 {
return Err(StrictError::TooManyHeaderFields);
} else {
header_item.name = KeyString::from(temp[0].trim());
let mut t = temp[1].trim().split('-');
match t.next().unwrap() {
"I" | "Int" | "int" | "i" => header_item.kind = DbType::Int,
"F" | "Float" | "float" | "f" => header_item.kind = DbType::Float,
"T" | "Text" | "text" | "t" => header_item.kind = DbType::Text,
_ => return Err(StrictError::WrongType),
}
match t.next().unwrap() {
"P" => {
if primary_key_set {
return Err(StrictError::TooManyPrimaryKeys);
}
header_item.key = TableKey::Primary;
primary_key_set = true;
}
"N" => header_item.key = TableKey::None,
"F" => header_item.key = TableKey::Foreign,
_ => return Err(StrictError::WrongKey),
}
}
header.push(header_item);
}
if !primary_key_set {
match header[0].kind {
DbType::Int => header[0].key = TableKey::Primary,
DbType::Text => header[0].key = TableKey::Primary,
_ => unreachable!(
"Should already have a primary key or have been rejected for float primary key"
),
};
}
let mut line_index = 0;
let mut data: Vec<Vec<&str>> = Vec::new();
for line in s.lines() {
if line_index == 0 {
line_index += 1;
continue;
}
for (row_index, cell) in line.split(';').enumerate() {
if line_index == 1 {
data.push(Vec::from([cell]));
} else {
data[row_index].push(cell);
}
}
line_index += 1;
}
let mut result = BTreeMap::new();
let mut i = 0;
for col in data {
let db_vec = match header[i].kind {
DbType::Float => {
let mut outvec = Vec::with_capacity(col.len());
let mut index = 0;
for cell in col {
let temp = match cell.parse::<f32>() {
Ok(x) => x,
Err(_) => {
println!("failed to parse float: {:x?}", cell.as_bytes());
return Err(StrictError::Parse(index));
}
};
outvec.push(temp);
index += 1;
}
DbColumn::Floats(outvec)
}
DbType::Int => {
let mut outvec = Vec::with_capacity(col.len());
let mut index = 0;
for cell in col {
let temp = match cell.parse::<i32>() {
Ok(x) => x,
Err(_) => return Err(StrictError::Parse(index)),
};
outvec.push(temp);
index += 1;
}
DbColumn::Ints(outvec)
}
DbType::Text => {
let mut outvec = Vec::with_capacity(col.len());
for cell in col {
outvec.push(KeyString::from(cell));
}
DbColumn::Texts(outvec)
}
};
result.insert(header[i].name.clone(), db_vec);
i += 1;
}
let mut primary_key_index = None;
for item in header.iter() {
if item.key == TableKey::Primary {
primary_key_index = Some(item.name.clone());
}
}
let primary_key_index = match primary_key_index {
Some(x) => x,
None => return Err(StrictError::WrongKey)
};
match &result[&primary_key_index] {
DbColumn::Ints(col) => {
let mut i = 1;
while i < col.len() {
if col[i] == col[i - 1] {
return Err(StrictError::NonUniquePrimaryKey(i));
}
i += 1;
}
}
DbColumn::Texts(col) => {
let mut i = 1;
while i < col.len() {
if col[i] == col[i - 1] {
return Err(StrictError::NonUniquePrimaryKey(i));
}
i += 1;
}
}
DbColumn::Floats(_) => unreachable!("Should never have a float primary key"),
}
header.sort_by_key(|x| x.name.clone());
let mut output = EZTable {
metadata: Metadata::from_table(created_by, &header, &result),
name: KeyString::from(table_name),
header: header,
columns: result,
};
output.sort();
Ok(output)
}
pub fn update_from_csv(&mut self, input_csv: &str) -> Result<(), StrictError> {
let update_table = EZTable::from_csv_string(input_csv, "update", "system")?;
self.update(&update_table)?;
Ok(())
}
pub fn insert(&mut self, inserts: Inserts) -> Result<(), StrictError> {
let mut new_header = Vec::new();
for item in inserts.value_columns {
match self.header.iter().find(|x| x.name == item) {
Some(x) => {
new_header.push(x.clone());
},
None => return Err(StrictError::Query("Input table header does not match target table header".to_owned()))
}
}
let mut csv = String::from(print_sep_list(&new_header, ";"));
csv.push('\n');
csv.push_str(&inserts.new_values);
let mut input_table = EZTable::from_csv_string(&csv, "input", "inserts")?;
if self.header != input_table.header {
return Err(StrictError::Query("Input table header does not match target table header".to_owned()));
}
let mut losers = Vec::new();
match &input_table.columns[&input_table.get_primary_key_col_index()] {
DbColumn::Ints(column) => {
for item in column {
if let Some(index) = self.contains_key_i32(*item) {
losers.push(index);
}
}
},
DbColumn::Texts(column) => {
for item in column {
if let Some(index) = self.contains_key_string(*item) {
losers.push(index);
}
}
},
DbColumn::Floats(_column) => unreachable!("There should never be a float primary key"),
}
input_table.delete_by_indexes(&losers);
self.update(&input_table)?;
Ok(())
}
pub fn contains_key_i32(&self, key: i32) -> Option<usize> {
match &self.columns[&self.get_primary_key_col_index()] {
DbColumn::Ints(column) => {
match column.binary_search(&key) {
Ok(x) => Some(x),
Err(_) => None,
}
},
_ => unreachable!("Already checked the key type earlier")
}
}
pub fn contains_key_string(&self, key: KeyString) -> Option<usize> {
match &self.columns[&self.get_primary_key_col_index()] {
DbColumn::Texts(column) => {
match column.binary_search(&key) {
Ok(x) => Some(x),
Err(_) => None,
}
},
_ => unreachable!("Already checked the key type earlier")
}
}
pub fn byte_size(&self) -> usize {
let mut total = 0;
for item in &self.header {
total += item.name.as_bytes().len();
total += 16; }
for column in self.columns.values() {
match column {
DbColumn::Ints(c) => total += c.len() * 4,
DbColumn::Floats(c) => total += c.len() * 4,
DbColumn::Texts(c) => total += c.len() * 64,
}
}
total
}
pub fn get_primary_key_col_index(&self) -> KeyString {
for item in &self.header {
if item.key == TableKey::Primary {
return item.name.clone();
}
}
unreachable!("There should always be a primary key")
}
pub fn get_primary_key_type(&self) -> DbType {
match self.columns[&self.get_primary_key_col_index()] {
DbColumn::Ints(_) => DbType::Int,
DbColumn::Texts(_) => DbType::Text,
DbColumn::Floats(_) => unreachable!("There should never be a float primary key"),
}
}
pub fn update(&mut self, other_table: &EZTable) -> Result<(), StrictError> {
if self.header != other_table.header {
return Err(StrictError::Update("Headers don't match".to_owned()));
}
let self_primary_key_index = self.get_primary_key_col_index();
let minlen = std::cmp::min(self.columns.len(), other_table.columns.len());
let record_vec: Vec<u8>;
match self.columns.get_mut(&self_primary_key_index).unwrap() {
DbColumn::Ints(col) => match &other_table.columns[&self_primary_key_index] {
DbColumn::Ints(other_col) => {
(*col, record_vec) = merge_sorted(col, other_col);
}
_ => unreachable!("Should always have the same primary key column"),
},
DbColumn::Texts(col) => match &other_table.columns[&self_primary_key_index] {
DbColumn::Texts(other_col) => {
(*col, record_vec) = merge_sorted(col, other_col);
}
_ => unreachable!("Should always have the same primary key column"),
},
DbColumn::Floats(_) => unreachable!("Should never have a float primary key column"),
}
let pk = self.get_primary_key_col_index();
for (key, column) in self.columns.iter_mut() {
if key == &pk {
continue
}
match column {
DbColumn::Ints(col) => match &other_table.columns[key] {
DbColumn::Ints(other_col) => {
*col = merge_in_order(col, other_col, &record_vec);
}
_ => unreachable!("Should always have the same type column"),
},
DbColumn::Texts(col) => match &other_table.columns[key] {
DbColumn::Texts(other_col) => {
*col = merge_in_order(col, other_col, &record_vec);
}
_ => unreachable!("Should always have the same type column"),
},
DbColumn::Floats(col) => match &other_table.columns[key] {
DbColumn::Floats(other_col) => {
*col = merge_in_order(col, other_col, &record_vec);
}
_ => unreachable!("Should always have the same type column"),
},
}
}
self.metadata.update_size(&self.header, &self.columns);
Ok(())
}
pub fn key_index(&self, key: &KeyString) -> Option<usize> {
match &self.columns[&self.get_primary_key_col_index()] {
DbColumn::Ints(column) => {
match column.binary_search(&key.to_i32()) {
Ok(x) => Some(x),
Err(_) => None
}
},
DbColumn::Texts(column) => {
match column.binary_search(key) {
Ok(x) => Some(x),
Err(_) => None
}
},
DbColumn::Floats(_) => unreachable!("The should never be a primary key"),
}
}
pub fn len(&self) -> usize {
match &self.columns.values().next().unwrap() {
DbColumn::Floats(col) => col.len(),
DbColumn::Ints(col) => col.len(),
DbColumn::Texts(col) => col.len(),
}
}
pub fn sort(&mut self) {
let len = self.len();
let mut indexer: Vec<usize> = (0..len).collect();
let primary_index = self.get_primary_key_col_index();
let vec = self.columns.get_mut(&primary_index).unwrap();
match vec {
DbColumn::Ints(col) => {
indexer.sort_unstable_by_key(|&i| col[i]);
}
DbColumn::Texts(col) => {
indexer.sort_unstable_by_key(|&i| &col[i]);
}
DbColumn::Floats(_) => {
unreachable!("There should never be a float primary key");
}
}
for column in self.columns.iter_mut() {
match column.1 {
DbColumn::Floats(col) => {
rearrange_by_index(col, &indexer);
}
DbColumn::Ints(col) => {
rearrange_by_index(col, &indexer);
}
DbColumn::Texts(col) => {
rearrange_by_index(col, &indexer);
}
}
};
}
pub fn get_line(&self, index: usize) -> Result<String, StrictError> {
if index > self.len() {
return Err(StrictError::Query("Index larger than data".to_owned()));
}
let mut output = String::new();
for v in self.columns.values() {
match v {
DbColumn::Floats(col) => {
let item = col[index];
output.push_str(&item.to_string());
}
DbColumn::Ints(col) => {
let item = col[index];
output.push_str(&item.to_string());
}
DbColumn::Texts(col) => {
let item = &col[index];
output.push_str(item.as_str());
}
}
output.push(';');
}
output.pop();
Ok(output)
}
pub fn get_column_int<'a>(&'a self, index: &KeyString) -> Result<&'a Vec<i32>, StrictError> {
match self.columns.get(&index) {
Some(dbcol) => match dbcol {
DbColumn::Ints(column) => Ok(column),
DbColumn::Texts(_) => Err(StrictError::WrongType),
DbColumn::Floats(_) => Err(StrictError::WrongType),
},
None => Err(StrictError::WrongKey)
}
}
pub fn get_column_text<'a>(&'a self, index: &KeyString) -> Result<&'a Vec<KeyString>, StrictError> {
match self.columns.get(&index) {
Some(dbcol) => match dbcol {
DbColumn::Texts(column) => Ok(column),
DbColumn::Ints(_) => Err(StrictError::WrongType),
DbColumn::Floats(_) => Err(StrictError::WrongType),
},
None => Err(StrictError::WrongKey)
}
}
pub fn get_column_float<'a>(&'a self, index: &KeyString) -> Result<&'a Vec<f32>, StrictError> {
match self.columns.get(&index) {
Some(dbcol) => match dbcol {
DbColumn::Floats(column) => Ok(column),
DbColumn::Texts(_) => Err(StrictError::WrongType),
DbColumn::Ints(_) => Err(StrictError::WrongType),
},
None => Err(StrictError::WrongKey)
}
}
pub fn query_list(&self, mut key_list: Vec<&str>) -> Result<String, StrictError> {
let mut printer = String::new();
let primary_index = self.get_primary_key_col_index();
key_list.sort();
let mut indexes = Vec::new();
for item in key_list {
match &self.columns[&primary_index] {
DbColumn::Floats(_) => return Err(StrictError::FloatPrimaryKey),
DbColumn::Ints(col) => {
let key: i32;
match item.parse::<i32>() {
Ok(num) => key = num,
Err(_) => continue,
};
let index: usize;
match col.binary_search(&key) {
Ok(num) => index = num,
Err(_) => continue,
}
indexes.push(index);
}
DbColumn::Texts(col) => {
let index: usize;
match col.binary_search(&KeyString::from(item)) {
Ok(num) => index = num,
Err(_) => continue,
}
indexes.push(index);
}
}
}
for index in indexes {
for v in self.columns.values() {
match v {
DbColumn::Floats(col) => printer.push_str(&col[index].to_string()),
DbColumn::Ints(col) => printer.push_str(&col[index].to_string()),
DbColumn::Texts(col) => printer.push_str(col[index].as_str()),
}
printer.push(';');
}
printer.pop();
printer.push('\n');
}
printer.pop();
Ok(printer)
}
pub fn subtable_from_indexes(&self, indexes: &[usize], new_name: &KeyString) -> EZTable {
let mut result_columns = BTreeMap::new();
for (key, column) in self.columns.iter() {
for index in indexes {
assert!(*index < self.len());
match column {
DbColumn::Ints(column) => {
let mut temp = Vec::with_capacity(indexes.len());
for index in indexes {
temp.push(column[*index].clone());
}
result_columns.insert(key.clone(), DbColumn::Ints(temp));
},
DbColumn::Floats(column) => {
let mut temp = Vec::with_capacity(indexes.len());
for index in indexes {
temp.push(column[*index].clone());
}
result_columns.insert(key.clone(), DbColumn::Floats(temp));
},
DbColumn::Texts(column) => {
let mut temp = Vec::with_capacity(indexes.len());
for index in indexes {
temp.push(column[*index].clone());
}
result_columns.insert(key.clone(), DbColumn::Texts(temp));
},
}
}
}
EZTable {
metadata: Metadata::new("QUERY"),
name: new_name.clone(),
header: self.header.clone(),
columns: result_columns,
}
}
pub fn query_range(&self, range: (&str, &str)) -> Result<String, StrictError> {
let mut printer = String::new();
if range.1 < range.0 {
return Err(StrictError::Empty);
}
if range.0 == range.1 {
return self.query(range.0);
}
let primary_index = self.get_primary_key_col_index();
let mut indexes: [usize; 2] = [0, 0];
match &self.columns[&primary_index] {
DbColumn::Floats(_) => return Err(StrictError::FloatPrimaryKey),
DbColumn::Ints(col) => {
let key = match range.0.parse::<i32>() {
Ok(num) => num,
Err(_) => return Err(StrictError::Empty),
};
let index: usize = col.partition_point(|n| n < &key);
indexes[0] = index;
if range.1 == "" {
indexes[1] = col.len();
} else {
let key2 = match range.1.parse::<i32>() {
Ok(num) => num,
Err(_) => return Err(StrictError::WrongKey),
};
let index: usize = col.partition_point(|n| n < &key2);
if col[index] == key2 {
indexes[1] = index;
} else {
indexes[1] = index - 1;
}
}
}
DbColumn::Texts(col) => {
let index: usize = col.partition_point(|n| n < &KeyString::from(range.0));
indexes[0] = index;
if range.1 == "" {
indexes[1] = col.len();
}
let index: usize = col.partition_point(|n| n < &KeyString::from(range.1));
if col[index] == KeyString::from(range.1) {
indexes[1] = index;
} else {
indexes[1] = index - 1;
}
indexes[1] = index;
}
}
let mut i = indexes[0];
while i <= indexes[1] {
for v in self.columns.values() {
match v {
DbColumn::Floats(col) => printer.push_str(&col[i].to_string()),
DbColumn::Ints(col) => printer.push_str(&col[i].to_string()),
DbColumn::Texts(col) => printer.push_str(col[i].as_str()),
}
printer.push(';');
}
printer.pop();
printer.push('\n');
i += 1;
}
printer.pop();
Ok(printer)
}
pub fn query(&self, query: &str) -> Result<String, StrictError> {
self.query_list(Vec::from([query]))
}
pub fn copy_lines(&self, target: &mut EZTable, line_keys: &DbColumn) -> Result<(), StrictError> {
if target.header != self.header {
return Err(StrictError::Query("Target table header does not match source table header.".to_owned()));
}
let mut temp_table = EZTable {
metadata: Metadata::new("none"),
name: KeyString::from("none"),
header: target.header.clone(),
columns: BTreeMap::new(),
};
let mut temp_tree = BTreeMap::new();
for item in &self.header {
match item.kind {
DbType::Int => temp_tree.insert(item.name.clone(), DbColumn::Ints(Vec::with_capacity(line_keys.len()))),
DbType::Float => temp_tree.insert(item.name.clone(), DbColumn::Floats(Vec::with_capacity(line_keys.len()))),
DbType::Text => temp_tree.insert(item.name.clone(), DbColumn::Texts(Vec::with_capacity(line_keys.len()))),
};
}
temp_table.columns = temp_tree;
let pk_index = self.get_primary_key_col_index();
let mut indexes: Vec<usize> = Vec::with_capacity(line_keys.len());
match line_keys {
DbColumn::Ints(col) => {
let source_col = match &self.columns[&pk_index] {
DbColumn::Ints(col) => col,
_ => return Err(StrictError::Copy("Source and target table do not have matching primary key types".to_owned())),
};
for key in col {
match source_col.binary_search(&key) {
Ok(i) => indexes.push(i),
Err(_) => continue,
}
}
},
DbColumn::Texts(col) => {
let source_col = match &self.columns[&pk_index] {
DbColumn::Texts(col) => col,
_ => return Err(StrictError::Copy("Source and target table do not have matching primary key types".to_owned())),
};
for key in col {
match source_col.binary_search(&key) {
Ok(i) => indexes.push(i),
Err(_) => continue,
}
}
},
_ => unreachable!("Should never have a float primary key."),
}
for (key, column) in self.columns.iter() {
match column {
DbColumn::Floats(col) => {
for index in &indexes {
match temp_table.columns.get_mut(key).unwrap() {
DbColumn::Floats(temp) => temp.push(col[*index]),
_ => unreachable!("Source and target column should always have the same type"),
}
}
},
DbColumn::Ints(col) => {
for index in &indexes {
match temp_table.columns.get_mut(key).unwrap() {
DbColumn::Ints(temp) => temp.push(col[*index]),
_ => unreachable!("Source and target column should always have the same type"),
}
}
},
DbColumn::Texts(col) => {
for index in &indexes {
match temp_table.columns.get_mut(key).unwrap() {
DbColumn::Texts(temp) => temp.push(col[*index].clone()),
_ => unreachable!("Source and target column should always have the same type"),
}
}
},
}
}
target.update(&temp_table)?;
Ok(())
}
pub fn create_subtable(&self, start: usize, stop: usize) -> EZTable {
assert!(stop <= self.len());
assert!(stop >= start);
let mut subtable = BTreeMap::new();
for (key, v) in self.columns.iter() {
match v {
DbColumn::Ints(column) => {
subtable.insert(key.clone(), DbColumn::Ints(column[start..stop].to_vec()));
},
DbColumn::Floats(column) => {
subtable.insert(key.clone(), DbColumn::Floats(column[start..stop].to_vec()));
},
DbColumn::Texts(column) => {
subtable.insert(key.clone(), DbColumn::Texts(column[start..stop].to_vec()));
},
}
}
EZTable {
name: KeyString::from("subtable"),
header: self.header.clone(),
metadata: self.metadata.clone(),
columns: subtable,
}
}
pub fn delete_range(&mut self, range: (&str, &str)) -> Result<(), StrictError> {
if range.1 < range.0 {
return Err(StrictError::Empty);
}
if range.0 == range.1 {
return self.delete(range.0);
}
let primary_index = self.get_primary_key_col_index();
let mut indexes: [usize; 2] = [0, 0];
match &self.columns[&primary_index] {
DbColumn::Floats(_) => return Err(StrictError::FloatPrimaryKey),
DbColumn::Ints(col) => {
let key = match range.0.parse::<i32>() {
Ok(num) => num,
Err(_) => return Err(StrictError::Empty),
};
let index: usize = col.partition_point(|n| *n < key);
indexes[0] = index;
if range.1 == "" {
indexes[1] = col.len();
} else {
let key2 = match range.1.parse::<i32>() {
Ok(num) => num,
Err(_) => return Err(StrictError::WrongKey),
};
let index: usize = col.partition_point(|n| n < &key2);
indexes[1] = index;
}
}
DbColumn::Texts(col) => {
let index: usize = col.partition_point(|n| n < &KeyString::from(range.0));
indexes[0] = index;
if range.1 == "" {
indexes[1] = col.len();
}
let index: usize = col.partition_point(|n| n < &KeyString::from(range.1));
if col[index] == KeyString::from(range.1) {
indexes[1] = index;
} else {
indexes[1] = index - 1;
}
indexes[1] = index;
}
}
for col in self.columns.values_mut() {
match col {
DbColumn::Floats(v) => {
v.drain(indexes[0]..indexes[1]);
}
DbColumn::Ints(v) => {
v.drain(indexes[0]..indexes[1]);
}
DbColumn::Texts(v) => {
v.drain(indexes[0]..indexes[1]);
}
};
}
self.metadata.update_size(&self.header, &self.columns);
Ok(())
}
pub fn delete_list(&mut self, mut key_list: Vec<&str>) -> Result<(), StrictError> {
let primary_index = self.get_primary_key_col_index();
key_list.sort();
let mut indexes = Vec::new();
for item in key_list {
match &self.columns[&primary_index] {
DbColumn::Floats(_) => return Err(StrictError::FloatPrimaryKey),
DbColumn::Ints(col) => {
let key: i32;
match item.parse::<i32>() {
Ok(num) => key = num,
Err(_) => continue,
};
let index: usize;
match col.binary_search(&key) {
Ok(num) => index = num,
Err(_) => continue,
}
indexes.push(index);
}
DbColumn::Texts(col) => {
let index: usize;
match col.binary_search(&KeyString::from(item)) {
Ok(num) => index = num,
Err(_) => continue,
}
indexes.push(index);
}
}
}
let imut = self.columns.values_mut();
for col in imut {
match col {
DbColumn::Floats(v) => {
remove_indices(v, &indexes);
}
DbColumn::Ints(v) => {
remove_indices(v, &indexes);
}
DbColumn::Texts(v) => {
remove_indices(v, &indexes);
}
};
}
self.metadata.update_size(&self.header, &self.columns);
Ok(())
}
pub fn delete_by_vec(&mut self, key_list: DbColumn) -> Result<(), StrictError> {
let primary_index = self.get_primary_key_col_index();
let mut indexes = Vec::with_capacity(key_list.len());
match key_list {
DbColumn::Ints(mut column) => {
column.sort();
for item in column {
match &self.columns[&primary_index] {
DbColumn::Ints(col) => {
let index: usize;
match col.binary_search(&item) {
Ok(num) => index = num,
Err(_) => continue,
}
indexes.push(index);
},
_ => return Err(StrictError::WrongKey),
}
}
},
DbColumn::Texts(mut column) => {
column.sort();
for item in column {
match &self.columns[&primary_index] {
DbColumn::Texts(col) => {
let index: usize;
match col.binary_search(&item) {
Ok(num) => index = num,
Err(_) => continue,
}
indexes.push(index);
},
_ => return Err(StrictError::WrongKey),
}
}
},
DbColumn::Floats(_) => return Err(StrictError::FloatPrimaryKey)
}
let imut = self.columns.values_mut();
for col in imut {
match col {
DbColumn::Floats(v) => {
remove_indices(v, &indexes);
}
DbColumn::Ints(v) => {
remove_indices(v, &indexes);
}
DbColumn::Texts(v) => {
remove_indices(v, &indexes);
}
};
}
self.metadata.update_size(&self.header, &self.columns);
Ok(())
}
pub fn delete_by_indexes(&mut self, indexes: &[usize]) {
let imut = self.columns.values_mut();
for col in imut {
match col {
DbColumn::Floats(v) => {
remove_indices(v, &indexes);
}
DbColumn::Ints(v) => {
remove_indices(v, &indexes);
}
DbColumn::Texts(v) => {
remove_indices(v, &indexes);
}
};
}
}
fn delete(&mut self, query: &str) -> Result<(), StrictError> {
self.delete_list(Vec::from([query]))
}
pub fn clear(&mut self) {
for column in self.columns.values_mut() {
match column {
DbColumn::Ints(col) => {
*col = Vec::with_capacity(0);
},
DbColumn::Floats(col) => {
*col = Vec::with_capacity(0);
},
DbColumn::Texts(col) => {
*col = Vec::with_capacity(0);
},
}
}
}
pub fn add_column(&mut self, name: KeyString, kind: TableKey, column: DbColumn) -> Result<(), StrictError> {
let kind = match column {
DbColumn::Ints(_) => DbType::Int,
DbColumn::Texts(_) => DbType::Text,
DbColumn::Floats(_) => DbType::Float,
};
self.header.push(HeaderItem {
name: name,
key: TableKey::None,
kind: kind,
});
self.columns.insert(name, column);
self.header.sort_by_key(|x| x.name.clone());
Ok(())
}
pub fn left_join(&mut self, right_table: &EZTable, predicate_column: &KeyString) -> Result<(), StrictError> {
match self.columns.keys().find(|x| **x == *predicate_column) {
Some(_) => (),
None => return Err(StrictError::Query("Predicate column is not common".to_owned())),
};
match right_table.columns.keys().find(|x| **x == *predicate_column) {
Some(_) => (),
None => return Err(StrictError::Query("Predicate column is not common".to_owned())),
};
let mut indexes: Vec<usize> = Vec::with_capacity(self.len());
match &self.columns[predicate_column] {
DbColumn::Ints(column) => {
let right_col = right_table.get_column_int(predicate_column)?;
let mut lookup = HashMap::with_capacity(right_col.len());
for item in column.iter() {
if lookup.contains_key(item) {
indexes.push(lookup[item]);
} else {
match right_col.binary_search(item) {
Ok(x) => {
indexes.push(x);
lookup.insert(item, x);
},
Err(_) => todo!("This should only happen if the database is out of sync"),
};
}
}
},
DbColumn::Texts(column) => {
let right_col = right_table.get_column_text(predicate_column)?;
let mut lookup = HashMap::with_capacity(right_col.len());
for item in column.iter() {
if lookup.contains_key(item) {
indexes.push(lookup[item]);
} else {
match right_col.binary_search(item) {
Ok(x) => {
indexes.push(x);
lookup.insert(item, x);
},
Err(_) => todo!("This should only happen if the database is out of sync"),
};
}
}
},
DbColumn::Floats(_column) => unreachable!("Can never have a float key column"),
}
for (place, (name, column)) in right_table.columns.iter().enumerate() {
if name == predicate_column {
continue
}
let table_key = right_table.header[place].key.clone();
match column {
DbColumn::Ints(col) => {
let mut new_column = Vec::with_capacity(indexes.len());
for index in &indexes {
new_column.push(col[*index]);
}
self.add_column(name.clone(), table_key, DbColumn::Ints(new_column))?;
},
DbColumn::Texts(col) => {
let mut new_column = Vec::with_capacity(indexes.len());
for index in &indexes {
new_column.push(col[*index]);
}
self.add_column(name.clone(), table_key, DbColumn::Texts(new_column))?;
},
DbColumn::Floats(col) => {
let mut new_column = Vec::with_capacity(indexes.len());
for index in &indexes {
new_column.push(col[*index]);
}
self.add_column(name.clone(), table_key, DbColumn::Floats(new_column))?;
},
}
}
Ok(())
}
pub fn save_to_disk_csv(&self, path: &str) -> Result<(), StrictError> {
let file_name = &self.name;
let metadata = &self.metadata.to_string();
let table = &self.to_string();
let mut table_file =
match std::fs::File::create(format!("{}raw_tables/{}", path, file_name)) {
Ok(f) => f,
Err(e) => return Err(StrictError::Io(e)),
};
let mut meta_file =
match std::fs::File::create(format!("{}raw_tables-metadata/{}", path, file_name)) {
Ok(f) => f,
Err(e) => return Err(StrictError::Io(e)),
};
match table_file.write_all(table.as_bytes()) {
Ok(_) => (),
Err(e) => println!("Error while writing to disk. Error was:\n{}", e),
};
match meta_file.write_all(metadata.as_bytes()) {
Ok(_) => (),
Err(e) => println!("Error while writing to disk. Error was:\n{}", e),
};
Ok(())
}
pub fn write_to_raw_binary(&self) -> Vec<u8> {
let mut output: Vec<u8> = Vec::with_capacity(self.metadata.size_of_table());
for item in &self.header {
let kind = match item.kind {
DbType::Int => b'i',
DbType::Float => b'f',
DbType::Text => b't',
};
output.push(kind);
let name = item.name.as_bytes();
output.extend_from_slice(name);
match &item.key {
TableKey::Primary => output.push(b'P'),
TableKey::None => output.push(b'N'),
TableKey::Foreign => output.push(b'F'),
}
output.push(b';');
}
output.pop();
output.push(b'\n');
output.extend_from_slice(&(self.len() as u32).to_le_bytes());
output.extend_from_slice(self.metadata.created_by.raw());
output.extend_from_slice(&self.metadata.last_access.to_le_bytes());
output.extend_from_slice(&self.metadata.times_accessed.to_le_bytes());
for column in self.columns.values() {
match &column {
DbColumn::Floats(col) => {
for item in col {
output.extend_from_slice(&item.to_le_bytes());
}
}
&DbColumn::Ints(col) => {
for item in col {
output.extend_from_slice(&item.to_le_bytes());
}
}
DbColumn::Texts(col) => {
for item in col {
output.extend_from_slice(item.raw());
}
}
};
}
output
}
pub fn read_raw_binary(name: &str, binary: &[u8]) -> Result<EZTable, StrictError> {
let mut binter = binary.iter();
let first_newline = binter.position(|n| *n == b'\n').unwrap();
let bin_header = &binary[0..first_newline];
let bin_length = &binary[first_newline + 1..first_newline + 5];
let bin_body = &binary[first_newline + 5..];
let bin_length = u32_from_le_slice(bin_length) as usize;
let mut header = Vec::new();
for item in bin_header.split(|n| n == &b';') {
let kind = match item.first().unwrap() {
b'i' => DbType::Int,
b'f' => DbType::Float,
b't' => DbType::Text,
x => unreachable!("the first byte of a header item should never be {:x?}", x),
};
let key = match item.last().unwrap() {
b'P' => TableKey::Primary,
b'N' => TableKey::None,
b'F' => TableKey::Foreign,
x => unreachable!("The last byte of a header item should never be {:x?}", x),
};
let name = match std::str::from_utf8(&item[1..item.len() - 1]) {
Ok(n) => n,
Err(e) => {
return Err(StrictError::BinaryRead(format!(
"Utf8 error while parsing header item name.\nError body: {}",
e
)))
}
};
let header_item = HeaderItem {
kind: kind,
name: KeyString::from(name),
key: key,
};
header.push(header_item);
}
let mut table = BTreeMap::new();
let metadata_created_by = KeyString::from(&bin_body[0..64]);
let metadata_last_access = u64_from_le_slice(&bin_body[64..72]);
let metadata_times_accessed = u64_from_le_slice(&bin_body[72..80]);
let mut total = 80;
let mut index = 0;
while index < header.len() {
match header[index].kind {
DbType::Int => {
let blob = &bin_body[total..total + (bin_length * 4)];
let v = blob.chunks(4).map(|n| i32_from_le_slice(n)).collect();
total += bin_length * 4;
table.insert(header[index].name.clone(), DbColumn::Ints(v));
index += 1;
}
DbType::Float => {
let blob = &bin_body[total..total + (bin_length * 4)];
let v: Vec<f32> = blob.chunks(4).map(|n| f32_from_le_slice(n)).collect();
total += bin_length * 4;
table.insert(header[index].name.clone(), DbColumn::Floats(v));
index += 1;
}
DbType::Text => {
let blob = &bin_body[total..total + (bin_length * 64)];
let v: Vec<KeyString> = blob.chunks(64).map(|n| KeyString::from(n)).collect();
total += bin_length * 64;
table.insert(header[index].name.clone(), DbColumn::Texts(v));
index += 1;
}
}
}
let mut metadata = Metadata::new(metadata_created_by.as_str());
metadata.last_access = metadata_last_access;
metadata.times_accessed = metadata_times_accessed;
metadata.size_of_row = header.iter().fold(0, |acc: usize, x| {
match x.kind {
DbType::Float => acc + 4,
DbType::Int => acc +4,
DbType::Text => acc + 64,
}
});
metadata.size_of_table = table.iter().fold(0, |acc: usize, x| {
match x.1 {
DbColumn::Ints(v) => acc + v.len() * 4,
DbColumn::Floats(v) => acc + v.len() * 4,
DbColumn::Texts(v) => acc + v.len() * 64,
}
});
header.sort_by_key(|x| x.name.clone());
let new_table = EZTable {
metadata,
name: KeyString::from(name),
header: header,
columns: table,
};
Ok(new_table)
}
}
pub fn write_subtable_to_raw_binary(subtable: EZTable) -> Vec<u8> {
let mut total_bytes = 0;
let length = subtable.len();
for item in subtable.columns.values() {
match item {
DbColumn::Texts(_) => {
total_bytes += length * 64;
}
_ => {
total_bytes += length * 4;
}
};
}
let mut output: Vec<u8> = Vec::with_capacity(total_bytes);
for column in subtable.columns.values() {
match &column {
DbColumn::Floats(col) => {
for item in col {
output.extend_from_slice(&item.to_le_bytes());
}
}
&DbColumn::Ints(col) => {
for item in col {
output.extend_from_slice(&item.to_le_bytes());
}
}
DbColumn::Texts(col) => {
for item in col {
output.extend_from_slice(item.raw());
}
}
};
}
output
}
pub fn subtable_from_keys(table: &EZTable, mut keys: Vec<KeyString>) -> Result<EZTable, StrictError> {
let mut indexes = Vec::new();
match table.get_primary_key_type() {
DbType::Int => {
let mut int_keys = Vec::new();
for key in keys {
match key.to_i32_checked() {
Ok(x) => int_keys.push(x),
Err(e) => return Err(StrictError::WrongType),
}
}
int_keys.sort();
let mut key_pointer = 0;
let col = table.get_column_int(&table.get_primary_key_col_index()).unwrap();
for index in 0..col.len() {
if int_keys[key_pointer] == col[index] {
indexes.push(index);
key_pointer += 1
}
}
},
DbType::Text => {
keys.sort();
let mut key_pointer = 0;
let col = table.get_column_text(&table.get_primary_key_col_index()).unwrap();
for index in 0..col.len() {
if keys[key_pointer] == col[index] {
indexes.push(index);
key_pointer += 1
}
}
},
DbType::Float => unreachable!(),
};
Ok(
table.subtable_from_indexes(&indexes, &KeyString::from("__RESULT__"))
)
}
#[inline]
fn rearrange_by_index<T: Clone>(col: &mut Vec<T>, indexer: &[usize]) {
let mut temp = Vec::with_capacity(col.len());
for i in 0..col.len() {
temp.push(col[indexer[i]].clone());
}
*col = temp;
}
pub fn remove_indices<T>(vec: &mut Vec<T>, indices: &[usize]) {
let indices_set: HashSet<_> = indices.iter().cloned().collect();
let mut shift = 0;
for i in 0..vec.len() {
if indices_set.contains(&i) {
shift += 1;
} else if shift > 0 {
vec.swap(i - shift, i);
}
}
vec.truncate(vec.len() - shift);
}
fn merge_sorted<T: Ord + Clone + Display + Debug>(one: &[T], two: &[T]) -> (Vec<T>, Vec<u8>) {
let mut output: Vec<T> = Vec::with_capacity(one.len() + two.len());
let mut record_vec: Vec<u8> = Vec::with_capacity(one.len() + two.len());
let mut one_pointer = 0;
let mut two_pointer = 0;
loop {
match one[one_pointer].cmp(&two[two_pointer]) {
std::cmp::Ordering::Less => {
output.push(one[one_pointer].clone());
record_vec.push(1);
one_pointer += 1;
}
std::cmp::Ordering::Equal => {
output.push(two[two_pointer].clone());
record_vec.push(3);
two_pointer += 1;
one_pointer += 1;
}
std::cmp::Ordering::Greater => {
output.push(two[two_pointer].clone());
record_vec.push(2);
two_pointer += 1;
}
}
if one_pointer >= one.len() {
output.extend_from_slice(&two[two_pointer..two.len()]);
while two_pointer < two.len() {
record_vec.push(2);
two_pointer += 1;
}
break;
} else if two_pointer >= two.len() {
output.extend_from_slice(&one[one_pointer..one.len()]);
while one_pointer < one.len() {
record_vec.push(1);
one_pointer += 1;
}
break;
}
}
(output, record_vec)
}
fn merge_in_order<T: Clone + Display>(one: &[T], two: &[T], record_vec: &[u8]) -> Vec<T> {
let mut output = Vec::with_capacity(one.len() + two.len());
let mut one_pointer = 0;
let mut two_pointer = 0;
for index in record_vec {
match index {
1 => {
output.push(one[one_pointer].clone());
one_pointer += 1;
}
2 => {
output.push(two[two_pointer].clone());
two_pointer += 1;
}
3 => {
output.push(two[two_pointer].clone());
one_pointer += 1;
two_pointer += 1;
}
_ => unreachable!("Should always be 1, 2, or 3"),
}
}
output
}
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct Value {
pub name: KeyString,
pub body: Vec<u8>,
pub metadata: Metadata,
}
impl Value {
pub fn new(name: &str, creator: &str, body: &[u8]) -> Value {
let mut body = Vec::from(body);
body.shrink_to_fit();
Value {
name: KeyString::from(name),
body: body,
metadata: Metadata::new(creator),
}
}
pub fn update(&mut self, value: Value) {
assert_eq!(self.name, value.name);
self.body = value.body;
self.metadata.last_access = get_current_time();
self.metadata.times_accessed += 1;
}
pub fn write_to_raw_binary(&self) -> Vec<u8> {
let mut output = Vec::with_capacity(self.body.len() + 80);
output.extend_from_slice(self.metadata.created_by.raw());
output.extend_from_slice(&self.metadata.last_access.to_le_bytes());
output.extend_from_slice(&self.metadata.times_accessed.to_le_bytes());
output.extend_from_slice(&self.body);
output
}
pub fn read_raw_binary(name: &str, binary: &[u8]) -> Value {
let metadata_created_by = KeyString::from(&binary[0..64]);
let metadata_last_access = u64_from_le_slice(&binary[64..72]);
let metadata_times_accessed = u64_from_le_slice(&binary[72..80]);
let mut metadata = Metadata::new(metadata_created_by.as_str());
metadata.last_access = metadata_last_access;
metadata.times_accessed = metadata_times_accessed;
metadata.size_of_row = 0;
let body = &binary[80..];
Value {
name: KeyString::from(name),
body: body.to_vec(),
metadata,
}
}
}
#[cfg(test)]
mod tests {
#![allow(unused)]
use rand::Rng;
use super::*;
#[test]
fn test_columntable_from_to_string() {
let input = "1vnr,i-P;2heiti,t-N;3magn,i-N\n113035;undirlegg;200\n113050;annad undirlegg;500";
let t = EZTable::from_csv_string(input, "test", "test").unwrap();
assert_eq!(input, t.to_string());
}
#[test]
fn test_columntable_combine_sorted() {
let mut i = 0;
let mut printer = String::from("vnr,text-P;heiti,text-N;magn,int-N;lengd,float-N\n");
let mut printer2 = String::from("vnr,text-P;heiti,text-N;magn,int-N;lengd,float-N\n");
let mut printer22 = String::new();
loop {
if i > 50 {
break;
}
let random_number: i32 = rand::thread_rng().gen();
let random_float: f32 = rand::thread_rng().gen();
let mut random_string = String::new();
for _ in 0..8 {
random_string.push(rand::thread_rng().gen_range(97..122) as u8 as char);
}
printer.push_str(&format!(
"a{i};{random_string};{random_number};{random_float}\n"
));
printer2.push_str(&format!(
"b{i};{random_string};{random_number};{random_float}\n"
));
printer22.push_str(&format!(
"b{i};{random_string};{random_number};{random_float}\n"
));
i += 1;
}
let mut printer3 = String::new();
printer3.push_str(&printer);
printer3.push_str(&printer22);
let mut a = EZTable::from_csv_string(&printer, "a", "test").unwrap();
let b = EZTable::from_csv_string(&printer2, "b", "test").unwrap();
a.update(&b).unwrap();
let c = EZTable::from_csv_string(&printer3, "c", "test").unwrap();
assert_eq!(a.to_string(), c.to_string());
}
#[test]
fn test_columntable_combine_unsorted_csv() {
let unsorted1 = std::fs::read_to_string(format!(
"test_files{PATH_SEP}test_csv_from_google_sheets_unsorted.csv"
))
.unwrap();
let unsorted2 = std::fs::read_to_string(format!(
"test_files{PATH_SEP}test_csv_from_google_sheets2_unsorted.csv"
))
.unwrap();
let sorted_combined = std::fs::read_to_string(format!(
"test_files{PATH_SEP}test_csv_from_google_sheets_combined_sorted.csv"
))
.unwrap();
let mut a = EZTable::from_csv_string(&unsorted1, "a", "test").unwrap();
let b = EZTable::from_csv_string(&unsorted2, "b", "test").unwrap();
let c = EZTable::from_csv_string(&sorted_combined, "c", "test").unwrap();
a.update(&b).unwrap();
let mut file = std::fs::File::create("combined.csv").unwrap();
file.write_all(a.to_string().as_bytes());
let a_string = a.to_string();
let b_string = b.to_string();
let mut a_iter = a_string.split(';');
let mut b_iter = b_string.split(';');
loop {
let x = a_iter.next();
if x.is_none() {break}
let y = b_iter.next();
if y.is_none() {break}
println!("a: {}", x.unwrap());
println!("b: {}", y.unwrap());
}
}
#[test]
fn test_columntable_query_list() {
let input = "vnr,i-P;heiti,t-N;magn,i-N\n113035;undirlegg;200\n113050;annad undirlegg;500";
let t = EZTable::from_csv_string(input, "test", "test").unwrap();
let x = t.query_list(Vec::from(["113035"])).unwrap();
assert_eq!(x, "undirlegg;200;113035");
}
#[test]
fn test_columntable_query_single() {
let input = "vnr,i-P;heiti,t-N;magn,i-N\n113035;undirlegg;200\n113050;annad undirlegg;500";
let t = EZTable::from_csv_string(input, "test", "test").unwrap();
let x = t.query("113035").unwrap();
assert_eq!(x, "undirlegg;200;113035");
}
#[test]
fn test_columntable_query_range() {
let input = "vnr,i-P;heiti,t-N;magn,i-N\n113035;undirlegg;200\n113050;annad undirlegg;500\n18572054;flÃsalÃm;42\n113446;harlech;250";
let t = EZTable::from_csv_string(input, "test", "test").unwrap();
let x = t.query_range(("113035", "113060")).unwrap();
assert_eq!(x, "undirlegg;200;113035\nannad undirlegg;500;113050")
}
#[test]
fn test_raw_binary() {
let input = std::fs::read_to_string(format!(
"test_files{PATH_SEP}test_csv_from_google_sheets_combined_sorted.csv"
))
.unwrap();
let t = EZTable::from_csv_string(&input, "test", "test").unwrap();
let bin_t = t.write_to_raw_binary();
let trans_t = EZTable::read_raw_binary("test", &bin_t).unwrap();
assert_eq!(t, trans_t);
}
#[test]
fn test_delete_range() {
let input = std::fs::read_to_string(format!(
"test_files{PATH_SEP}test_csv_from_google_sheets_combined_sorted.csv"
))
.unwrap();
let test_input = std::fs::read_to_string(format!(
"test_files{PATH_SEP}test_csv_from_google_sheets_combined_sorted_test_range.csv"
))
.unwrap();
let mut t = EZTable::from_csv_string(&input, "test", "test").unwrap();
let test_t = EZTable::from_csv_string(&test_input, "test", "test").unwrap();
t.delete_range(("262", "673"));
assert_eq!(t.to_string(), test_t.to_string());
}
#[test]
fn test_delete_list() {
let input = std::fs::read_to_string(format!(
"test_files{PATH_SEP}test_csv_from_google_sheets_combined_sorted.csv"
))
.unwrap();
let test_input = std::fs::read_to_string(format!(
"test_files{PATH_SEP}test_csv_from_google_sheets_combined_sorted_test_range.csv"
))
.unwrap();
let mut t = EZTable::from_csv_string(&input, "test", "test").unwrap();
let test_t = EZTable::from_csv_string(&test_input, "test", "test").unwrap();
t.delete_list(vec!["262", "264", "353", "544", "656"]);
assert_eq!(t.to_string(), test_t.to_string());
}
#[test]
fn test_copy_lines() {
let input_string = std::fs::read_to_string(format!(
"test_files{PATH_SEP}test_csv_from_google_sheets_combined_sorted.csv"
))
.unwrap();
let table = EZTable::from_csv_string(&input_string, "source", "test").unwrap();
let input_string = std::fs::read_to_string(format!(
"test_files{PATH_SEP}test_csv_from_google_sheets_sorted.csv"
))
.unwrap();
let mut target = EZTable::from_csv_string(&input_string, "target", "test").unwrap();
let line_keys = DbColumn::Ints(vec![
178,
262,
264,
353,
544,
656,
]);
table.copy_lines(&mut target, &line_keys);
}
#[test]
fn test_subtable() {
let table_string = std::fs::read_to_string(&format!("testlarge.csv")).unwrap();
let table = EZTable::from_csv_string(&table_string, "basic_test", "test").unwrap();
let subtable = table.create_subtable(0, 7515);
println!("{}", subtable);
}
#[test]
fn test_left_join() {
let left_string = std::fs::read_to_string(format!("test_files{PATH_SEP}employees.csv")).unwrap();
let right_string = std::fs::read_to_string(format!("test_files{PATH_SEP}departments.csv")).unwrap();
let mut left_table = EZTable::from_csv_string(&left_string, "employees", "test").unwrap();
let right_table = EZTable::from_csv_string(&right_string, "departments", "test").unwrap();
println!("{}", left_table);
println!("{}", right_table);
left_table.left_join(&right_table, &KeyString::from("department"));
println!("{}", left_table);
}
}