Merge 'Run all statements from sql argument in cli' from Vrishabh

When we had multiple sql statements in cli/interactive shell, we were
only running one from them as  shown below. This PR fixes them.
One added benefit of this is we can add complex sql in the tcl test
files like the changes in insert.test .
sqlite3 output
```
❯ sqlite3  :memory: "select 1;select 2"
1
2
```
Limbo main branch output
```
❯ ./target/debug/limbo.exe :memory: "select 1;select 2;"
1
```
Limbos output with this PR
```
❯ ./target/debug/limbo.exe :memory: "select 1;select 2;"
1
2
```

Reviewed-by: Preston Thorpe <cory.pride83@gmail.com>

Closes #673
This commit is contained in:
Pekka Enberg
2025-01-15 18:44:17 +02:00
3 changed files with 112 additions and 63 deletions

View File

@@ -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<Option<Rows>, 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 {

View File

@@ -298,57 +298,65 @@ impl Connection {
pub fn query(self: &Rc<Connection>, sql: impl Into<String>) -> Result<Option<Rows>> {
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<Connection>, cmd: Cmd) -> Result<Option<Rows>> {
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<Connection>, sql: &'a [u8]) -> QueryRunner<'a> {
QueryRunner::new(self, sql)
}
pub fn execute(self: &Rc<Connection>, sql: impl Into<String>) -> 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<Connection>,
}
impl<'a> QueryRunner<'a> {
pub(crate) fn new(conn: &'a Rc<Connection>, statements: &'a [u8]) -> Self {
Self {
parser: Parser::new(statements),
conn,
}
}
}
impl Iterator for QueryRunner<'_> {
type Item = Result<Option<Rows>>;
fn next(&mut self) -> Option<Self::Item> {
match self.parser.next() {
Ok(Some(cmd)) => Some(self.conn.run_cmd(cmd)),
Ok(None) => None,
Err(err) => Some(Result::Err(LimboError::from(err))),
}
}
}

View File

@@ -1,3 +1,9 @@
#!/usr/bin/env tclsh
set testdir [file dirname $argv0]
source $testdir/tester.tcl
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}