Ctrl-C kills current query in REPL

main
Ziyang Hu 2 years ago
parent 4b33e9e848
commit a754a3b701

3
.gitignore vendored

@ -36,4 +36,5 @@ release/
release* release*
Cross.toml Cross.toml
/tools /tools
*.cozo_auth *.cozo_auth
.cozo_repl_history

33
Cargo.lock generated

@ -667,6 +667,7 @@ dependencies = [
"chrono", "chrono",
"clap 4.0.32", "clap 4.0.32",
"cozo", "cozo",
"ctrlc",
"env_logger", "env_logger",
"log", "log",
"miette", "miette",
@ -773,6 +774,16 @@ dependencies = [
"memchr", "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]] [[package]]
name = "cxx" name = "cxx"
version = "1.0.85" version = "1.0.85"
@ -1971,6 +1982,18 @@ dependencies = [
"libc", "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]] [[package]]
name = "nom" name = "nom"
version = "5.1.2" version = "5.1.2"
@ -2088,9 +2111,9 @@ dependencies = [
[[package]] [[package]]
name = "object" name = "object"
version = "0.30.0" version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "239da7f290cfa979f43f85a8efeee9a8a76d0827c356d37f9d3d7254d6b537fb" checksum = "8d864c91689fdc196779b98dba0aceac6118594c2df6ee5d943eb6a8df4d107a"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -3010,7 +3033,7 @@ dependencies = [
"libc", "libc",
"log", "log",
"memchr", "memchr",
"nix", "nix 0.24.3",
"radix_trie", "radix_trie",
"scopeguard", "scopeguard",
"unicode-segmentation", "unicode-segmentation",
@ -3673,9 +3696,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.23.0" version = "1.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" checksum = "38a54aca0c15d014013256222ba0ebed095673f89345dd79119d912eb561b7a8"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"bytes", "bytes",

@ -15,7 +15,7 @@ sys_script = {SOI ~ "::" ~ (compact_op | list_relations_op | list_relation_op |
compact_op = {"compact"} compact_op = {"compact"}
running_op = {"running"} running_op = {"running"}
kill_op = {"kill" ~ int} kill_op = {"kill" ~ expr}
explain_op = {"explain" ~ query_script_inner} explain_op = {"explain" ~ query_script_inner}
list_relations_op = {"relations"} list_relations_op = {"relations"}
list_relation_op = {"columns" ~ compound_ident} list_relation_op = {"columns" ~ compound_ident}

@ -10,12 +10,13 @@ use std::collections::BTreeMap;
use std::sync::Arc; use std::sync::Arc;
use itertools::Itertools; use itertools::Itertools;
use miette::{Diagnostic, Result}; use miette::{miette, Diagnostic, Result};
use thiserror::Error; use thiserror::Error;
use crate::data::program::InputProgram; use crate::data::program::InputProgram;
use crate::data::symb::Symbol; use crate::data::symb::Symbol;
use crate::data::value::{DataValue, ValidityTs}; use crate::data::value::{DataValue, ValidityTs};
use crate::parse::expr::build_expr;
use crate::parse::query::parse_query; use crate::parse::query::parse_query;
use crate::parse::{ExtractSpan, Pairs, Rule, SourceSpan}; use crate::parse::{ExtractSpan, Pairs, Rule, SourceSpan};
use crate::runtime::relation::AccessLevel; use crate::runtime::relation::AccessLevel;
@ -44,19 +45,20 @@ pub(crate) fn parse_sys(
mut src: Pairs<'_>, mut src: Pairs<'_>,
param_pool: &BTreeMap<String, DataValue>, param_pool: &BTreeMap<String, DataValue>,
algorithms: &BTreeMap<String, Arc<Box<dyn FixedRule>>>, algorithms: &BTreeMap<String, Arc<Box<dyn FixedRule>>>,
cur_vld: ValidityTs cur_vld: ValidityTs,
) -> Result<SysOp> { ) -> Result<SysOp> {
let inner = src.next().unwrap(); let inner = src.next().unwrap();
Ok(match inner.as_rule() { Ok(match inner.as_rule() {
Rule::compact_op => SysOp::Compact, Rule::compact_op => SysOp::Compact,
Rule::running_op => SysOp::ListRunning, Rule::running_op => SysOp::ListRunning,
Rule::kill_op => { Rule::kill_op => {
let i_str = inner.into_inner().next().unwrap(); let i_expr = inner.into_inner().next().unwrap();
let i = i_str let i_val = build_expr(i_expr, param_pool)?;
.as_str() let i_val = i_val.eval_to_const()?;
.parse::<u64>() let i_val = i_val
.map_err(|_| ProcessIdError(i_str.as_str().to_string(), i_str.extract_span()))?; .get_int()
SysOp::KillRunning(i) .ok_or_else(|| miette!("Process ID must be an integer"))?;
SysOp::KillRunning(i_val as u64)
} }
Rule::explain_op => { Rule::explain_op => {
let prog = parse_query( let prog = parse_query(
@ -128,7 +130,12 @@ pub(crate) fn parse_sys(
let op = clause_inner.next().unwrap(); let op = clause_inner.next().unwrap();
let script = clause_inner.next().unwrap(); let script = clause_inner.next().unwrap();
let script_str = script.as_str(); 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() { match op.as_rule() {
Rule::trigger_put => puts.push(script_str.to_string()), Rule::trigger_put => puts.push(script_str.to_string()),
Rule::trigger_rm => rms.push(script_str.to_string()), Rule::trigger_rm => rms.push(script_str.to_string()),

@ -1186,9 +1186,9 @@ impl Poison {
#[inline(always)] #[inline(always)]
pub(crate) fn check(&self) -> Result<()> { pub(crate) fn check(&self) -> Result<()> {
#[derive(Debug, Error, Diagnostic)] #[derive(Debug, Error, Diagnostic)]
#[error("Process is killed before completion")] #[error("Running query is killed before completion")]
#[diagnostic(code(eval::killed))] #[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; struct ProcessKilled;
if self.0.load(Ordering::Relaxed) { if self.0.load(Ordering::Relaxed) {

@ -56,3 +56,4 @@ prettytable = "0.10.0"
rustyline = "10.0.0" rustyline = "10.0.0"
minreq = { version = "2.6.0", features = ["https-rustls"] } minreq = { version = "2.6.0", features = ["https-rustls"] }
miette = { version = "5.5.0", features = ["fancy"] } miette = { version = "5.5.0", features = ["fancy"] }
ctrlc = "3.2.4"

@ -89,6 +89,21 @@ fn main() {
} }
if args.repl { 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) { if let Err(e) = repl_main(db) {
eprintln!("{}", e); eprintln!("{}", e);
exit(-1); exit(-1);

@ -70,6 +70,11 @@ pub(crate) fn repl_main(db: DbInstance) -> Result<(), Box<dyn Error>> {
let mut save_next: Option<String> = None; let mut save_next: Option<String> = None;
rl.set_helper(Some(Indented)); 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 { loop {
let readline = rl.readline("=> "); let readline = rl.readline("=> ");
match readline { match readline {
@ -78,6 +83,7 @@ pub(crate) fn repl_main(db: DbInstance) -> Result<(), Box<dyn Error>> {
eprintln!("{:?}", err); eprintln!("{:?}", err);
} }
rl.add_history_entry(line); rl.add_history_entry(line);
exit = false;
} }
Err(rustyline::error::ReadlineError::Interrupted) => { Err(rustyline::error::ReadlineError::Interrupted) => {
if exit { if exit {
@ -91,6 +97,10 @@ pub(crate) fn repl_main(db: DbInstance) -> Result<(), Box<dyn Error>> {
Err(e) => eprintln!("{:?}", e), Err(e) => eprintln!("{:?}", e),
} }
} }
if rl.save_history(history_file).is_ok() {
eprintln!("Query history saved in {}", history_file);
}
Ok(()) Ok(())
} }

Loading…
Cancel
Save