use crate::table::schema::{Column, DataType, Schema};
#[derive(Debug, Clone)]
pub struct TableMeta {
pub name: String,
pub schema: Schema,
pub root_page: usize,
pub row_count: usize,
pub autoinc_last: u64,
}
impl TableMeta {
pub fn new(name: &str, schema: Schema, root_page: usize) -> Self {
TableMeta { name: name.to_string(), schema, root_page, row_count: 0, autoinc_last: 0 }
}
}
#[derive(Debug, Clone)]
pub struct IndexMeta {
pub name: String,
pub table: String,
pub columns: Vec<String>,
pub unique: bool,
}
impl IndexMeta {
pub fn new(name: &str, table: &str, columns: &[String], unique: bool) -> Self {
IndexMeta {
name: name.to_string(),
table: table.to_string(),
columns: columns.to_vec(),
unique,
}
}
}
#[derive(Debug, Clone)]
pub struct ViewMeta {
pub name: String,
pub query: String,
}
impl ViewMeta {
pub fn new(name: &str, query: &str) -> Self {
ViewMeta {
name: name.to_string(),
query: query.to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct TriggerMeta {
pub name: String,
pub table: String,
pub timing: TriggerTiming,
pub event: TriggerEvent,
pub for_each_row: bool,
pub when: Option<String>,
pub body: String,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TriggerTiming { Before, After, InsteadOf }
#[derive(Debug, Clone, PartialEq)]
pub enum TriggerEvent { Delete, Insert, Update(Option<Vec<String>>) }
impl TriggerMeta {
pub fn new(name: &str, table: &str, timing: TriggerTiming, event: TriggerEvent, for_each_row: bool, when: Option<String>, body: &str) -> Self {
TriggerMeta {
name: name.to_string(),
table: table.to_string(),
timing,
event,
for_each_row,
when,
body: body.to_string(),
}
}
}
pub fn encode_meta(meta: &TableMeta) -> Vec<u8> {
let mut buf = Vec::new();
encode_str(&mut buf, &meta.name);
buf.extend_from_slice(&(meta.schema.columns.len() as u32).to_le_bytes());
for col in &meta.schema.columns {
encode_str(&mut buf, &col.name);
buf.push(datatype_tag(&col.data_type));
buf.push(col.nullable as u8);
buf.push(col.autoinc as u8);
}
buf.extend_from_slice(&(meta.root_page as u32).to_le_bytes());
buf.extend_from_slice(&(meta.row_count as u32).to_le_bytes());
buf.extend_from_slice(&meta.autoinc_last.to_le_bytes());
buf
}
pub fn decode_meta(bytes: &[u8]) -> TableMeta {
let mut cur = 0;
let (name, n) = decode_str(&bytes[cur..]);
cur += n;
let col_count = u32::from_le_bytes(bytes[cur..cur+4].try_into().unwrap()) as usize;
cur += 4;
let mut columns = Vec::with_capacity(col_count);
for _ in 0..col_count {
let (col_name, n) = decode_str(&bytes[cur..]);
cur += n;
let dt = tag_datatype(bytes[cur]);
cur += 1;
let nullable = bytes[cur] != 0;
cur += 1;
let autoinc = bytes[cur] != 0;
cur += 1;
let mut col = Column::new(&col_name, dt);
col.nullable = nullable;
col.autoinc = autoinc;
columns.push(col);
}
let root_page = u32::from_le_bytes(bytes[cur..cur+4].try_into().unwrap()) as usize;
cur += 4;
let row_count = u32::from_le_bytes(bytes[cur..cur+4].try_into().unwrap()) as usize;
cur += 4;
let autoinc_last = u64::from_le_bytes(bytes[cur..cur+8].try_into().unwrap());
TableMeta { name, schema: Schema::new(columns), root_page, row_count, autoinc_last }
}
fn encode_str(buf: &mut Vec<u8>, s: &str) {
let b = s.as_bytes();
buf.extend_from_slice(&(b.len() as u32).to_le_bytes());
buf.extend_from_slice(b);
}
fn decode_str(buf: &[u8]) -> (String, usize) {
let len = u32::from_le_bytes(buf[0..4].try_into().unwrap()) as usize;
let s = std::str::from_utf8(&buf[4..4+len]).unwrap().to_string();
(s, 4 + len)
}
fn datatype_tag(dt: &DataType) -> u8 {
match dt {
DataType::Integer => 0,
DataType::Text => 1,
DataType::Boolean => 2,
DataType::Float => 3,
}
}
fn tag_datatype(tag: u8) -> DataType {
match tag {
0 => DataType::Integer,
1 => DataType::Text,
2 => DataType::Boolean,
3 => DataType::Float,
t => panic!("unknown datatype tag: {}", t),
}
}
pub fn encode_trigger(trigger: &TriggerMeta) -> Vec<u8> {
let mut buf = Vec::new();
encode_str(&mut buf, &trigger.name);
encode_str(&mut buf, &trigger.table);
buf.push(match trigger.timing {
TriggerTiming::Before => 0u8,
TriggerTiming::After => 1u8,
TriggerTiming::InsteadOf => 2u8,
});
match &trigger.event {
TriggerEvent::Delete => {
buf.push(0u8);
}
TriggerEvent::Insert => {
buf.push(1u8);
}
TriggerEvent::Update(cols) => {
buf.push(2u8);
match cols {
Some(c) => {
buf.extend_from_slice(&(c.len() as u32).to_le_bytes());
for col in c {
encode_str(&mut buf, col);
}
}
None => {
buf.extend_from_slice(&u32::MAX.to_le_bytes());
}
}
}
}
buf.push(trigger.for_each_row as u8);
match &trigger.when {
Some(w) => {
buf.extend_from_slice(&(w.len() as u32).to_le_bytes());
buf.extend_from_slice(w.as_bytes());
}
None => {
buf.extend_from_slice(&u32::MAX.to_le_bytes());
}
}
encode_str(&mut buf, &trigger.body);
buf
}
pub fn decode_trigger(bytes: &[u8]) -> TriggerMeta {
let mut cur = 0;
let (name, n) = decode_str(&bytes[cur..]);
cur += n;
let (table, n) = decode_str(&bytes[cur..]);
cur += n;
let timing = match bytes[cur] {
0 => TriggerTiming::Before,
1 => TriggerTiming::After,
_ => TriggerTiming::InsteadOf,
};
cur += 1;
let event = match bytes[cur] {
0 => TriggerEvent::Delete,
1 => TriggerEvent::Insert,
_ => {
cur += 1;
let cols_len = u32::from_le_bytes(bytes[cur..cur+4].try_into().unwrap()) as usize;
cur += 4;
let cols = if cols_len == u32::MAX as usize {
None
} else {
let mut cols_vec = Vec::with_capacity(cols_len);
for _ in 0..cols_len {
let (col, n) = decode_str(&bytes[cur..]);
cur += n;
cols_vec.push(col);
}
Some(cols_vec)
};
TriggerEvent::Update(cols)
}
};
if matches!(event, TriggerEvent::Delete | TriggerEvent::Insert) {
cur += 1;
}
let for_each_row = bytes[cur] != 0;
cur += 1;
let when_len = u32::from_le_bytes(bytes[cur..cur+4].try_into().unwrap()) as usize;
cur += 4;
let when = if when_len == u32::MAX as usize {
None
} else {
Some(std::str::from_utf8(&bytes[cur..cur+when_len]).unwrap().to_string())
};
if when.is_some() { cur += when_len; }
let (body, _) = decode_str(&bytes[cur..]);
TriggerMeta {
name,
table,
timing,
event,
for_each_row,
when,
body,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::table::schema::{Column, DataType, Schema};
fn sample_meta() -> TableMeta {
let schema = Schema::new(vec![
Column::new("id", DataType::Integer).not_null(),
Column::new("name", DataType::Text),
Column::new("ok", DataType::Boolean),
]);
let mut meta = TableMeta::new("users", schema, 3);
meta.row_count = 42;
meta
}
#[test]
fn roundtrip_meta() {
let original = sample_meta();
let bytes = encode_meta(&original);
let decoded = decode_meta(&bytes);
assert_eq!(decoded.name, "users");
assert_eq!(decoded.root_page, 3);
assert_eq!(decoded.row_count, 42);
assert_eq!(decoded.schema.columns.len(), 3);
assert_eq!(decoded.schema.columns[0].name, "id");
assert_eq!(decoded.schema.columns[0].data_type, DataType::Integer);
assert!(!decoded.schema.columns[0].nullable);
assert_eq!(decoded.schema.columns[1].name, "name");
assert_eq!(decoded.schema.columns[2].data_type, DataType::Boolean);
}
}