mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-23 08:55:40 +01:00
I/O interface refactoring
This commit is contained in:
@@ -12,5 +12,5 @@ path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.75"
|
||||
lig_core = { path = "../../core" }
|
||||
lig_core = { path = "../../core", default-features = false }
|
||||
wasm-bindgen = "0.2"
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
use anyhow::Result;
|
||||
use lig_core::DatabaseRef;
|
||||
use std::sync::Arc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
@@ -11,19 +8,8 @@ pub struct Database {
|
||||
#[wasm_bindgen]
|
||||
impl Database {
|
||||
pub fn open(path: &str) -> Database {
|
||||
let inner = lig_core::Database::open(Arc::new(IO {}), path).unwrap();
|
||||
let io = lig_core::IO::default();
|
||||
let inner = lig_core::Database::open(io, path).unwrap();
|
||||
Database { _inner: inner }
|
||||
}
|
||||
}
|
||||
|
||||
struct IO {}
|
||||
|
||||
impl lig_core::IO for IO {
|
||||
fn open(&self, _path: &str) -> Result<DatabaseRef> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get(&self, _database_ref: DatabaseRef, _page_idx: usize, _buf: &mut [u8]) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use clap::{Parser, ValueEnum};
|
||||
use cli_table::{Cell, Table};
|
||||
use lig_core::{Database, Value, SyncIO};
|
||||
use lig_core::{Database, Value, IO};
|
||||
use rustyline::{error::ReadlineError, DefaultEditor};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq)]
|
||||
enum OutputMode {
|
||||
@@ -30,8 +29,8 @@ struct Opts {
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let opts = Opts::parse();
|
||||
let io = SyncIO::new();
|
||||
let db = Database::open(Arc::new(io), opts.database.to_str().unwrap())?;
|
||||
let io = IO::default();
|
||||
let db = Database::open(io, opts.database.to_str().unwrap())?;
|
||||
let conn = db.connect();
|
||||
let mut rl = DefaultEditor::new()?;
|
||||
let home = dirs::home_dir().unwrap();
|
||||
@@ -102,4 +101,3 @@ fn main() -> anyhow::Result<()> {
|
||||
rl.save_history(history_file.as_path())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,10 @@ description = "The Lig database library"
|
||||
name = "lig_core"
|
||||
path = "lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["fs"]
|
||||
fs = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.75"
|
||||
concurrent_lru = "0.2.0"
|
||||
|
||||
@@ -3,7 +3,6 @@ use lig_core::{Database, SyncIO};
|
||||
use pprof::criterion::{Output, PProfProfiler};
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
fn bench_db() -> Database {
|
||||
let io = SyncIO::new();
|
||||
Database::open(Arc::new(io), "../testing/hello.db").unwrap()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::pager::Pager;
|
||||
use crate::sqlite3_ondisk::{BTreeCell, BTreePage, TableInteriorCell, TableLeafCell};
|
||||
use crate::sqlite3_ondisk::{BTreeCell, TableInteriorCell, TableLeafCell};
|
||||
use crate::types::Record;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
92
core/io.rs
Normal file
92
core/io.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use anyhow::Result;
|
||||
use std::cell::RefCell;
|
||||
use std::io::{Read, Seek};
|
||||
use std::{fs::File, sync::Arc};
|
||||
|
||||
/// I/O access method
|
||||
enum IOMethod {
|
||||
Memory,
|
||||
|
||||
#[cfg(feature = "fs")]
|
||||
Sync,
|
||||
}
|
||||
|
||||
/// I/O access interface.
|
||||
pub struct IO {
|
||||
io_method: IOMethod,
|
||||
}
|
||||
|
||||
#[cfg(feature = "fs")]
|
||||
impl Default for IO {
|
||||
fn default() -> Self {
|
||||
IO {
|
||||
io_method: IOMethod::Sync,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "fs"))]
|
||||
impl Default for IO {
|
||||
fn default() -> Self {
|
||||
IO {
|
||||
io_method: IOMethod::Memory,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IO {
|
||||
pub fn open(&self, path: &str) -> Result<PageSource> {
|
||||
match self.io_method {
|
||||
#[cfg(feature = "fs")]
|
||||
IOMethod::Sync => {
|
||||
let io = Arc::new(FileIO::open(path)?);
|
||||
Ok(PageSource { io })
|
||||
}
|
||||
IOMethod::Memory => {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PageSource {
|
||||
io: Arc<dyn PageIO>,
|
||||
}
|
||||
|
||||
impl PageSource {
|
||||
pub fn get(&self, page_idx: usize, buf: &mut [u8]) -> Result<()> {
|
||||
self.io.get(page_idx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
trait PageIO {
|
||||
fn get(&self, page_idx: usize, buf: &mut [u8]) -> Result<()>;
|
||||
}
|
||||
|
||||
struct FileIO {
|
||||
file: RefCell<File>,
|
||||
}
|
||||
|
||||
impl PageIO for FileIO {
|
||||
fn get(&self, page_idx: usize, buf: &mut [u8]) -> Result<()> {
|
||||
let page_size = buf.len();
|
||||
assert!(page_idx > 0);
|
||||
assert!(page_size >= 512);
|
||||
assert!(page_size <= 65536);
|
||||
assert!((page_size & (page_size - 1)) == 0);
|
||||
let pos = (page_idx - 1) * page_size;
|
||||
let mut file = self.file.borrow_mut();
|
||||
file.seek(std::io::SeekFrom::Start(pos as u64))?;
|
||||
file.read_exact(buf)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FileIO {
|
||||
fn open(path: &str) -> Result<Self> {
|
||||
let file = std::fs::File::open(path)?;
|
||||
Ok(FileIO {
|
||||
file: RefCell::new(file),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub(crate) mod sync_io;
|
||||
@@ -1,53 +0,0 @@
|
||||
use crate::{DatabaseRef, IO};
|
||||
|
||||
use std::io::{Read, Seek};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use anyhow::Result;
|
||||
|
||||
/// Synchronous I/O using traditional read() and write() system calls.
|
||||
pub struct SyncIO {
|
||||
inner: RefCell<SyncIOInner>,
|
||||
}
|
||||
|
||||
struct SyncIOInner {
|
||||
db_refs: usize,
|
||||
db_files: HashMap<DatabaseRef, File>,
|
||||
}
|
||||
|
||||
impl IO for SyncIO {
|
||||
fn open(&self, path: &str) -> Result<DatabaseRef> {
|
||||
let file = std::fs::File::open(path)?;
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
let db_ref = inner.db_refs;
|
||||
inner.db_refs += 1;
|
||||
inner.db_files.insert(db_ref, file);
|
||||
Ok(db_ref)
|
||||
}
|
||||
|
||||
fn get(&self, database_ref: DatabaseRef, page_idx: usize, buf: &mut [u8]) -> Result<()> {
|
||||
let page_size = buf.len();
|
||||
assert!(page_idx > 0);
|
||||
assert!(page_size >= 512);
|
||||
assert!(page_size <= 65536);
|
||||
assert!((page_size & (page_size - 1)) == 0);
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
let file = inner.db_files.get_mut(&database_ref).unwrap();
|
||||
let pos = (page_idx - 1) * page_size;
|
||||
file.seek(std::io::SeekFrom::Start(pos as u64))?;
|
||||
file.read_exact(buf)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SyncIO {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: RefCell::new(SyncIOInner {
|
||||
db_refs: 0,
|
||||
db_files: HashMap::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
20
core/lib.rs
20
core/lib.rs
@@ -1,11 +1,11 @@
|
||||
mod btree;
|
||||
mod buffer_pool;
|
||||
mod io;
|
||||
mod pager;
|
||||
mod schema;
|
||||
mod sqlite3_ondisk;
|
||||
mod types;
|
||||
mod vdbe;
|
||||
mod io;
|
||||
|
||||
use mimalloc::MiMalloc;
|
||||
|
||||
@@ -17,9 +17,9 @@ use fallible_iterator::FallibleIterator;
|
||||
use pager::Pager;
|
||||
use schema::Schema;
|
||||
use sqlite3_parser::{ast::Cmd, lexer::sql::Parser};
|
||||
use std::{borrow::BorrowMut, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use io::sync_io::SyncIO;
|
||||
pub use io::{PageSource, IO};
|
||||
pub use types::Value;
|
||||
|
||||
pub struct Database {
|
||||
@@ -28,8 +28,8 @@ pub struct Database {
|
||||
}
|
||||
|
||||
impl Database {
|
||||
pub fn open(io: Arc<dyn IO>, path: &str) -> Result<Database> {
|
||||
let pager = Arc::new(Pager::open(io.clone(), path)?);
|
||||
pub fn open(io: IO, path: &str) -> Result<Database> {
|
||||
let pager = Arc::new(Pager::open(&io, path)?);
|
||||
let bootstrap_schema = Arc::new(Schema::new());
|
||||
let conn = Connection {
|
||||
pager: pager.clone(),
|
||||
@@ -190,13 +190,3 @@ impl Row {
|
||||
T::from_value(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub type DatabaseRef = usize;
|
||||
|
||||
pub trait IO {
|
||||
/// Open a database file.
|
||||
fn open(&self, path: &str) -> Result<DatabaseRef>;
|
||||
|
||||
/// Get a page from the database file.
|
||||
fn get(&self, database_ref: DatabaseRef, page_idx: usize, buf: &mut [u8]) -> Result<()>;
|
||||
}
|
||||
|
||||
@@ -2,28 +2,25 @@ use crate::buffer_pool;
|
||||
use crate::buffer_pool::BufferPool;
|
||||
use crate::sqlite3_ondisk;
|
||||
use crate::sqlite3_ondisk::BTreePage;
|
||||
use crate::DatabaseRef;
|
||||
use crate::IO;
|
||||
use crate::{PageSource, IO};
|
||||
use concurrent_lru::unsharded::LruCache;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub struct Pager {
|
||||
io: Arc<dyn IO>,
|
||||
database_ref: DatabaseRef,
|
||||
database: PageSource,
|
||||
page_cache: LruCache<usize, Arc<BTreePage>>,
|
||||
buffer_pool: Arc<Mutex<BufferPool>>,
|
||||
}
|
||||
|
||||
impl Pager {
|
||||
pub fn open(io: Arc<dyn IO>, path: &str) -> anyhow::Result<Self> {
|
||||
let database_ref = io.open(path)?;
|
||||
let db_header = sqlite3_ondisk::read_database_header(io.clone(), database_ref)?;
|
||||
pub fn open(io: &IO, path: &str) -> anyhow::Result<Self> {
|
||||
let database = io.open(path)?;
|
||||
let db_header = sqlite3_ondisk::read_database_header(&database)?;
|
||||
let page_size = db_header.page_size as usize;
|
||||
let buffer_pool = Arc::new(Mutex::new(buffer_pool::BufferPool::new(page_size)));
|
||||
let page_cache = LruCache::new(10);
|
||||
Ok(Self {
|
||||
io,
|
||||
database_ref,
|
||||
database,
|
||||
buffer_pool,
|
||||
page_cache,
|
||||
})
|
||||
@@ -32,13 +29,8 @@ impl Pager {
|
||||
pub fn read_page(&self, page_idx: usize) -> anyhow::Result<Arc<BTreePage>> {
|
||||
let handle = self.page_cache.get_or_try_init(page_idx, 1, |idx| {
|
||||
let mut buffer_pool = self.buffer_pool.lock().unwrap();
|
||||
let page = sqlite3_ondisk::read_btree_page(
|
||||
self.io.clone(),
|
||||
self.database_ref,
|
||||
&mut buffer_pool,
|
||||
page_idx,
|
||||
)
|
||||
.unwrap();
|
||||
let page = sqlite3_ondisk::read_btree_page(&self.database, &mut buffer_pool, page_idx)
|
||||
.unwrap();
|
||||
Ok::<Arc<BTreePage>, anyhow::Error>(Arc::new(page))
|
||||
})?;
|
||||
Ok(handle.value().clone())
|
||||
|
||||
@@ -25,10 +25,9 @@
|
||||
/// For more information, see: https://www.sqlite.org/fileformat.html
|
||||
use crate::buffer_pool::BufferPool;
|
||||
use crate::types::{Record, Value};
|
||||
use crate::{DatabaseRef, IO};
|
||||
use crate::PageSource;
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::borrow::BorrowMut;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// The size of the database header in bytes.
|
||||
pub const DATABASE_HEADER_SIZE: usize = 100;
|
||||
@@ -60,9 +59,9 @@ pub struct DatabaseHeader {
|
||||
version_number: u32,
|
||||
}
|
||||
|
||||
pub fn read_database_header(io: Arc<dyn IO>, database_ref: DatabaseRef) -> Result<DatabaseHeader> {
|
||||
pub fn read_database_header(database: &PageSource) -> Result<DatabaseHeader> {
|
||||
let mut buf = [0; 512];
|
||||
io.get(database_ref, 1, &mut buf)?;
|
||||
database.get(1, &mut buf)?;
|
||||
let mut header = DatabaseHeader::default();
|
||||
header.magic.copy_from_slice(&buf[0..16]);
|
||||
header.page_size = u16::from_be_bytes([buf[16], buf[17]]);
|
||||
@@ -130,14 +129,13 @@ pub struct BTreePage {
|
||||
}
|
||||
|
||||
pub fn read_btree_page(
|
||||
io: Arc<dyn IO>,
|
||||
database_ref: DatabaseRef,
|
||||
database: &PageSource,
|
||||
buffer_pool: &mut BufferPool,
|
||||
page_idx: usize,
|
||||
) -> Result<BTreePage> {
|
||||
let mut buf = buffer_pool.get();
|
||||
let page = &mut buf.borrow_mut().data_mut();
|
||||
io.get(database_ref, page_idx, page)?;
|
||||
database.get(page_idx, page)?;
|
||||
let mut pos = if page_idx == 1 {
|
||||
DATABASE_HEADER_SIZE
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user