mirror of
https://github.com/nushell/nushell.git
synced 2024-12-25 00:19:39 +01:00
Overhaul schema
command, remove database name (#7344)
This PR changes the `schema` command for viewing the schema of a SQLite
database file. It removes 1 level of nesting (intended to handle
multiple databases in the same connection) that I believe is
unnecessary.
### Before
![image](https://user-images.githubusercontent.com/26268125/205467643-05df0f64-bc8f-4135-9ff1-f978cc7a12bd.png)
### After
![image](https://user-images.githubusercontent.com/26268125/205467655-c4783184-9bde-46e2-9316-0f06acd1abe1.png)
## Rationale
A SQLite database connection can technically be associated with multiple
non-temporary databases using [the ATTACH DATABASE
command](https://www.sqlite.org/lang_attach.html). But it's not possible
to do that _in the context of Nushell_, and so I believe that there is
no benefit to displaying the schema as if there could be multiple
databases.
I initially raised this concern back in April, but we decided to keep
the database nesting because at the time we were still looking into more
generalized database functionality (i.e. not just SQLite). I believe
that rationale no longer applies.
Also, the existing code would not have worked correctly even if a
connection had multiple databases; for every database, it was looking up
tables without filtering them by database:
6295b20545/crates/nu-command/src/database/values/sqlite.rs (L104-L118)
## Future Work
I'd like to add information on views+triggers to the `schema` output.
I'm also working on making it possible to `ctrl+c` reading from a
database (which is turning into a massive yak shave).
This commit is contained in:
parent
6295b20545
commit
e8a55aa647
@ -1,5 +1,5 @@
|
|||||||
use super::super::SQLiteDatabase;
|
use super::super::SQLiteDatabase;
|
||||||
use crate::database::values::definitions::{db::Db, db_row::DbRow, db_table::DbTable};
|
use crate::database::values::definitions::{db_row::DbRow, db_table::DbTable};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
@ -22,7 +22,7 @@ impl Command for SchemaDb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Show SQLite database information, including its schema."
|
"Show the schema of a SQLite database."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -50,17 +50,16 @@ impl Command for SchemaDb {
|
|||||||
|
|
||||||
let sqlite_db = SQLiteDatabase::try_from_pipeline(input, span)?;
|
let sqlite_db = SQLiteDatabase::try_from_pipeline(input, span)?;
|
||||||
let conn = open_sqlite_db_connection(&sqlite_db, span)?;
|
let conn = open_sqlite_db_connection(&sqlite_db, span)?;
|
||||||
let dbs = get_databases_and_tables(&sqlite_db, &conn, span)?;
|
let tables = sqlite_db.get_tables(&conn).map_err(|e| {
|
||||||
|
ShellError::GenericError(
|
||||||
|
"Error reading tables".into(),
|
||||||
|
e.to_string(),
|
||||||
|
Some(span),
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
cols.push("db_filename".into());
|
|
||||||
vals.push(Value::String {
|
|
||||||
val: sqlite_db.path.to_string_lossy().into(),
|
|
||||||
span,
|
|
||||||
});
|
|
||||||
|
|
||||||
for db in dbs {
|
|
||||||
let tables = get_database_tables(&db);
|
|
||||||
let mut table_list: Vec<Value> = vec![];
|
|
||||||
let mut table_names = vec![];
|
let mut table_names = vec![];
|
||||||
let mut table_values = vec![];
|
let mut table_values = vec![];
|
||||||
for table in tables {
|
for table in tables {
|
||||||
@ -69,57 +68,45 @@ impl Command for SchemaDb {
|
|||||||
let foreign_key_info = get_table_foreign_keys(&sqlite_db, &conn, &table, span)?;
|
let foreign_key_info = get_table_foreign_keys(&sqlite_db, &conn, &table, span)?;
|
||||||
let index_info = get_table_indexes(&sqlite_db, &conn, &table, span)?;
|
let index_info = get_table_indexes(&sqlite_db, &conn, &table, span)?;
|
||||||
|
|
||||||
table_names.push(table.name);
|
let mut cols = vec![];
|
||||||
table_values.push(Value::Record {
|
let mut vals = vec![];
|
||||||
cols: vec![
|
|
||||||
"columns".into(),
|
cols.push("columns".into());
|
||||||
"constraints".into(),
|
vals.push(Value::List {
|
||||||
"foreign_keys".into(),
|
|
||||||
"indexes".into(),
|
|
||||||
],
|
|
||||||
vals: vec![
|
|
||||||
Value::List {
|
|
||||||
vals: column_info,
|
vals: column_info,
|
||||||
span,
|
span,
|
||||||
},
|
});
|
||||||
Value::List {
|
|
||||||
|
cols.push("constraints".into());
|
||||||
|
vals.push(Value::List {
|
||||||
vals: constraint_info,
|
vals: constraint_info,
|
||||||
span,
|
span,
|
||||||
},
|
});
|
||||||
Value::List {
|
|
||||||
|
cols.push("foreign_keys".into());
|
||||||
|
vals.push(Value::List {
|
||||||
vals: foreign_key_info,
|
vals: foreign_key_info,
|
||||||
span,
|
span,
|
||||||
},
|
});
|
||||||
Value::List {
|
|
||||||
|
cols.push("indexes".into());
|
||||||
|
vals.push(Value::List {
|
||||||
vals: index_info,
|
vals: index_info,
|
||||||
span,
|
span,
|
||||||
},
|
|
||||||
],
|
|
||||||
span,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
table_names.push(table.name);
|
||||||
|
table_values.push(Value::Record { cols, vals, span });
|
||||||
}
|
}
|
||||||
table_list.push(Value::Record {
|
|
||||||
|
cols.push("tables".into());
|
||||||
|
vals.push(Value::Record {
|
||||||
cols: table_names,
|
cols: table_names,
|
||||||
vals: table_values,
|
vals: table_values,
|
||||||
span,
|
span,
|
||||||
});
|
});
|
||||||
|
|
||||||
cols.push("databases".into());
|
// TODO: add views and triggers
|
||||||
|
|
||||||
let mut rcols = vec![];
|
|
||||||
let mut rvals = vec![];
|
|
||||||
rcols.push("name".into());
|
|
||||||
rvals.push(Value::string(db.name().to_string(), span));
|
|
||||||
|
|
||||||
rcols.push("tables".into());
|
|
||||||
rvals.append(&mut table_list);
|
|
||||||
|
|
||||||
vals.push(Value::Record {
|
|
||||||
cols: rcols,
|
|
||||||
vals: rvals,
|
|
||||||
span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(PipelineData::Value(
|
Ok(PipelineData::Value(
|
||||||
Value::Record { cols, vals, span },
|
Value::Record { cols, vals, span },
|
||||||
@ -140,26 +127,6 @@ fn open_sqlite_db_connection(db: &SQLiteDatabase, span: Span) -> Result<Connecti
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_databases_and_tables(
|
|
||||||
db: &SQLiteDatabase,
|
|
||||||
conn: &Connection,
|
|
||||||
span: Span,
|
|
||||||
) -> Result<Vec<Db>, ShellError> {
|
|
||||||
db.get_databases_and_tables(conn).map_err(|e| {
|
|
||||||
ShellError::GenericError(
|
|
||||||
"Error getting databases and tables".into(),
|
|
||||||
e.to_string(),
|
|
||||||
Some(span),
|
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_database_tables(db: &Db) -> Vec<DbTable> {
|
|
||||||
db.tables()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_table_columns(
|
fn get_table_columns(
|
||||||
db: &SQLiteDatabase,
|
db: &SQLiteDatabase,
|
||||||
conn: &Connection,
|
conn: &Connection,
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
use super::db_table::DbTable;
|
|
||||||
|
|
||||||
// Thank you gobang
|
|
||||||
// https://github.com/TaKO8Ki/gobang/blob/main/database-tree/src/lib.rs
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
|
||||||
pub struct Db {
|
|
||||||
pub name: String,
|
|
||||||
pub tables: Vec<DbTable>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Db {
|
|
||||||
pub fn new(database: String, tables: Vec<DbTable>) -> Self {
|
|
||||||
Self {
|
|
||||||
name: database,
|
|
||||||
tables,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn name(&self) -> &str {
|
|
||||||
self.name.as_str()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tables(&self) -> Vec<DbTable> {
|
|
||||||
self.tables.clone()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,3 @@
|
|||||||
pub mod db;
|
|
||||||
pub mod db_column;
|
pub mod db_column;
|
||||||
pub mod db_constraint;
|
pub mod db_constraint;
|
||||||
pub mod db_foreignkey;
|
pub mod db_foreignkey;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use super::definitions::{
|
use super::definitions::{
|
||||||
db::Db, db_column::DbColumn, db_constraint::DbConstraint, db_foreignkey::DbForeignKey,
|
db_column::DbColumn, db_constraint::DbConstraint, db_foreignkey::DbForeignKey,
|
||||||
db_index::DbIndex, db_table::DbTable,
|
db_index::DbIndex, db_table::DbTable,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -101,35 +101,6 @@ impl SQLiteDatabase {
|
|||||||
Ok(conn)
|
Ok(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_databases_and_tables(&self, conn: &Connection) -> Result<Vec<Db>, rusqlite::Error> {
|
|
||||||
let mut db_query = conn.prepare("SELECT name FROM pragma_database_list")?;
|
|
||||||
|
|
||||||
let databases = db_query.query_map([], |row| {
|
|
||||||
let name: String = row.get(0)?;
|
|
||||||
Ok(Db::new(name, self.get_tables(conn)?))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut db_list = vec![];
|
|
||||||
for db in databases {
|
|
||||||
db_list.push(db?);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(db_list)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_databases(&self, conn: &Connection) -> Result<Vec<String>, rusqlite::Error> {
|
|
||||||
let mut db_query = conn.prepare("SELECT name FROM pragma_database_list")?;
|
|
||||||
|
|
||||||
let mut db_list = vec![];
|
|
||||||
let _ = db_query.query_map([], |row| {
|
|
||||||
let name: String = row.get(0)?;
|
|
||||||
db_list.push(name);
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(db_list)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_tables(&self, conn: &Connection) -> Result<Vec<DbTable>, rusqlite::Error> {
|
pub fn get_tables(&self, conn: &Connection) -> Result<Vec<DbTable>, rusqlite::Error> {
|
||||||
let mut table_names =
|
let mut table_names =
|
||||||
conn.prepare("SELECT name FROM sqlite_master WHERE type = 'table'")?;
|
conn.prepare("SELECT name FROM sqlite_master WHERE type = 'table'")?;
|
||||||
|
Loading…
Reference in New Issue
Block a user