diff --git a/cli/app.rs b/cli/app.rs index 496a56b2a..a6bc003a9 100644 --- a/cli/app.rs +++ b/cli/app.rs @@ -76,6 +76,8 @@ pub enum Command { NullValue, /// Toggle 'echo' mode to repeat commands before execution Echo, + /// Display tables + Tables, } impl Command { @@ -86,6 +88,7 @@ impl Command { | Self::Help | Self::Opcodes | Self::ShowInfo + | Self::Tables | Self::SetOutput => 0, Self::Open | Self::OutputMode | Self::Cwd | Self::Echo | Self::NullValue => 1, } + 1) // argv0 @@ -104,6 +107,7 @@ impl Command { Self::ShowInfo => ".show", Self::NullValue => ".nullvalue ", Self::Echo => ".echo on|off", + Self::Tables => ".tables", } } } @@ -116,6 +120,7 @@ impl FromStr for Command { ".open" => Ok(Self::Open), ".help" => Ok(Self::Help), ".schema" => Ok(Self::Schema), + ".tables" => Ok(Self::Tables), ".opcodes" => Ok(Self::Opcodes), ".mode" => Ok(Self::OutputMode), ".output" => Ok(Self::SetOutput), @@ -421,6 +426,12 @@ impl Limbo { let _ = self.writeln(e.to_string()); } } + Command::Tables => { + let pattern = args.get(1).copied(); + if let Err(e) = self.display_tables(pattern) { + let _ = self.writeln(e.to_string()); + } + } Command::Opcodes => { if args.len() > 1 { for op in &OPCODE_DESCRIPTIONS { @@ -621,6 +632,63 @@ impl Limbo { Ok(()) } + + fn display_tables(&mut self, pattern: Option<&str>) -> anyhow::Result<()> { + let sql = match pattern { + Some(pattern) => format!( + "SELECT name FROM sqlite_schema WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name LIKE '{}' ORDER BY 1", + pattern + ), + None => String::from( + "SELECT name FROM sqlite_schema WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY 1" + ), + }; + + match self.conn.query(&sql) { + Ok(Some(ref mut rows)) => { + let mut tables = String::new(); + loop { + match rows.next_row()? { + RowResult::Row(row) => { + if let Some(Value::Text(table)) = row.values.first() { + tables.push_str(table); + tables.push(' '); + } + } + RowResult::IO => { + self.io.run_once()?; + } + RowResult::Done => break, + } + } + + if tables.len() > 0 { + let _ = self.writeln(tables.trim_end()); + } else { + if let Some(pattern) = pattern { + let _ = self.write_fmt(format_args!( + "Error: Tables with pattern '{}' not found.", + pattern + )); + } else { + let _ = self.writeln("No tables found in the database."); + } + } + } + Ok(None) => { + let _ = self.writeln("No results returned from the query."); + } + Err(err) => { + if err.to_string().contains("no such table: sqlite_schema") { + return Err(anyhow::anyhow!("Unable to access database schema. The database may be using an older SQLite version or may not be properly initialized.")); + } else { + return Err(anyhow::anyhow!("Error querying schema: {}", err)); + } + } + } + + Ok(()) + } } fn get_writer(output: &str) -> Box { @@ -656,6 +724,7 @@ Special Commands: .open Open and connect to a database file. .output Change the output mode. Available modes are 'raw' and 'pretty'. .schema Show the schema of the specified table. +.tables List names of tables matching LIKE pattern TABLE .opcodes Display all the opcodes defined by the virtual machine .cd Change the current working directory. .nullvalue Set the value to be displayed for null values. @@ -673,19 +742,22 @@ Usage Examples: 3. To view the schema of a table named 'employees': .schema employees -4. To list all available SQL opcodes: +4. To list all tables: + .tables + +5. To list all available SQL opcodes: .opcodes -5. To change the current output mode to 'pretty': +6. To change the current output mode to 'pretty': .mode pretty -6. Send output to STDOUT if no file is specified: +7. Send output to STDOUT if no file is specified: .output -7. To change the current working directory to '/tmp': +8. To change the current working directory to '/tmp': .cd /tmp -8. Show the current values of settings: +9. Show the current values of settings: .show Note: diff --git a/testing/cmdlineshell.test b/testing/cmdlineshell.test index bb4183bcd..73c6377ec 100755 --- a/testing/cmdlineshell.test +++ b/testing/cmdlineshell.test @@ -95,3 +95,8 @@ do_execsql_test_on_specific_db testing/testing.db schema-2 { # name TEXT, # price REAL # );"} + +# FIXME sqlite uses multicolumn output mode for display resulting in different spacing +# do_execsql_test_on_specific_db testing/testing.db schema-1 { +# .tables +# } {"products users"} diff --git a/testing/shelltests.py b/testing/shelltests.py index f36972e25..6b1dab121 100755 --- a/testing/shelltests.py +++ b/testing/shelltests.py @@ -182,6 +182,11 @@ Null value: LIMBO CWD: {cwd}/testing Echo: off""", ) + +do_execshell_test(pipe, "test-show-tables", ".tables", "products users") + +do_execshell_test(pipe, "test-show-tables-with-pattern", ".tables us%", "users") + # test we can set the null value write_to_pipe(".open :memory:")