diff --git a/.gitignore b/.gitignore index a0f62a32..4a6505cb 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,5 @@ release/ release* Cross.toml /tools -*.cozo_auth \ No newline at end of file +*.cozo_auth +.cozo_repl_history \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 77e00260..02a169e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -667,6 +667,7 @@ dependencies = [ "chrono", "clap 4.0.32", "cozo", + "ctrlc", "env_logger", "log", "miette", @@ -773,6 +774,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctrlc" +version = "3.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1631ca6e3c59112501a9d87fd86f21591ff77acd31331e8a73f8d80a65bbdd71" +dependencies = [ + "nix 0.26.1", + "windows-sys 0.42.0", +] + [[package]] name = "cxx" version = "1.0.85" @@ -1971,6 +1982,18 @@ dependencies = [ "libc", ] +[[package]] +name = "nix" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "libc", + "static_assertions", +] + [[package]] name = "nom" version = "5.1.2" @@ -2088,9 +2111,9 @@ dependencies = [ [[package]] name = "object" -version = "0.30.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239da7f290cfa979f43f85a8efeee9a8a76d0827c356d37f9d3d7254d6b537fb" +checksum = "8d864c91689fdc196779b98dba0aceac6118594c2df6ee5d943eb6a8df4d107a" dependencies = [ "memchr", ] @@ -3010,7 +3033,7 @@ dependencies = [ "libc", "log", "memchr", - "nix", + "nix 0.24.3", "radix_trie", "scopeguard", "unicode-segmentation", @@ -3673,9 +3696,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.23.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" +checksum = "38a54aca0c15d014013256222ba0ebed095673f89345dd79119d912eb561b7a8" dependencies = [ "autocfg", "bytes", diff --git a/cozo-core/src/cozoscript.pest b/cozo-core/src/cozoscript.pest index f73bfc42..b9b12e48 100644 --- a/cozo-core/src/cozoscript.pest +++ b/cozo-core/src/cozoscript.pest @@ -15,7 +15,7 @@ sys_script = {SOI ~ "::" ~ (compact_op | list_relations_op | list_relation_op | compact_op = {"compact"} running_op = {"running"} -kill_op = {"kill" ~ int} +kill_op = {"kill" ~ expr} explain_op = {"explain" ~ query_script_inner} list_relations_op = {"relations"} list_relation_op = {"columns" ~ compound_ident} diff --git a/cozo-core/src/parse/sys.rs b/cozo-core/src/parse/sys.rs index 4855c7b1..345489d2 100644 --- a/cozo-core/src/parse/sys.rs +++ b/cozo-core/src/parse/sys.rs @@ -10,12 +10,13 @@ use std::collections::BTreeMap; use std::sync::Arc; use itertools::Itertools; -use miette::{Diagnostic, Result}; +use miette::{miette, Diagnostic, Result}; use thiserror::Error; use crate::data::program::InputProgram; use crate::data::symb::Symbol; use crate::data::value::{DataValue, ValidityTs}; +use crate::parse::expr::build_expr; use crate::parse::query::parse_query; use crate::parse::{ExtractSpan, Pairs, Rule, SourceSpan}; use crate::runtime::relation::AccessLevel; @@ -44,19 +45,20 @@ pub(crate) fn parse_sys( mut src: Pairs<'_>, param_pool: &BTreeMap, algorithms: &BTreeMap>>, - cur_vld: ValidityTs + cur_vld: ValidityTs, ) -> Result { let inner = src.next().unwrap(); Ok(match inner.as_rule() { Rule::compact_op => SysOp::Compact, Rule::running_op => SysOp::ListRunning, Rule::kill_op => { - let i_str = inner.into_inner().next().unwrap(); - let i = i_str - .as_str() - .parse::() - .map_err(|_| ProcessIdError(i_str.as_str().to_string(), i_str.extract_span()))?; - SysOp::KillRunning(i) + let i_expr = inner.into_inner().next().unwrap(); + let i_val = build_expr(i_expr, param_pool)?; + let i_val = i_val.eval_to_const()?; + let i_val = i_val + .get_int() + .ok_or_else(|| miette!("Process ID must be an integer"))?; + SysOp::KillRunning(i_val as u64) } Rule::explain_op => { let prog = parse_query( @@ -128,7 +130,12 @@ pub(crate) fn parse_sys( let op = clause_inner.next().unwrap(); let script = clause_inner.next().unwrap(); let script_str = script.as_str(); - parse_query(script.into_inner(), &Default::default(), algorithms, cur_vld)?; + parse_query( + script.into_inner(), + &Default::default(), + algorithms, + cur_vld, + )?; match op.as_rule() { Rule::trigger_put => puts.push(script_str.to_string()), Rule::trigger_rm => rms.push(script_str.to_string()), diff --git a/cozo-core/src/runtime/db.rs b/cozo-core/src/runtime/db.rs index 15210bef..db331676 100644 --- a/cozo-core/src/runtime/db.rs +++ b/cozo-core/src/runtime/db.rs @@ -1186,9 +1186,9 @@ impl Poison { #[inline(always)] pub(crate) fn check(&self) -> Result<()> { #[derive(Debug, Error, Diagnostic)] - #[error("Process is killed before completion")] + #[error("Running query is killed before completion")] #[diagnostic(code(eval::killed))] - #[diagnostic(help("A process may be killed by timeout, or explicit command"))] + #[diagnostic(help("A query may be killed by timeout, or explicit command"))] struct ProcessKilled; if self.0.load(Ordering::Relaxed) { diff --git a/cozoserver/Cargo.toml b/cozoserver/Cargo.toml index 1adeb5f9..eb1c3c93 100644 --- a/cozoserver/Cargo.toml +++ b/cozoserver/Cargo.toml @@ -56,3 +56,4 @@ prettytable = "0.10.0" rustyline = "10.0.0" minreq = { version = "2.6.0", features = ["https-rustls"] } miette = { version = "5.5.0", features = ["fancy"] } +ctrlc = "3.2.4" \ No newline at end of file diff --git a/cozoserver/src/main.rs b/cozoserver/src/main.rs index e1eb3e34..b53df1ca 100644 --- a/cozoserver/src/main.rs +++ b/cozoserver/src/main.rs @@ -89,6 +89,21 @@ fn main() { } if args.repl { + let db_copy = db.clone(); + ctrlc::set_handler(move || { + let running = db_copy + .run_script("::running", Default::default()) + .expect("Cannot determine running queries"); + for row in running.rows { + let id = row.into_iter().next().unwrap(); + eprintln!("Killing running query {}", id); + db_copy + .run_script("::kill $id", BTreeMap::from([("id".to_string(), id)])) + .expect("Cannot kill process"); + } + }) + .expect("Error setting Ctrl-C handler"); + if let Err(e) = repl_main(db) { eprintln!("{}", e); exit(-1); diff --git a/cozoserver/src/repl.rs b/cozoserver/src/repl.rs index 06b16c79..0e705391 100644 --- a/cozoserver/src/repl.rs +++ b/cozoserver/src/repl.rs @@ -70,6 +70,11 @@ pub(crate) fn repl_main(db: DbInstance) -> Result<(), Box> { let mut save_next: Option = None; rl.set_helper(Some(Indented)); + let history_file = ".cozo_repl_history"; + if rl.load_history(history_file).is_ok() { + println!("Loaded history from {}", history_file); + } + loop { let readline = rl.readline("=> "); match readline { @@ -78,6 +83,7 @@ pub(crate) fn repl_main(db: DbInstance) -> Result<(), Box> { eprintln!("{:?}", err); } rl.add_history_entry(line); + exit = false; } Err(rustyline::error::ReadlineError::Interrupted) => { if exit { @@ -91,6 +97,10 @@ pub(crate) fn repl_main(db: DbInstance) -> Result<(), Box> { Err(e) => eprintln!("{:?}", e), } } + + if rl.save_history(history_file).is_ok() { + eprintln!("Query history saved in {}", history_file); + } Ok(()) }