diff --git a/Cargo.lock b/Cargo.lock index 3400ba7ad..35b1fe5c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -220,6 +220,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "cfg_aliases" version = "0.2.1" @@ -490,6 +496,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctrlc" +version = "3.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345" +dependencies = [ + "nix 0.28.0", + "windows-sys 0.52.0", +] + [[package]] name = "debugid" version = "0.8.0" @@ -1014,6 +1030,7 @@ dependencies = [ "anyhow", "clap 4.5.8", "cli-table", + "ctrlc", "dirs", "env_logger 0.10.2", "limbo_core", @@ -1149,6 +1166,18 @@ dependencies = [ "libc", ] +[[package]] +name = "nix" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "cfg_aliases 0.1.1", + "libc", +] + [[package]] name = "nix" version = "0.29.0" @@ -1157,7 +1186,7 @@ checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags 2.6.0", "cfg-if", - "cfg_aliases", + "cfg_aliases 0.2.1", "libc", ] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 4dab73153..589b2b7ca 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -17,6 +17,7 @@ dist = true name = "limbo" path = "main.rs" + [dependencies] anyhow = "1.0.75" clap = { version = "4.4.0", features = ["derive"] } @@ -25,3 +26,4 @@ dirs = "5.0.1" env_logger = "0.10.1" limbo_core = { path = "../core" } rustyline = "12.0.0" +ctrlc = "3.4.4" \ No newline at end of file diff --git a/cli/main.rs b/cli/main.rs index bae51058e..8ce18dfc0 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -6,6 +6,7 @@ use limbo_core::{Database, RowResult, Value}; use opcodes_dictionary::OPCODE_DESCRIPTIONS; use rustyline::{error::ReadlineError, DefaultEditor}; use std::path::PathBuf; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; #[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq)] @@ -39,11 +40,24 @@ fn main() -> anyhow::Result<()> { let io = Arc::new(limbo_core::PlatformIO::new()?); let db = Database::open_file(io.clone(), path)?; let conn = db.connect(); + + let interrupt_count = Arc::new(AtomicUsize::new(0)); + + { + let interrupt_count = Arc::clone(&interrupt_count); + + ctrlc::set_handler(move || { + // Increment the interrupt count on Ctrl-C + interrupt_count.fetch_add(1, Ordering::SeqCst); + }) + .expect("Error setting Ctrl-C handler"); + } + if let Some(sql) = opts.sql { if sql.trim().starts_with('.') { handle_dot_command(io.clone(), &conn, &sql)?; } else { - query(io.clone(), &conn, &sql, &opts.output_mode)?; + query(io.clone(), &conn, &sql, &opts.output_mode, &interrupt_count)?; } return Ok(()); } @@ -60,14 +74,27 @@ fn main() -> anyhow::Result<()> { match readline { Ok(line) => { rl.add_history_entry(line.to_owned())?; + interrupt_count.store(0, Ordering::SeqCst); if line.trim().starts_with('.') { handle_dot_command(io.clone(), &conn, &line)?; } else { - query(io.clone(), &conn, &line, &opts.output_mode)?; + query( + io.clone(), + &conn, + &line, + &opts.output_mode, + &interrupt_count, + )?; } } Err(ReadlineError::Interrupted) => { - break; + // At prompt, increment interrupt count + if interrupt_count.fetch_add(1, Ordering::SeqCst) >= 1 { + eprintln!("Interrupted. Exiting..."); + break; + } + println!("Use .quit to exit or press Ctrl-C again to force quit."); + continue; } Err(ReadlineError::Eof) => { break; @@ -91,16 +118,20 @@ In addition to standard SQL commands, the following special commands are availab Special Commands: ----------------- +.quit Stop interpreting input stream and exit. .schema Show the schema of the specified table. .opcodes Display all the opcodes defined by the virtual machine .help Display this help message. Usage Examples: --------------- -1. To view the schema of a table named 'employees': +1. To quit the Limbo SQL Shell: + .quit + +2. To view the schema of a table named 'employees': .schema employees -2. To list all available SQL opcodes: +3. To list all available SQL opcodes: .opcodes Note: @@ -125,6 +156,10 @@ fn handle_dot_command( } match args[0] { + ".quit" => { + println!("Exiting Limbo SQL Shell."); + std::process::exit(0) + } ".schema" => { let table_name = args.get(1).copied(); display_schema(io, conn, table_name)?; @@ -218,10 +253,16 @@ fn query( conn: &limbo_core::Connection, sql: &str, output_mode: &OutputMode, + interrupt_count: &Arc, ) -> anyhow::Result<()> { match conn.query(sql) { Ok(Some(ref mut rows)) => match output_mode { OutputMode::Raw => loop { + if interrupt_count.load(Ordering::SeqCst) > 0 { + println!("Query interrupted."); + return Ok(()); + } + match rows.next_row()? { RowResult::Row(row) => { for (i, value) in row.values.iter().enumerate() { @@ -241,10 +282,16 @@ fn query( RowResult::IO => { io.run_once()?; } - RowResult::Done => break, + RowResult::Done => { + break; + } } }, OutputMode::Pretty => { + if interrupt_count.load(Ordering::SeqCst) > 0 { + println!("Query interrupted."); + return Ok(()); + } let mut table_rows: Vec> = vec![]; loop { match rows.next_row()? {