diff --git a/cli/app.rs b/cli/app.rs index 26a99c470..03ee75ffa 100644 --- a/cli/app.rs +++ b/cli/app.rs @@ -3,7 +3,7 @@ use crate::{ opcodes_dictionary::OPCODE_DESCRIPTIONS, }; use cli_table::{Cell, Table}; -use limbo_core::{Database, LimboError, StepResult, Value}; +use limbo_core::{Database, LimboError, Rows, StepResult, Value}; use clap::{Parser, ValueEnum}; use std::{ @@ -304,8 +304,14 @@ impl Limbo { fn handle_first_input(&mut self, cmd: &str) { if cmd.trim().starts_with('.') { self.handle_dot_command(cmd); - } else if let Err(e) = self.query(cmd) { - eprintln!("{}", e); + } else { + let conn = self.conn.clone(); + let runner = conn.query_runner(cmd.as_bytes()); + for output in runner { + if let Err(e) = self.print_query_result(cmd, output) { + let _ = self.writeln(e.to_string()); + } + } } std::process::exit(0); } @@ -443,17 +449,16 @@ impl Limbo { self.buffer_input(line); let buff = self.input_buff.clone(); let echo = self.opts.echo; - buff.split(';') - .map(str::trim) - .filter(|s| !s.is_empty()) - .for_each(|stmt| { - if echo { - let _ = self.writeln(stmt); - } - if let Err(e) = self.query(stmt) { - let _ = self.writeln(e.to_string()); - } - }); + if echo { + let _ = self.writeln(&buff); + } + let conn = self.conn.clone(); + let runner = conn.query_runner(buff.as_bytes()); + for output in runner { + if let Err(e) = self.print_query_result(&buff, output) { + let _ = self.writeln(e.to_string()); + } + } self.reset_input(); } else { self.buffer_input(line); @@ -570,8 +575,12 @@ impl Limbo { } } - pub fn query(&mut self, sql: &str) -> anyhow::Result<()> { - match self.conn.query(sql) { + fn print_query_result( + &mut self, + sql: &str, + mut output: Result, LimboError>, + ) -> anyhow::Result<()> { + match output { Ok(Some(ref mut rows)) => match self.opts.output_mode { OutputMode::Raw => loop { if self.interrupt_count.load(Ordering::SeqCst) > 0 { diff --git a/core/lib.rs b/core/lib.rs index a0bd6d98c..906a1675f 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -298,57 +298,65 @@ impl Connection { pub fn query(self: &Rc, sql: impl Into) -> Result> { let sql = sql.into(); trace!("Querying: {}", sql); - let db = self.db.clone(); - let syms: &SymbolTable = &db.syms.borrow(); let mut parser = Parser::new(sql.as_bytes()); let cmd = parser.next()?; - if let Some(cmd) = cmd { - match cmd { - Cmd::Stmt(stmt) => { - let program = Rc::new(translate::translate( - &self.schema.borrow(), - stmt, - self.header.clone(), - self.pager.clone(), - Rc::downgrade(self), - syms, - )?); - let stmt = Statement::new(program, self.pager.clone()); - Ok(Some(Rows { stmt })) - } - Cmd::Explain(stmt) => { - let program = translate::translate( - &self.schema.borrow(), - stmt, - self.header.clone(), - self.pager.clone(), - Rc::downgrade(self), - syms, - )?; - program.explain(); - Ok(None) - } - Cmd::ExplainQueryPlan(stmt) => { - match stmt { - ast::Stmt::Select(select) => { - let mut plan = prepare_select_plan( - &self.schema.borrow(), - *select, - &self.db.syms.borrow(), - )?; - optimize_plan(&mut plan)?; - println!("{}", plan); - } - _ => todo!(), - } - Ok(None) - } - } - } else { - Ok(None) + match cmd { + Some(cmd) => self.run_cmd(cmd), + None => Ok(None), } } + pub(crate) fn run_cmd(self: &Rc, cmd: Cmd) -> Result> { + let db = self.db.clone(); + let syms: &SymbolTable = &db.syms.borrow(); + + match cmd { + Cmd::Stmt(stmt) => { + let program = Rc::new(translate::translate( + &self.schema.borrow(), + stmt, + self.header.clone(), + self.pager.clone(), + Rc::downgrade(self), + syms, + )?); + let stmt = Statement::new(program, self.pager.clone()); + Ok(Some(Rows { stmt })) + } + Cmd::Explain(stmt) => { + let program = translate::translate( + &self.schema.borrow(), + stmt, + self.header.clone(), + self.pager.clone(), + Rc::downgrade(self), + syms, + )?; + program.explain(); + Ok(None) + } + Cmd::ExplainQueryPlan(stmt) => { + match stmt { + ast::Stmt::Select(select) => { + let mut plan = prepare_select_plan( + &self.schema.borrow(), + *select, + &self.db.syms.borrow(), + )?; + optimize_plan(&mut plan)?; + println!("{}", plan); + } + _ => todo!(), + } + Ok(None) + } + } + } + + pub fn query_runner<'a>(self: &'a Rc, sql: &'a [u8]) -> QueryRunner<'a> { + QueryRunner::new(self, sql) + } + pub fn execute(self: &Rc, sql: impl Into) -> Result<()> { let sql = sql.into(); let db = self.db.clone(); @@ -560,3 +568,29 @@ impl SymbolTable { self.functions.get(name).cloned() } } + +pub struct QueryRunner<'a> { + parser: Parser<'a>, + conn: &'a Rc, +} + +impl<'a> QueryRunner<'a> { + pub(crate) fn new(conn: &'a Rc, statements: &'a [u8]) -> Self { + Self { + parser: Parser::new(statements), + conn, + } + } +} + +impl Iterator for QueryRunner<'_> { + type Item = Result>; + + fn next(&mut self) -> Option { + match self.parser.next() { + Ok(Some(cmd)) => Some(self.conn.run_cmd(cmd)), + Ok(None) => None, + Err(err) => Some(Result::Err(LimboError::from(err))), + } + } +} diff --git a/testing/insert.test b/testing/insert.test index e7a03ad81..731ccfd6c 100755 --- a/testing/insert.test +++ b/testing/insert.test @@ -1,3 +1,9 @@ #!/usr/bin/env tclsh set testdir [file dirname $argv0] -source $testdir/tester.tcl \ No newline at end of file +source $testdir/tester.tcl + +do_execsql_test_on_specific_db {:memory:} basic-insert { + create table temp (t1 integer, primary key (t1)); + insert into temp values (1); + select * from temp; +} {1} \ No newline at end of file