diff --git a/Cargo.lock b/Cargo.lock index 552996ed6..132a96057 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,7 +120,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.29", ] [[package]] @@ -129,6 +129,29 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +[[package]] +name = "cli-table" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adfbb116d9e2c4be7011360d0c0bee565712c11e969c9609b25b619366dc379d" +dependencies = [ + "cli-table-derive", + "csv", + "termcolor", + "unicode-width", +] + +[[package]] +name = "cli-table-derive" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af3bfb9da627b0a6c467624fb7963921433774ed435493b5c08a3053e829ad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "clipboard-win" version = "4.5.0" @@ -146,6 +169,27 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "csv" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626ae34994d3d8d668f4269922248239db4ae42d538b14c398b74a52208e8086" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + [[package]] name = "dirs" version = "5.0.1" @@ -269,6 +313,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + [[package]] name = "libc" version = "0.2.147" @@ -281,6 +331,7 @@ version = "0.0.0" dependencies = [ "anyhow", "clap", + "cli-table", "dirs", "lig_core", "rustyline", @@ -493,12 +544,38 @@ dependencies = [ "winapi", ] +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -542,6 +619,17 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.29" @@ -553,6 +641,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.47" @@ -570,7 +667,7 @@ checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.29", ] [[package]] @@ -639,7 +736,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.29", "wasm-bindgen-shared", ] @@ -661,7 +758,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.29", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -688,6 +785,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index cde2bdf1d..3a21fb82e 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -17,6 +17,7 @@ path = "main.rs" [dependencies] anyhow = "1.0.75" clap = { version = "4.4.0", features = ["derive"] } +cli-table = "0.4.7" dirs = "5.0.1" lig_core = { path = "../core" } rustyline = "12.0.0" diff --git a/cli/main.rs b/cli/main.rs index 5975322c1..4f3fa3b26 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -1,6 +1,7 @@ use anyhow::Result; use clap::Parser; -use lig_core::{Database, DatabaseRef}; +use cli_table::{Cell, Table}; +use lig_core::{Database, DatabaseRef, Value}; use rustyline::{error::ReadlineError, DefaultEditor}; use std::cell::RefCell; use std::collections::HashMap; @@ -31,10 +32,31 @@ fn main() -> anyhow::Result<()> { let readline = rl.readline("\x1b[90m>\x1b[0m "); match readline { Ok(line) => { - if let Err(err) = conn.execute(&line) { - eprintln!("{}", err); - } else { - rl.add_history_entry(line)?; + rl.add_history_entry(line.to_owned())?; + match conn.query(line) { + Ok(Some(ref mut rows)) => { + let mut table_rows: Vec> = vec![]; + while let Some(row) = rows.next()? { + table_rows.push( + row.values + .iter() + .map(|value| match value { + Value::Null => "NULL".cell(), + Value::Integer(i) => i.to_string().cell(), + Value::Float(f) => f.to_string().cell(), + Value::Text(s) => s.cell(), + Value::Blob(b) => format!("{:?}", b).cell(), + }) + .collect(), + ); + } + let table = table_rows.table(); + cli_table::print_stdout(table).unwrap(); + } + Ok(None) => {} + Err(err) => { + eprintln!("{}", err); + } } } Err(ReadlineError::Interrupted) => { diff --git a/core/btree.rs b/core/btree.rs index ef761cf2e..e8e38a6d3 100644 --- a/core/btree.rs +++ b/core/btree.rs @@ -1,5 +1,6 @@ use crate::pager::Pager; -use crate::sqlite3_ondisk::{BTreeCell, BTreePage, Record, TableLeafCell}; +use crate::sqlite3_ondisk::{BTreeCell, BTreePage, TableLeafCell}; +use crate::types::Record; use anyhow::Result; diff --git a/core/lib.rs b/core/lib.rs index 5e91463b9..a64f5f2b8 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -3,6 +3,7 @@ mod buffer_pool; mod pager; mod schema; mod sqlite3_ondisk; +mod types; mod vdbe; use anyhow::Result; @@ -12,6 +13,8 @@ use schema::Schema; use sqlite3_parser::{ast::Cmd, lexer::sql::Parser}; use std::sync::Arc; +pub use types::Value; + pub struct Database { pager: Arc, schema: Arc, @@ -43,7 +46,7 @@ pub struct Connection { } impl Connection { - pub fn query(&self, sql: impl Into) -> Result<()> { + pub fn query(&self, sql: impl Into) -> Result> { let sql = sql.into(); let mut parser = Parser::new(sql.as_bytes()); let cmd = parser.next()?; @@ -53,28 +56,14 @@ impl Connection { let program = vdbe::translate(&self.schema, stmt)?; let mut state = vdbe::ProgramState::new(self.pager.clone(), program.max_registers); - loop { - let result = program.step(&mut state)?; - match result { - vdbe::StepResult::Row => { - let mut row = Vec::new(); - for i in 0..state.column_count() { - row.push(state.column(i).unwrap().to_string()); - } - log::trace!("Row = {:?}", row); - } - vdbe::StepResult::IO => todo!(), - vdbe::StepResult::Done => break, - } - } + Ok(Some(Rows::new(state, program))) } - Cmd::Explain(_stmt) => { - todo!(); - } - Cmd::ExplainQueryPlan(_stmt) => todo!(), + Cmd::Explain(_stmt) => Ok(None), + Cmd::ExplainQueryPlan(_stmt) => Ok(None), } + } else { + Ok(None) } - Ok(()) } pub fn execute(&self, sql: impl Into) -> Result<()> { @@ -100,6 +89,32 @@ impl Connection { } } +pub struct Rows { + state: vdbe::ProgramState, + program: vdbe::Program, +} + +impl Rows { + pub fn new(state: vdbe::ProgramState, program: vdbe::Program) -> Self { + Self { state, program } + } + + pub fn next(&mut self) -> Result> { + loop { + let result = self.program.step(&mut self.state)?; + match result { + vdbe::StepResult::Row(row) => { + return Ok(Some(row)); + } + vdbe::StepResult::IO => todo!(), + vdbe::StepResult::Done => { + return Ok(None); + } + } + } + } +} + pub type DatabaseRef = usize; pub trait IO { diff --git a/core/sqlite3_ondisk.rs b/core/sqlite3_ondisk.rs index 79319b514..c9a7ad3dc 100644 --- a/core/sqlite3_ondisk.rs +++ b/core/sqlite3_ondisk.rs @@ -24,6 +24,7 @@ /// /// 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 anyhow::{anyhow, Result}; use std::borrow::BorrowMut; @@ -202,20 +203,6 @@ pub fn read_btree_cell(page: &[u8], page_type: &PageType, pos: usize) -> Result< } } -#[derive(Debug, Clone)] -pub enum Value { - Null, - Integer(i64), - Float(f64), - Text(String), - Blob(Vec), -} - -#[derive(Debug)] -pub struct Record { - pub values: Vec, -} - #[derive(Debug)] pub enum SerialType { Null, diff --git a/core/types.rs b/core/types.rs new file mode 100644 index 000000000..a23712cd0 --- /dev/null +++ b/core/types.rs @@ -0,0 +1,19 @@ +#[derive(Debug, Clone)] +pub enum Value { + Null, + Integer(i64), + Float(f64), + Text(String), + Blob(Vec), +} + +#[derive(Debug)] +pub struct Record { + pub values: Vec, +} + +impl Record { + pub fn new(values: Vec) -> Self { + Self { values } + } +} diff --git a/core/vdbe.rs b/core/vdbe.rs index 190e758b9..ae1a0f57b 100644 --- a/core/vdbe.rs +++ b/core/vdbe.rs @@ -1,7 +1,7 @@ use crate::btree::Cursor; use crate::pager::Pager; use crate::schema::Schema; -use crate::sqlite3_ondisk::Value; +use crate::types::{Record, Value}; use anyhow::Result; use sqlite3_parser::ast::{OneSelect, Select, Stmt}; @@ -49,8 +49,8 @@ pub enum Insn { // Emit a row of results. ResultRow { - // FIXME: This is incorrect, it should be reading from registers. - cursor_id: CursorID, + register_start: usize, + register_end: usize, }, // Advance the cursor to the next row. @@ -101,6 +101,10 @@ impl ProgramBuilder { reg } + pub fn next_free_register(&self) -> usize { + self.next_free_register + } + pub fn emit_placeholder(&mut self) -> usize { let offset = self.insns.len(); self.insns.push(Insn::Halt); @@ -130,7 +134,7 @@ impl ProgramBuilder { pub enum StepResult { Done, IO, - Row, + Row(Record), } /// The program state describes the environment in which the program executes. @@ -231,11 +235,16 @@ impl Program { } state.pc += 1; } - Insn::ResultRow { cursor_id } => { - let cursor = state.cursors.get_mut(cursor_id).unwrap(); - let _ = cursor.record()?; + Insn::ResultRow { + register_start, + register_end, + } => { + let mut values = vec![]; + for i in *register_start..*register_end { + values.push(state.registers[i].clone().unwrap()); + } state.pc += 1; - return Ok(StepResult::Row); + return Ok(StepResult::Row(Record::new(values))); } Insn::NextAsync { cursor_id } => { let cursor = state.cursors.get_mut(cursor_id).unwrap(); @@ -309,8 +318,12 @@ fn translate_select(schema: &Schema, select: Select) -> Result { program.emit_insn(Insn::OpenReadAwait); program.emit_insn(Insn::RewindAsync { cursor_id }); let rewind_await_offset = program.emit_placeholder(); - translate_columns(&mut program, Some(cursor_id), Some(table), columns); - program.emit_insn(Insn::ResultRow { cursor_id }); + let (register_start, register_end) = + translate_columns(&mut program, Some(cursor_id), Some(table), columns); + program.emit_insn(Insn::ResultRow { + register_start, + register_end, + }); program.emit_insn(Insn::NextAsync { cursor_id }); program.emit_insn(Insn::NextAwait { cursor_id, @@ -344,8 +357,12 @@ fn translate_select(schema: &Schema, select: Select) -> Result { let mut program = ProgramBuilder::new(); let init_offset = program.emit_placeholder(); let after_init_offset = program.offset(); - translate_columns(&mut program, None, None, columns); - program.emit_insn(Insn::ResultRow { cursor_id: 0 }); + let (register_start, register_end) = + translate_columns(&mut program, None, None, columns); + program.emit_insn(Insn::ResultRow { + register_start, + register_end, + }); program.emit_insn(Insn::Halt); program.fixup_insn( init_offset, @@ -367,7 +384,8 @@ fn translate_columns( cursor_id: Option, table: Option<&crate::schema::Table>, columns: Vec, -) { +) -> (usize, usize) { + let register_start = program.next_free_register(); for col in columns { match col { sqlite3_parser::ast::ResultColumn::Expr(expr, _) => match expr { @@ -449,6 +467,8 @@ fn translate_columns( sqlite3_parser::ast::ResultColumn::TableStar(_) => todo!(), } } + let register_end = program.next_free_register(); + (register_start, register_end) } fn trace_insn(addr: usize, insn: &Insn) { @@ -503,7 +523,18 @@ fn insn_to_str(addr: usize, insn: &Insn) -> String { column, dest, } => ("Column", *cursor_id, *column, *dest, "", 0, "".to_string()), - Insn::ResultRow { cursor_id } => ("ResultRow", *cursor_id, 0, 0, "", 0, "".to_string()), + Insn::ResultRow { + register_start, + register_end, + } => ( + "ResultRow", + *register_start, + *register_end, + 0, + "", + 0, + "".to_string(), + ), Insn::NextAsync { cursor_id } => ("NextAsync", *cursor_id, 0, 0, "", 0, "".to_string()), Insn::NextAwait { cursor_id,