diff --git a/src/builder.rs b/src/builder.rs index f407dfe..d51c3b5 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,13 +1,13 @@ -use std::mem; -use crate::database::{Database, SchemaRecord}; +use crate::database::SchemaRecord; use crate::page::{self, Page}; use crate::record::Record; +use std::mem; -pub struct Builder { - current_page: Page, - n_records_on_current_page: u16, - leaf_pages: Vec, - schema: Option, +pub struct DatabaseBuilder { + pub current_page: Page, + pub n_records_on_current_page: u16, + pub leaf_pages: Vec, + pub schema: Option, } fn new_page() -> Page { @@ -16,7 +16,7 @@ fn new_page() -> Page { page } -impl Builder { +impl DatabaseBuilder { pub fn new() -> Self { Self { current_page: new_page(), @@ -29,14 +29,16 @@ impl Builder { pub fn add_record(&mut self, record: Record) { if self.current_page_is_full(&record) { self.finish_current_page(); - self.leaf_pages.push(mem::replace(&mut self.current_page, new_page())); + self.leaf_pages + .push(mem::replace(&mut self.current_page, new_page())); self.n_records_on_current_page = 0; } self.current_page.key = record.rowid; //clone? let bytes: Vec = record.into(); self.current_page.put_bytes_bw(&bytes); - self.current_page.put_u16(self.current_page.bw_position as u16); + self.current_page + .put_u16(self.current_page.bw_position as u16); self.n_records_on_current_page += 1; } @@ -54,46 +56,3 @@ impl Builder { self.current_page.put_u16(self.current_page.bw_position); } } - -impl Into for Builder { - fn into(mut self) -> Database { - self.current_page.fw_position = page::POSITION_CELL_COUNT; - self.current_page.put_u16(self.n_records_on_current_page); - - if self.n_records_on_current_page > 0 { - self.current_page.put_u16(self.current_page.bw_position); - } else { - self.current_page.put_u16(self.current_page.bw_position - 1); - } - - self.leaf_pages.push(self.current_page); - Database::new(self.schema.unwrap(), self.leaf_pages) //panics is schema is not set - } -} - -#[cfg(test)] -mod tests { - use std::fs::File; - use std::io::{BufWriter, Error}; - use crate::database::write; - use crate::values; - use super::*; - - #[test] - fn test_build() -> Result<(), Error> { - let mut builder = Builder::new(); - builder.schema( - "foo", - "create table foo(bar varchar(10))", - ); - let mut record = Record::new(1); - record.add_value(values::string("helloworld")); - builder.add_record(record); - - let database: Database = builder.into(); - let file = File::create("foo.db")?; - let writer = BufWriter::new(file); - write(database, writer)?; - Ok(()) - } -} \ No newline at end of file diff --git a/src/database.rs b/src/database.rs index 10eb124..f9c193e 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,10 +1,10 @@ -use std::io::{BufWriter, Error, Write}; -use std::mem; -use crate::varint; +use crate::builder::DatabaseBuilder; use crate::page; use crate::page::{Page, PageType}; use crate::record::Record; -use crate::values; +use crate::varint; +use std::io::{BufWriter, Error, Write}; +use std::mem; pub struct Database { schema: SchemaRecord, @@ -13,18 +13,31 @@ pub struct Database { impl Database { pub fn new(schema: SchemaRecord, leaf_pages: Vec) -> Self { - Self { - schema, - leaf_pages, + Self { schema, leaf_pages } + } +} + +impl From for Database { + fn from(mut dbb: DatabaseBuilder) -> Self { + dbb.current_page.fw_position = page::POSITION_CELL_COUNT; + dbb.current_page.put_u16(dbb.n_records_on_current_page); + + if dbb.n_records_on_current_page > 0 { + dbb.current_page.put_u16(dbb.current_page.bw_position); + } else { + dbb.current_page.put_u16(dbb.current_page.bw_position - 1); } + + dbb.leaf_pages.push(dbb.current_page); + Database::new(dbb.schema.unwrap(), dbb.leaf_pages) //panics is schema is not set } } pub struct SchemaRecord { - rowid: u64, - table_name: String, - root_page: u32, - sql: String, + pub rowid: u64, + pub table_name: String, + pub root_page: u32, + pub sql: String, } impl SchemaRecord { @@ -38,22 +51,11 @@ impl SchemaRecord { } } -impl Into for SchemaRecord { - fn into(self) -> Record { - let mut record = Record::new(self.rowid); - record.add_value(values::string("table")); - record.add_value(values::string(&self.table_name.to_ascii_lowercase())); - record.add_value(values::string(&self.table_name.to_ascii_lowercase())); - record.add_value(values::integer(self.root_page as i64)); - record.add_value(values::string(&self.sql)); - record - } -} - pub fn write(database: Database, mut writer: BufWriter) -> Result<(), Error> { let mut current_top_layer = database.leaf_pages; let mut n_pages = current_top_layer.len(); - while current_top_layer.len() > 1 { // db needs interior pages? + while current_top_layer.len() > 1 { + // db needs interior pages? current_top_layer = create_interior_pages(current_top_layer); n_pages += current_top_layer.len(); } @@ -103,7 +105,7 @@ fn set_page_references(page: &mut Page) { } fn write_pages(writer: &mut BufWriter, page: &Page) -> Result<(), Error> { - writer.write(&page.data)?; + writer.write_all(&page.data)?; for child in page.children.iter() { write_pages(writer, child)?; } @@ -121,7 +123,7 @@ fn create_header_page(n_pages: usize, schema: SchemaRecord) -> Page { header_page.put_u16(payload_location); //payload start header_page.put_u8(0); // the number of fragmented free bytes within the cell content area header_page.put_u16(payload_location); // first cell - return header_page; + header_page } fn write_schema(root_page: &mut Page, schema_record: SchemaRecord) -> u16 { @@ -131,18 +133,17 @@ fn write_schema(root_page: &mut Page, schema_record: SchemaRecord) -> u16 { root_page.bw_position } -fn create_interior_pages(mut child_pages: Vec) -> Vec { +fn create_interior_pages(child_pages: Vec) -> Vec { let mut interior_pages = Vec::new(); let mut interior_page = Page::new_interior(); interior_page.key = child_pages.iter().map(|p| p.key).max().unwrap(); interior_page.fw_position = page::START_OF_INTERIOR_PAGE; - let mut page_index = 0; let children_length = child_pages.len(); - let mut child_count = 0; let mut last_leaf: Page = Page::new_leaf(); // have to assign :( - for mut leaf_page in child_pages { + for (child_count, mut leaf_page) in child_pages.into_iter().enumerate() { if child_count < children_length - 1 { - if interior_page.bw_position <= interior_page.fw_position + 15 { // 15 is somewhat arbitrary + if interior_page.bw_position <= interior_page.fw_position + 15 { + // 15 is somewhat arbitrary interior_page.fw_position = page::START_OF_CONTENT_AREA; interior_page.put_u16(interior_page.bw_position); interior_page.put_bytes(&[0, 0, 0, 0, 0]); @@ -152,11 +153,9 @@ fn create_interior_pages(mut child_pages: Vec) -> Vec { } create_cell(&mut leaf_page); interior_page.add_child(leaf_page); - page_index += 1; } else { last_leaf = leaf_page; } - child_count += 1; } interior_page.fw_position = page::START_OF_CONTENT_AREA; @@ -184,8 +183,8 @@ fn write_header(rootpage: &mut Page, n_pages: u32) { rootpage.put_u8(MIN_EMBED_PAYLOAD_FRACTION); rootpage.put_u8(LEAF_PAYLOAD_FRACTION); rootpage.put_u32(FILECHANGE_COUNTER); - rootpage.put_u32(n_pages);// file size in pages - rootpage.put_u32(FREELIST_TRUNK_PAGE_HUMBER);// Page number of the first freelist trunk page. + rootpage.put_u32(n_pages); // file size in pages + rootpage.put_u32(FREELIST_TRUNK_PAGE_HUMBER); // Page number of the first freelist trunk page. rootpage.put_u32(TOTAL_N_FREELIST_PAGES); rootpage.put_u32(SCHEMA_COOKIE); rootpage.put_u32(SQLITE_SCHEMAVERSION); @@ -193,17 +192,19 @@ fn write_header(rootpage: &mut Page, n_pages: u32) { rootpage.put_u32(LARGEST_ROOT_BTREE_PAGE); rootpage.put_u32(ENCODING_UTF8); rootpage.put_u32(USER_VERSION); - rootpage.put_u32(VACUUM_MODE_OFF);// True (non-zero) for incremental-vacuum mode. False (zero) otherwise. - rootpage.put_u32(APP_ID);// Application ID - rootpage.put_bytes(&FILLER);// Reserved for expansion. Must be zero. - rootpage.put_bytes(&VERSION_VALID_FOR);// The version-valid-for number - rootpage.put_bytes(&SQLITE_VERSION);// SQLITE_VERSION_NUMBER + rootpage.put_u32(VACUUM_MODE_OFF); // True (non-zero) for incremental-vacuum mode. False (zero) otherwise. + rootpage.put_u32(APP_ID); // Application ID + rootpage.put_bytes(&FILLER); // Reserved for expansion. Must be zero. + rootpage.put_bytes(&VERSION_VALID_FOR); // The version-valid-for number + rootpage.put_bytes(&SQLITE_VERSION); // SQLITE_VERSION_NUMBER rootpage.put_u8(TABLE_LEAF_PAGE); // leaf table b-tree page for schema rootpage.put_u16(NO_FREE_BLOCKS); // zero if there are no freeblocks rootpage.put_u16(1); // the number of cells on this page } -const MAGIC_HEADER: [u8; 16] = [0x53, 0x51, 0x4c, 0x69, 0x74, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x33, 0x00]; +const MAGIC_HEADER: [u8; 16] = [ + 0x53, 0x51, 0x4c, 0x69, 0x74, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x33, 0x00, +]; pub const DEFAULT_PAGE_SIZE: u16 = 4096; const FILE_FORMAT_WRITE_VERSION: u8 = 1; const FILE_FORMAT_READ_VERSION: u8 = 1; @@ -222,7 +223,10 @@ const ENCODING_UTF8: u32 = 1; const USER_VERSION: u32 = 0; const VACUUM_MODE_OFF: u32 = 0; const APP_ID: u32 = 0; -const FILLER: [u8; 20] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; +const FILLER: [u8; 20] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, +]; const VERSION_VALID_FOR: [u8; 4] = [0, 0, 0x03, 250]; const SQLITE_VERSION: [u8; 4] = [0x00, 0x2e, 0x5F, 0x1A]; const NO_FREE_BLOCKS: u16 = 0; @@ -230,5 +234,3 @@ pub const TABLE_LEAF_PAGE: u8 = 0x0d; pub const TABLE_INTERIOR_PAGE: u8 = 0x05; const INDEX_LEAF_PAGE: u8 = 0x0a; const INDEX_INTERIOR_PAGE: u8 = 0x02; - - diff --git a/src/lib.rs b/src/lib.rs index 302978e..ea3e264 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,33 @@ #![allow(dead_code)] -mod page; +mod builder; mod database; +mod page; +mod record; mod values; mod varint; -mod record; -mod builder; - #[cfg(test)] mod tests { + use crate::builder::DatabaseBuilder; + use crate::database::{write, Database}; + use crate::record::Record; + use crate::values; + use std::fs::File; + use std::io::{BufWriter, Error}; + #[test] - fn it_works() { - let result = 2 + 2; - assert_eq!(result, 4); + fn test_build() -> Result<(), Error> { + let mut builder = DatabaseBuilder::new(); + builder.schema("foo", "create table foo(bar varchar(10))"); + let mut record = Record::new(1); + record.add_value(values::string("helloworld")); + builder.add_record(record); + + let database: Database = builder.into(); + let file = File::create("foo.db")?; + let writer = BufWriter::new(file); + write(database, writer)?; + Ok(()) } } diff --git a/src/page.rs b/src/page.rs index a7b991e..b3560b1 100644 --- a/src/page.rs +++ b/src/page.rs @@ -1,5 +1,5 @@ -use byteorder::{BigEndian, ByteOrder}; use crate::database; +use byteorder::{BigEndian, ByteOrder}; pub const POSITION_CELL_COUNT: u16 = 3; pub const START_OF_CONTENT_AREA: u16 = 5; @@ -116,7 +116,7 @@ impl Page { pub fn get_u16(&self) -> u16 { let position = self.fw_position as usize; - (self.data[position] as u16) << 8 + self.data[position + 1] + ((self.data[position] as u16) << 8) + (self.data[position + 1]) as u16 // does not increase the fw pointerr } } @@ -218,4 +218,4 @@ mod tests { assert_eq!(b.data[7], 0x10); assert_eq!(b.data[8], 0x02); } -} \ No newline at end of file +} diff --git a/src/record.rs b/src/record.rs index baaa800..bf10b35 100644 --- a/src/record.rs +++ b/src/record.rs @@ -1,3 +1,4 @@ +use crate::database::SchemaRecord; use crate::values::*; use crate::varint; @@ -21,42 +22,54 @@ impl Record { /// length of the byte representation pub fn bytes_len(&self) -> u16 { - let record_length: u16 = self.values.iter() - .map(|v| v.len()) - .sum(); - record_length+1 + let record_length: u16 = self.values.iter().map(|v| v.len()).sum(); + record_length + 1 } } -impl Into> for Record{ - fn into(mut self) -> Vec { - let record_length = self.bytes_len(); +impl From for Vec { + fn from(mut record: Record) -> Vec { + let record_length = record.bytes_len(); let mut length_bytes = varint::write(record_length as u64); - let mut rowid_bytes = varint::write(self.rowid); + let mut rowid_bytes = varint::write(record.rowid); - let mut buffer = Vec::with_capacity(length_bytes.len() + rowid_bytes.len() + record_length as usize); + let mut buffer = + Vec::with_capacity(length_bytes.len() + rowid_bytes.len() + record_length as usize); buffer.append(&mut length_bytes); buffer.append(&mut rowid_bytes); // 'The initial portion of the payload that does not spill to overflow pages.' - let length_of_encoded_column_types: usize = self.values.iter() - .map(|v| v.datatype.len()) - .sum(); - buffer.append(&mut varint::write((length_of_encoded_column_types + 1) as u64)); + let length_of_encoded_column_types: usize = + record.values.iter().map(|v| v.datatype.len()).sum(); + buffer.append(&mut varint::write( + (length_of_encoded_column_types + 1) as u64, + )); //write all types - for v in self.values.iter_mut() { + for v in record.values.iter_mut() { buffer.append(&mut v.datatype) } // write all values - for v in self.values.iter_mut() { + for v in record.values.iter_mut() { buffer.append(&mut v.data) } buffer } } +impl From for Record { + fn from(s: SchemaRecord) -> Self { + let mut record = Record::new(s.rowid); + record.add_value(string("table")); + record.add_value(string(&s.table_name.to_ascii_lowercase())); + record.add_value(string(&s.table_name.to_ascii_lowercase())); + record.add_value(integer(s.root_page as i64)); + record.add_value(string(&s.sql)); + record + } +} + #[cfg(test)] mod tests { use super::*; @@ -66,4 +79,4 @@ mod tests { let mut record = Record::new(1); record.add_value(string("hello")); } -} \ No newline at end of file +} diff --git a/src/values.rs b/src/values.rs index 8264092..10f7d6d 100644 --- a/src/values.rs +++ b/src/values.rs @@ -1,12 +1,11 @@ -use byteorder::{BigEndian, ByteOrder}; use crate::varint; +use byteorder::{BigEndian, ByteOrder}; pub struct Value { pub datatype: Vec, pub data: Vec, } - impl Value { pub fn len(&self) -> u16 { (self.datatype.len() + self.data.len()) as u16 @@ -15,21 +14,33 @@ impl Value { pub fn string(value: &str) -> Value { let bytes = value.chars().map(|c| c as u8).collect::>(); - Value { datatype: varint::write((bytes.len() * 2 + 13) as u64), data: bytes } + Value { + datatype: varint::write((bytes.len() * 2 + 13) as u64), + data: bytes, + } } pub fn blob(value: Vec) -> Value { - Value { datatype: varint::write((value.len() * 2 + 12) as u64), data: value } + Value { + datatype: varint::write((value.len() * 2 + 12) as u64), + data: value, + } } pub fn integer(value: i64) -> Value { - Value { datatype: get_int_type(value), data: sqlite_integer_to_bytes(value) } + Value { + datatype: get_int_type(value), + data: sqlite_integer_to_bytes(value), + } } pub fn float(value: f64) -> Value { let mut buffer = [0_u8; 8]; BigEndian::write_f64(&mut buffer, value); - Value { datatype: vec![7], data: buffer.to_vec() } + Value { + datatype: vec![7], + data: buffer.to_vec(), + } } pub fn len(value: &Value) -> usize { @@ -73,12 +84,7 @@ fn get_int_type(value: i64) -> Vec { } fn get_length_of_byte_encoding(value: i64) -> u8 { - let u = - if value < 0 { - !value - } else { - value - }; + let u = if value < 0 { !value } else { value }; if u <= 127 { 1 } else if u <= 32767 { @@ -96,7 +102,6 @@ fn get_length_of_byte_encoding(value: i64) -> u8 { #[cfg(test)] mod tests { - use crate::values::Value; use super::*; #[test] @@ -147,4 +152,4 @@ mod tests { assert_eq!(v.datatype, vec![2]); assert_eq!(v.data, vec![0, 128]); } -} \ No newline at end of file +} diff --git a/src/varint.rs b/src/varint.rs index cc33068..33aa569 100644 --- a/src/varint.rs +++ b/src/varint.rs @@ -29,13 +29,14 @@ mod tests { #[test] fn test() { - assert_eq!(vec![0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], write(0xffffffffffffffff)); + assert_eq!( + vec![0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], + write(0xffffffffffffffff) + ); } #[test] fn test_write1() { - let a:i16 = -1; - println!("{}", a as u16); assert_eq!(vec![1], write(0x01)); } -} \ No newline at end of file +}