initial stage
This commit is contained in:
parent
0f3c4676c6
commit
553dfb4099
9 changed files with 491 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
||||||
|
*.iml
|
||||||
|
/.idea
|
||||||
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "sqlighters"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
byteorder = "1.4.3"
|
||||||
163
src/bytebuffer.rs
Normal file
163
src/bytebuffer.rs
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
|
|
||||||
|
/// bytebuffer that supports forward and backward writing (this is not endianness)
|
||||||
|
/// Reason: SQLite pages are written in 2 directions: from the front for the cell-pointers and from the back for the cells
|
||||||
|
/// - fixed size
|
||||||
|
/// - big endian only
|
||||||
|
pub struct ByteBuffer {
|
||||||
|
data: Vec<u8>,
|
||||||
|
pub fw_position: usize,
|
||||||
|
pub bw_position: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ByteBuffer {
|
||||||
|
pub fn new(size: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
data: vec![0; size],
|
||||||
|
fw_position: 0,
|
||||||
|
bw_position: size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// forward put unsigned byte array
|
||||||
|
pub fn put_u8a(&mut self, bytes: &[u8]) {
|
||||||
|
for v in bytes {
|
||||||
|
self.data[self.fw_position] = *v;
|
||||||
|
self.fw_position += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// backward put unsigned byte array
|
||||||
|
pub fn put_u8a_bw(&mut self, bytes: &[u8]) {
|
||||||
|
self.bw_position -= bytes.len();
|
||||||
|
for v in bytes {
|
||||||
|
self.data[self.bw_position] = *v;
|
||||||
|
self.bw_position += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// forward put unsigned byte
|
||||||
|
pub fn put_u8(&mut self, byte: u8) {
|
||||||
|
self.put_u8a(&[byte]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// backward put unsigned byte
|
||||||
|
pub fn put_u8_bw(&mut self, byte: u8) {
|
||||||
|
self.put_u8a_bw(&[byte]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// forward put unsigned 16bit integer
|
||||||
|
pub fn put_u16(&mut self, val: u16) {
|
||||||
|
let mut buf = [0; 2];
|
||||||
|
BigEndian::write_u16(&mut buf, val);
|
||||||
|
self.put_u8a(&buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// backward put unsigned 16bit integer
|
||||||
|
pub fn put_u16_bw(&mut self, val: u16) {
|
||||||
|
let mut buf = [0; 2];
|
||||||
|
BigEndian::write_u16(&mut buf, val);
|
||||||
|
self.put_u8a_bw(&buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// forward put unsigned 16bit integer
|
||||||
|
pub fn put_u32(&mut self, val: u32) {
|
||||||
|
let mut buf = [0; 4];
|
||||||
|
BigEndian::write_u32(&mut buf, val);
|
||||||
|
self.put_u8a(&buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// backward put unsigned 32bit integer
|
||||||
|
pub fn put_u32_bw(&mut self, val: u32) {
|
||||||
|
let mut buf = [0; 4];
|
||||||
|
BigEndian::write_u32(&mut buf, val);
|
||||||
|
self.put_u8a_bw(&buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_u8() {
|
||||||
|
let mut b = ByteBuffer::new(1);
|
||||||
|
b.put_u8(64_u8);
|
||||||
|
assert_eq!(b.data[0], 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_u8a() {
|
||||||
|
let mut b = ByteBuffer::new(2);
|
||||||
|
b.put_u8a(&[1, 2]);
|
||||||
|
assert_eq!(b.data[0], 1);
|
||||||
|
assert_eq!(b.data[1], 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_u16() {
|
||||||
|
let mut b = ByteBuffer::new(2);
|
||||||
|
b.put_u16(4096);
|
||||||
|
assert_eq!(b.data[0], 16);
|
||||||
|
assert_eq!(b.data[1], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_u32() {
|
||||||
|
let mut b = ByteBuffer::new(4);
|
||||||
|
b.put_u32(0xFFFFFFFF);
|
||||||
|
assert_eq!(b.data[0], 0xFF);
|
||||||
|
assert_eq!(b.data[1], 0xFF);
|
||||||
|
assert_eq!(b.data[2], 0xFF);
|
||||||
|
assert_eq!(b.data[3], 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_u16_position() {
|
||||||
|
let mut b = ByteBuffer::new(4);
|
||||||
|
b.fw_position = 2;
|
||||||
|
b.put_u16(4096);
|
||||||
|
assert_eq!(b.data[0], 0);
|
||||||
|
assert_eq!(b.data[1], 0);
|
||||||
|
assert_eq!(b.data[2], 16);
|
||||||
|
assert_eq!(b.data[3], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_u16_backwards() {
|
||||||
|
let mut b = ByteBuffer::new(4);
|
||||||
|
b.put_u16_bw(0x1000);
|
||||||
|
assert_eq!(b.data[0], 0);
|
||||||
|
assert_eq!(b.data[1], 0);
|
||||||
|
assert_eq!(b.data[2], 0x10);
|
||||||
|
assert_eq!(b.data[3], 0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_u16_2_directions() {
|
||||||
|
let mut b = ByteBuffer::new(5);
|
||||||
|
b.put_u16(0x1001);
|
||||||
|
b.put_u16_bw(0x1000);
|
||||||
|
assert_eq!(b.data[0], 0x10);
|
||||||
|
assert_eq!(b.data[1], 0x01);
|
||||||
|
assert_eq!(b.data[2], 0); // decimal suggests this value has not been written
|
||||||
|
assert_eq!(b.data[3], 0x10);
|
||||||
|
assert_eq!(b.data[4], 0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_u32_2_directions() {
|
||||||
|
let mut b = ByteBuffer::new(9);
|
||||||
|
b.put_u32(0x1001);
|
||||||
|
b.put_u32_bw(0x1002);
|
||||||
|
assert_eq!(b.data[0], 0x00);
|
||||||
|
assert_eq!(b.data[1], 0x00);
|
||||||
|
assert_eq!(b.data[2], 0x10);
|
||||||
|
assert_eq!(b.data[3], 0x01);
|
||||||
|
assert_eq!(b.data[4], 0);
|
||||||
|
assert_eq!(b.data[5], 0x00);
|
||||||
|
assert_eq!(b.data[6], 0x00);
|
||||||
|
assert_eq!(b.data[7], 0x10);
|
||||||
|
assert_eq!(b.data[8], 0x02);
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/database.rs
Normal file
0
src/database.rs
Normal file
19
src/lib.rs
Normal file
19
src/lib.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
mod page;
|
||||||
|
mod database;
|
||||||
|
mod bytebuffer;
|
||||||
|
mod values;
|
||||||
|
mod varint;
|
||||||
|
mod record;
|
||||||
|
|
||||||
|
const DEFAULT_PAGE_SIZE: usize = 4096;
|
||||||
|
const TABLE_INTERIOR_PAGE: u8 = 0x05;
|
||||||
|
const TABLE_LEAF_PAGE: u8 = 0x0D;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
let result = 2 + 2;
|
||||||
|
assert_eq!(result, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
76
src/page.rs
Normal file
76
src/page.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
use crate::{DEFAULT_PAGE_SIZE, TABLE_LEAF_PAGE};
|
||||||
|
use crate::bytebuffer::ByteBuffer;
|
||||||
|
|
||||||
|
const POSITION_CELL_COUNT: u32 = 3;
|
||||||
|
const START_OF_CONTENT_AREA: u32 = 5;
|
||||||
|
|
||||||
|
pub enum PageType {
|
||||||
|
Leaf,
|
||||||
|
Interior,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents an SQLite page
|
||||||
|
struct Page {
|
||||||
|
data: ByteBuffer,
|
||||||
|
key: i64,
|
||||||
|
children: Vec<Page>,
|
||||||
|
number: u32,
|
||||||
|
page_type: PageType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Page {
|
||||||
|
fn with_capacity(size: usize, page_type: PageType) -> Self {
|
||||||
|
Self {
|
||||||
|
data: ByteBuffer::new(size),
|
||||||
|
key: 0,
|
||||||
|
children: Vec::new(),
|
||||||
|
number: 0,
|
||||||
|
page_type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_leaf() -> Self {
|
||||||
|
let mut page = Page::with_capacity(DEFAULT_PAGE_SIZE, PageType::Leaf);
|
||||||
|
page.put_u8(TABLE_LEAF_PAGE);
|
||||||
|
page
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_interior() -> Self {
|
||||||
|
let mut page = Page::with_capacity(DEFAULT_PAGE_SIZE, PageType::Interior);
|
||||||
|
page.put_u8(TABLE_LEAF_PAGE);
|
||||||
|
page
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_child(&mut self, child: Self) {
|
||||||
|
self.children.push(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fw_position(&mut self, new_position: usize) {
|
||||||
|
self.data.fw_position = new_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bw_position(&mut self, new_position: usize) {
|
||||||
|
self.data.bw_position = new_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn put_u8a(&mut self, value: &[u8]) {
|
||||||
|
self.data.put_u8a(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn put_u8(&mut self, value: u8) {
|
||||||
|
self.data.put_u8(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn put_u16(&mut self, value: u16) {
|
||||||
|
self.data.put_u16(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn put_u32(&mut self, value: u32) {
|
||||||
|
self.data.put_u32(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// may panic
|
||||||
|
fn get_page_nr_last_child(self) -> u32 {
|
||||||
|
self.children[self.children.len()-1].number
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/record.rs
Normal file
30
src/record.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
use crate::values::*;
|
||||||
|
|
||||||
|
struct Record {
|
||||||
|
rowid: i64,
|
||||||
|
values: Vec<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Record {
|
||||||
|
fn new(rowid: i64) -> Self {
|
||||||
|
Self {
|
||||||
|
rowid,
|
||||||
|
values: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_value(&mut self, value: Value) {
|
||||||
|
self.values.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
let mut record = Record::new(1);
|
||||||
|
record.add_value(Value::String("hello".to_owned()));
|
||||||
|
}
|
||||||
|
}
|
||||||
149
src/values.rs
Normal file
149
src/values.rs
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
|
use crate::varint;
|
||||||
|
|
||||||
|
pub enum Value {
|
||||||
|
String(String),
|
||||||
|
Blob(Vec<u8>),
|
||||||
|
Integer(i64),
|
||||||
|
Float(f64),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns (datatype, value)
|
||||||
|
pub fn get_bytes(value: Value) -> (Vec<u8>, Vec<u8>) {
|
||||||
|
match value {
|
||||||
|
Value::String(value) => {
|
||||||
|
let bytes = value.chars().map(|c| c as u8).collect::<Vec<_>>();
|
||||||
|
(varint::write((bytes.len() * 2 + 13) as u64), bytes)
|
||||||
|
}
|
||||||
|
Value::Blob(value) => {
|
||||||
|
(varint::write((value.len() * 2 + 12) as u64), value)
|
||||||
|
}
|
||||||
|
Value::Integer(value) => {
|
||||||
|
(get_int_type(value), integer_to_bytes(value))
|
||||||
|
}
|
||||||
|
Value::Float(value) => {
|
||||||
|
let mut buffer = [0 as u8; 8];
|
||||||
|
BigEndian::write_f64(&mut buffer, value);
|
||||||
|
(vec![7], buffer.to_vec())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns a variable length Vec of u8
|
||||||
|
fn integer_to_bytes(value: i64) -> Vec<u8> {
|
||||||
|
if value == 0 || value == 1 {
|
||||||
|
vec![]
|
||||||
|
} else {
|
||||||
|
return long_to_bytes(value, get_length_of_byte_encoding(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn long_to_bytes(n: i64, nbytes: u8) -> Vec<u8> {
|
||||||
|
let mut bytes = vec![];
|
||||||
|
for i in 0..nbytes {
|
||||||
|
bytes.push(((n >> (nbytes - i - 1) * 8) & 0xFF) as u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_int_type(value: i64) -> Vec<u8> {
|
||||||
|
if value == 0 {
|
||||||
|
vec![8]
|
||||||
|
} else if value == 1 {
|
||||||
|
vec![9]
|
||||||
|
} else {
|
||||||
|
let length = get_length_of_byte_encoding(value);
|
||||||
|
if length < 5 {
|
||||||
|
varint::write(length as u64)
|
||||||
|
} else if length < 7 {
|
||||||
|
varint::write(5)
|
||||||
|
} else {
|
||||||
|
varint::write(5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_length_of_byte_encoding(value: i64) -> u8 {
|
||||||
|
let u =
|
||||||
|
if value < 0 {
|
||||||
|
!value
|
||||||
|
} else {
|
||||||
|
value
|
||||||
|
};
|
||||||
|
if u <= 127 {
|
||||||
|
1
|
||||||
|
} else if u <= 32767 {
|
||||||
|
2
|
||||||
|
} else if u <= 8388607 {
|
||||||
|
3
|
||||||
|
} else if u <= 2147483647 {
|
||||||
|
4
|
||||||
|
} else if u <= 140737488355327 {
|
||||||
|
6
|
||||||
|
} else {
|
||||||
|
8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::mem;
|
||||||
|
use crate::values::{get_bytes, Value};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_string() {
|
||||||
|
let v = Value::String("hello".to_owned());
|
||||||
|
let byte_rep = get_bytes(v);
|
||||||
|
assert_eq!(byte_rep.0, vec![23]);
|
||||||
|
assert_eq!(byte_rep.1, vec![0x68, 0x65, 0x6C, 0x6C, 0x6F]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blob() {
|
||||||
|
let v = Value::Blob(vec![1, 2, 3, 4, 5]);
|
||||||
|
let byte_rep = get_bytes(v);
|
||||||
|
assert_eq!(byte_rep.0, vec![22]);
|
||||||
|
assert_eq!(byte_rep.1, vec![1, 2, 3, 4, 5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_float() {
|
||||||
|
let v = Value::Float(1.1);
|
||||||
|
let byte_rep = get_bytes(v);
|
||||||
|
assert_eq!(byte_rep.0, vec![7]);
|
||||||
|
assert_eq!(byte_rep.1, vec![0x3f, 0xf1, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_integer0() {
|
||||||
|
let v = Value::Integer(0);
|
||||||
|
let byte_rep = get_bytes(v);
|
||||||
|
assert_eq!(byte_rep.0, vec![8]);
|
||||||
|
assert_eq!(byte_rep.1, vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_integer1() {
|
||||||
|
let v = Value::Integer(1);
|
||||||
|
let byte_rep = get_bytes(v);
|
||||||
|
assert_eq!(byte_rep.0, vec![9]);
|
||||||
|
assert_eq!(byte_rep.1, vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_integer2() {
|
||||||
|
let v = Value::Integer(2);
|
||||||
|
let byte_rep = get_bytes(v);
|
||||||
|
assert_eq!(byte_rep.0, vec![1]);
|
||||||
|
assert_eq!(byte_rep.1, vec![2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_integer128() {
|
||||||
|
let v = Value::Integer(128);
|
||||||
|
let byte_rep = get_bytes(v);
|
||||||
|
assert_eq!(byte_rep.0, vec![2]);
|
||||||
|
assert_eq!(byte_rep.1, vec![0, 128]);
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/varint.rs
Normal file
41
src/varint.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
/// varints as implemented in SQLite
|
||||||
|
pub fn write(value: u64) -> Vec<u8> {
|
||||||
|
let mut v = value;
|
||||||
|
if (v & ((0xff000000) << 32)) != 0 {
|
||||||
|
let mut result = vec![0_u8; 9];
|
||||||
|
result[8] = v as u8;
|
||||||
|
v >>= 8;
|
||||||
|
for i in (0..=7).rev() {
|
||||||
|
result[i] = ((v & 0x7f) | 0x80) as u8;
|
||||||
|
v >>= 7;
|
||||||
|
}
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
while v != 0 {
|
||||||
|
result.push(((v & 0x7f) | 0x80) as u8);
|
||||||
|
v >>= 7;
|
||||||
|
}
|
||||||
|
result[0] &= 0x7f;
|
||||||
|
|
||||||
|
result.reverse();
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue