From 969ab244c259adc6990781f1426e0a941c237a42 Mon Sep 17 00:00:00 2001 From: Konstantinos Artopoulos Date: Wed, 18 Dec 2024 00:00:29 +0200 Subject: [PATCH 1/3] feat(cli): added .tables command --- cli/app.rs | 85 ++++++++++++++++++++++++++++++++++++--- testing/cmdlineshell.test | 9 +++++ 2 files changed, 89 insertions(+), 5 deletions(-) diff --git a/cli/app.rs b/cli/app.rs index 496a56b2a..c1a5fcd43 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,21 +742,27 @@ 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 +10. Set the value 'NULL' to be displayed for null values instead of empty string: + .nullvalue "NULL" + Note: - All SQL commands must end with a semicolon (;). - Special commands do not require a semicolon."#; diff --git a/testing/cmdlineshell.test b/testing/cmdlineshell.test index bb4183bcd..1112e0aaa 100755 --- a/testing/cmdlineshell.test +++ b/testing/cmdlineshell.test @@ -95,3 +95,12 @@ 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"} + +do_execsql_test_on_specific_db testing/testing.db schema-1 { + .tables us% +} {"users"} From ba676b6eadb8ea6b9e95650e8f1b23c8010d83bd Mon Sep 17 00:00:00 2001 From: Konstantinos Artopoulos Date: Wed, 18 Dec 2024 00:13:42 +0200 Subject: [PATCH 2/3] fix: remove old help menu item that sneaked in on rebase --- cli/app.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/cli/app.rs b/cli/app.rs index c1a5fcd43..a6bc003a9 100644 --- a/cli/app.rs +++ b/cli/app.rs @@ -760,9 +760,6 @@ Usage Examples: 9. Show the current values of settings: .show -10. Set the value 'NULL' to be displayed for null values instead of empty string: - .nullvalue "NULL" - Note: - All SQL commands must end with a semicolon (;). - Special commands do not require a semicolon."#; From fb2908b3e9456c65872af5fb57e901a01c1b8a78 Mon Sep 17 00:00:00 2001 From: Konstantinos Artopoulos Date: Wed, 18 Dec 2024 09:10:37 +0200 Subject: [PATCH 3/3] refactor(testing): move .table tests to shelltests.py --- testing/cmdlineshell.test | 4 ---- testing/shelltests.py | 5 +++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/testing/cmdlineshell.test b/testing/cmdlineshell.test index 1112e0aaa..73c6377ec 100755 --- a/testing/cmdlineshell.test +++ b/testing/cmdlineshell.test @@ -100,7 +100,3 @@ do_execsql_test_on_specific_db testing/testing.db schema-2 { # do_execsql_test_on_specific_db testing/testing.db schema-1 { # .tables # } {"products users"} - -do_execsql_test_on_specific_db testing/testing.db schema-1 { - .tables us% -} {"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:")