mirror of
https://github.com/nushell/nushell
synced 2025-01-15 14:44:14 +00:00
parent
be3f0edc97
commit
5319544481
13 changed files with 742 additions and 12 deletions
245
crates/nu-command/src/database/commands/info.rs
Normal file
245
crates/nu-command/src/database/commands/info.rs
Normal file
|
@ -0,0 +1,245 @@
|
|||
use super::super::SQLiteDatabase;
|
||||
use crate::database::values::db_row::DbRow;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct InfoDb;
|
||||
|
||||
impl Command for InfoDb {
|
||||
fn name(&self) -> &str {
|
||||
"db info"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("db", SyntaxShape::Filepath, "sqlite database file name")
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Show database information."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Show information of a SQLite database",
|
||||
example: r#"db info foo.db"#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "info", "SQLite"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let db_file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
||||
let span = db_file.span;
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
let sqlite_db = SQLiteDatabase::try_from_path(db_file.item.as_path(), db_file.span)?;
|
||||
let conn = sqlite_db.open_connection().map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Error opening file".into(),
|
||||
e.to_string(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let dbs = sqlite_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(),
|
||||
)
|
||||
})?;
|
||||
|
||||
cols.push("db_filename".into());
|
||||
vals.push(Value::String {
|
||||
val: db_file.item.to_string_lossy().to_string(),
|
||||
span,
|
||||
});
|
||||
|
||||
for db in dbs {
|
||||
let tables = db.tables();
|
||||
let mut table_list: Vec<Value> = vec![];
|
||||
let mut table_names = vec![];
|
||||
let mut table_values = vec![];
|
||||
for table in tables {
|
||||
let columns = sqlite_db.get_columns(&conn, &table).map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Error getting database columns".into(),
|
||||
e.to_string(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
})?;
|
||||
// a record of column name = column value
|
||||
let mut column_info = vec![];
|
||||
for t in columns {
|
||||
let mut col_names = vec![];
|
||||
let mut col_values = vec![];
|
||||
let fields = t.fields();
|
||||
let columns = t.columns();
|
||||
for (k, v) in fields.iter().zip(columns.iter()) {
|
||||
col_names.push(k.clone());
|
||||
col_values.push(Value::string(v.clone(), span));
|
||||
}
|
||||
column_info.push(Value::Record {
|
||||
cols: col_names.clone(),
|
||||
vals: col_values.clone(),
|
||||
span,
|
||||
});
|
||||
}
|
||||
|
||||
let constraints = sqlite_db.get_constraints(&conn, &table).map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Error getting DB constraints".into(),
|
||||
e.to_string(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
})?;
|
||||
let mut constraint_info = vec![];
|
||||
for constraint in constraints {
|
||||
let mut con_cols = vec![];
|
||||
let mut con_vals = vec![];
|
||||
let fields = constraint.fields();
|
||||
let columns = constraint.columns();
|
||||
for (k, v) in fields.iter().zip(columns.iter()) {
|
||||
con_cols.push(k.clone());
|
||||
con_vals.push(Value::string(v.clone(), span));
|
||||
}
|
||||
constraint_info.push(Value::Record {
|
||||
cols: con_cols.clone(),
|
||||
vals: con_vals.clone(),
|
||||
span,
|
||||
});
|
||||
}
|
||||
|
||||
let foreign_keys = sqlite_db.get_foreign_keys(&conn, &table).map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Error getting DB Foreign Keys".into(),
|
||||
e.to_string(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
})?;
|
||||
let mut foreign_key_info = vec![];
|
||||
for fk in foreign_keys {
|
||||
let mut fk_cols = vec![];
|
||||
let mut fk_vals = vec![];
|
||||
let fields = fk.fields();
|
||||
let columns = fk.columns();
|
||||
for (k, v) in fields.iter().zip(columns.iter()) {
|
||||
fk_cols.push(k.clone());
|
||||
fk_vals.push(Value::string(v.clone(), span));
|
||||
}
|
||||
foreign_key_info.push(Value::Record {
|
||||
cols: fk_cols.clone(),
|
||||
vals: fk_vals.clone(),
|
||||
span,
|
||||
});
|
||||
}
|
||||
|
||||
let indexes = sqlite_db.get_indexes(&conn, &table).map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Error getting DB Indexes".into(),
|
||||
e.to_string(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
})?;
|
||||
let mut index_info = vec![];
|
||||
for index in indexes {
|
||||
let mut idx_cols = vec![];
|
||||
let mut idx_vals = vec![];
|
||||
let fields = index.fields();
|
||||
let columns = index.columns();
|
||||
for (k, v) in fields.iter().zip(columns.iter()) {
|
||||
idx_cols.push(k.clone());
|
||||
idx_vals.push(Value::string(v.clone(), span));
|
||||
}
|
||||
index_info.push(Value::Record {
|
||||
cols: idx_cols.clone(),
|
||||
vals: idx_vals.clone(),
|
||||
span,
|
||||
});
|
||||
}
|
||||
|
||||
table_names.push(table.name);
|
||||
table_values.push(Value::Record {
|
||||
cols: vec![
|
||||
"columns".into(),
|
||||
"constraints".into(),
|
||||
"foreign_keys".into(),
|
||||
"indexes".into(),
|
||||
],
|
||||
vals: vec![
|
||||
Value::List {
|
||||
vals: column_info,
|
||||
span,
|
||||
},
|
||||
Value::List {
|
||||
vals: constraint_info,
|
||||
span,
|
||||
},
|
||||
Value::List {
|
||||
vals: foreign_key_info,
|
||||
span,
|
||||
},
|
||||
Value::List {
|
||||
vals: index_info,
|
||||
span,
|
||||
},
|
||||
],
|
||||
span,
|
||||
});
|
||||
}
|
||||
table_list.push(Value::Record {
|
||||
cols: table_names,
|
||||
vals: table_values,
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("databases".into());
|
||||
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(
|
||||
Value::Record { cols, vals, span },
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ mod collect;
|
|||
mod command;
|
||||
mod describe;
|
||||
mod from;
|
||||
mod info;
|
||||
mod open;
|
||||
mod query;
|
||||
mod select;
|
||||
|
@ -11,6 +12,7 @@ use collect::CollectDb;
|
|||
use command::Database;
|
||||
use describe::DescribeDb;
|
||||
use from::FromDb;
|
||||
use info::InfoDb;
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
use open::OpenDb;
|
||||
use query::QueryDb;
|
||||
|
@ -27,5 +29,5 @@ pub fn add_database_decls(working_set: &mut StateWorkingSet) {
|
|||
}
|
||||
|
||||
// Series commands
|
||||
bind_command!(CollectDb, Database, DescribeDb, FromDb, QueryDb, SelectDb, OpenDb);
|
||||
bind_command!(CollectDb, Database, DescribeDb, FromDb, QueryDb, SelectDb, OpenDb, InfoDb);
|
||||
}
|
||||
|
|
|
@ -2,4 +2,7 @@ mod commands;
|
|||
mod values;
|
||||
|
||||
pub use commands::add_database_decls;
|
||||
pub(crate) use values::SQLiteDatabase;
|
||||
pub use values::{
|
||||
convert_sqlite_row_to_nu_value, convert_sqlite_value_to_nu_value, open_and_read_sqlite_db,
|
||||
open_connection_in_memory, read_sqlite_db, SQLiteDatabase,
|
||||
};
|
||||
|
|
27
crates/nu-command/src/database/values/db.rs
Normal file
27
crates/nu-command/src/database/values/db.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
use crate::database::values::db_table::DbTable;
|
||||
|
||||
// Thank you gobang
|
||||
// https://github.com/TaKO8Ki/gobang/blob/main/database-tree/src/lib.rs
|
||||
|
||||
#[derive(Clone, PartialEq, 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()
|
||||
}
|
||||
}
|
51
crates/nu-command/src/database/values/db_column.rs
Normal file
51
crates/nu-command/src/database/values/db_column.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
use crate::database::values::db_row::DbRow;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DbColumn {
|
||||
/// Column Index
|
||||
pub cid: Option<i32>,
|
||||
/// Column Name
|
||||
pub name: Option<String>,
|
||||
/// Column Type
|
||||
pub r#type: Option<String>,
|
||||
/// Column has a NOT NULL constraint
|
||||
pub notnull: Option<i16>,
|
||||
/// Column DEFAULT Value
|
||||
pub default: Option<String>,
|
||||
/// Column is part of the PRIMARY KEY
|
||||
pub pk: Option<i16>,
|
||||
}
|
||||
|
||||
impl DbRow for DbColumn {
|
||||
fn fields(&self) -> Vec<String> {
|
||||
vec![
|
||||
"cid".to_string(),
|
||||
"name".to_string(),
|
||||
"type".to_string(),
|
||||
"notnull".to_string(),
|
||||
"default".to_string(),
|
||||
"pk".to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
fn columns(&self) -> Vec<String> {
|
||||
vec![
|
||||
self.cid
|
||||
.as_ref()
|
||||
.map_or(String::new(), |cid| cid.to_string()),
|
||||
self.name
|
||||
.as_ref()
|
||||
.map_or(String::new(), |name| name.to_string()),
|
||||
self.r#type
|
||||
.as_ref()
|
||||
.map_or(String::new(), |r#type| r#type.to_string()),
|
||||
self.notnull
|
||||
.as_ref()
|
||||
.map_or(String::new(), |notnull| notnull.to_string()),
|
||||
self.default
|
||||
.as_ref()
|
||||
.map_or(String::new(), |default| default.to_string()),
|
||||
self.pk.as_ref().map_or(String::new(), |pk| pk.to_string()),
|
||||
]
|
||||
}
|
||||
}
|
26
crates/nu-command/src/database/values/db_constraint.rs
Normal file
26
crates/nu-command/src/database/values/db_constraint.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use crate::database::values::db_row::DbRow;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DbConstraint {
|
||||
pub name: String,
|
||||
pub column_name: String,
|
||||
pub origin: String,
|
||||
}
|
||||
|
||||
impl DbRow for DbConstraint {
|
||||
fn fields(&self) -> Vec<String> {
|
||||
vec![
|
||||
"name".to_string(),
|
||||
"column_name".to_string(),
|
||||
"origin".to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
fn columns(&self) -> Vec<String> {
|
||||
vec![
|
||||
self.name.to_string(),
|
||||
self.column_name.to_string(),
|
||||
self.origin.to_string(),
|
||||
]
|
||||
}
|
||||
}
|
32
crates/nu-command/src/database/values/db_foreignkey.rs
Normal file
32
crates/nu-command/src/database/values/db_foreignkey.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use crate::database::values::db_row::DbRow;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DbForeignKey {
|
||||
pub column_name: Option<String>,
|
||||
pub ref_table: Option<String>,
|
||||
pub ref_column: Option<String>,
|
||||
}
|
||||
|
||||
impl DbRow for DbForeignKey {
|
||||
fn fields(&self) -> Vec<String> {
|
||||
vec![
|
||||
"column_name".to_string(),
|
||||
"ref_table".to_string(),
|
||||
"ref_column".to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
fn columns(&self) -> Vec<String> {
|
||||
vec![
|
||||
self.column_name
|
||||
.as_ref()
|
||||
.map_or(String::new(), |r#type| r#type.to_string()),
|
||||
self.ref_table
|
||||
.as_ref()
|
||||
.map_or(String::new(), |r#type| r#type.to_string()),
|
||||
self.ref_column
|
||||
.as_ref()
|
||||
.map_or(String::new(), |r#type| r#type.to_string()),
|
||||
]
|
||||
}
|
||||
}
|
32
crates/nu-command/src/database/values/db_index.rs
Normal file
32
crates/nu-command/src/database/values/db_index.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use crate::database::values::db_row::DbRow;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DbIndex {
|
||||
pub name: Option<String>,
|
||||
pub column_name: Option<String>,
|
||||
pub seqno: Option<i16>,
|
||||
}
|
||||
|
||||
impl DbRow for DbIndex {
|
||||
fn fields(&self) -> Vec<String> {
|
||||
vec![
|
||||
"name".to_string(),
|
||||
"column_name".to_string(),
|
||||
"seqno".to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
fn columns(&self) -> Vec<String> {
|
||||
vec![
|
||||
self.name
|
||||
.as_ref()
|
||||
.map_or(String::new(), |name| name.to_string()),
|
||||
self.column_name
|
||||
.as_ref()
|
||||
.map_or(String::new(), |column_name| column_name.to_string()),
|
||||
self.seqno
|
||||
.as_ref()
|
||||
.map_or(String::new(), |seqno| seqno.to_string()),
|
||||
]
|
||||
}
|
||||
}
|
4
crates/nu-command/src/database/values/db_row.rs
Normal file
4
crates/nu-command/src/database/values/db_row.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
pub trait DbRow: std::marker::Send {
|
||||
fn fields(&self) -> Vec<String>;
|
||||
fn columns(&self) -> Vec<String>;
|
||||
}
|
7
crates/nu-command/src/database/values/db_schema.rs
Normal file
7
crates/nu-command/src/database/values/db_schema.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
use crate::database::values::db_table::DbTable;
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct DbSchema {
|
||||
pub name: String,
|
||||
pub tables: Vec<DbTable>,
|
||||
}
|
8
crates/nu-command/src/database/values/db_table.rs
Normal file
8
crates/nu-command/src/database/values/db_table.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DbTable {
|
||||
pub name: String,
|
||||
pub create_time: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub update_time: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub engine: Option<String>,
|
||||
pub schema: Option<String>,
|
||||
}
|
|
@ -1,3 +1,14 @@
|
|||
mod sqlite;
|
||||
pub mod db;
|
||||
pub mod db_column;
|
||||
pub mod db_constraint;
|
||||
pub mod db_foreignkey;
|
||||
pub mod db_index;
|
||||
pub mod db_row;
|
||||
pub mod db_schema;
|
||||
pub mod db_table;
|
||||
pub mod sqlite;
|
||||
|
||||
pub(crate) use sqlite::SQLiteDatabase;
|
||||
pub use sqlite::{
|
||||
convert_sqlite_row_to_nu_value, convert_sqlite_value_to_nu_value, open_and_read_sqlite_db,
|
||||
open_connection_in_memory, read_sqlite_db, SQLiteDatabase,
|
||||
};
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
use crate::database::values::{
|
||||
db::Db, db_column::DbColumn, db_constraint::DbConstraint, db_foreignkey::DbForeignKey,
|
||||
db_index::DbIndex, db_table::DbTable,
|
||||
};
|
||||
use nu_protocol::{CustomValue, PipelineData, ShellError, Span, Spanned, Value};
|
||||
use rusqlite::{types::ValueRef, Connection, Row};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use sqlparser::ast::Query;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::Read,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use nu_protocol::{CustomValue, PipelineData, ShellError, Span, Spanned, Value};
|
||||
use rusqlite::{types::ValueRef, Connection, Row};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use sqlparser::ast::Query;
|
||||
|
||||
const SQLITE_MAGIC_BYTES: &[u8] = "SQLite format 3\0".as_bytes();
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -184,6 +186,207 @@ impl SQLiteDatabase {
|
|||
span,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_connection(&self) -> Result<Connection, rusqlite::Error> {
|
||||
let conn = match Connection::open(self.path.to_string_lossy().to_string()) {
|
||||
Ok(conn) => conn,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
|
||||
Ok(conn)
|
||||
}
|
||||
|
||||
pub fn get_databases_and_tables(&self, conn: &Connection) -> Result<Vec<Db>, rusqlite::Error> {
|
||||
// let conn = open_connection(path)?;
|
||||
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> {
|
||||
let mut table_names =
|
||||
conn.prepare("SELECT name FROM sqlite_master WHERE type = 'table'")?;
|
||||
let rows = table_names.query_map([], |row| row.get(0))?;
|
||||
let mut tables = Vec::new();
|
||||
|
||||
for row in rows {
|
||||
let table_name: String = row?;
|
||||
tables.push(DbTable {
|
||||
name: table_name,
|
||||
create_time: None,
|
||||
update_time: None,
|
||||
engine: None,
|
||||
schema: None,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(tables.into_iter().collect())
|
||||
}
|
||||
|
||||
fn get_column_info(&self, row: &Row) -> Result<DbColumn, rusqlite::Error> {
|
||||
let dbc = DbColumn {
|
||||
cid: row.get("cid")?,
|
||||
name: row.get("name")?,
|
||||
r#type: row.get("type")?,
|
||||
notnull: row.get("notnull")?,
|
||||
default: row.get("dflt_value")?,
|
||||
pk: row.get("pk")?,
|
||||
};
|
||||
Ok(dbc)
|
||||
}
|
||||
|
||||
pub fn get_columns(
|
||||
&self,
|
||||
conn: &Connection,
|
||||
table: &DbTable,
|
||||
) -> Result<Vec<DbColumn>, rusqlite::Error> {
|
||||
let mut column_names = conn.prepare(&format!(
|
||||
"SELECT * FROM pragma_table_info('{}');",
|
||||
table.name
|
||||
))?;
|
||||
|
||||
let mut columns: Vec<DbColumn> = Vec::new();
|
||||
let rows = column_names.query_and_then([], |row| self.get_column_info(row))?;
|
||||
|
||||
for row in rows {
|
||||
columns.push(row?);
|
||||
}
|
||||
|
||||
Ok(columns)
|
||||
}
|
||||
|
||||
fn get_constraint_info(&self, row: &Row) -> Result<DbConstraint, rusqlite::Error> {
|
||||
let dbc = DbConstraint {
|
||||
name: row.get("index_name")?,
|
||||
column_name: row.get("column_name")?,
|
||||
origin: row.get("origin")?,
|
||||
};
|
||||
Ok(dbc)
|
||||
}
|
||||
|
||||
pub fn get_constraints(
|
||||
&self,
|
||||
conn: &Connection,
|
||||
table: &DbTable,
|
||||
) -> Result<Vec<DbConstraint>, rusqlite::Error> {
|
||||
let mut column_names = conn.prepare(&format!(
|
||||
"
|
||||
SELECT
|
||||
p.origin,
|
||||
s.name AS index_name,
|
||||
i.name AS column_name
|
||||
FROM
|
||||
sqlite_master s
|
||||
JOIN pragma_index_list(s.tbl_name) p ON s.name = p.name,
|
||||
pragma_index_info(s.name) i
|
||||
WHERE
|
||||
s.type = 'index'
|
||||
AND tbl_name = '{}'
|
||||
AND NOT p.origin = 'c'
|
||||
",
|
||||
&table.name
|
||||
))?;
|
||||
|
||||
let mut constraints: Vec<DbConstraint> = Vec::new();
|
||||
let rows = column_names.query_and_then([], |row| self.get_constraint_info(row))?;
|
||||
|
||||
for row in rows {
|
||||
constraints.push(row?);
|
||||
}
|
||||
|
||||
Ok(constraints)
|
||||
}
|
||||
|
||||
fn get_foreign_keys_info(&self, row: &Row) -> Result<DbForeignKey, rusqlite::Error> {
|
||||
let dbc = DbForeignKey {
|
||||
column_name: row.get("from")?,
|
||||
ref_table: row.get("table")?,
|
||||
ref_column: row.get("to")?,
|
||||
};
|
||||
Ok(dbc)
|
||||
}
|
||||
|
||||
pub fn get_foreign_keys(
|
||||
&self,
|
||||
conn: &Connection,
|
||||
table: &DbTable,
|
||||
) -> Result<Vec<DbForeignKey>, rusqlite::Error> {
|
||||
let mut column_names = conn.prepare(&format!(
|
||||
"SELECT p.`from`, p.`to`, p.`table` FROM pragma_foreign_key_list('{}') p",
|
||||
&table.name
|
||||
))?;
|
||||
|
||||
let mut foreign_keys: Vec<DbForeignKey> = Vec::new();
|
||||
let rows = column_names.query_and_then([], |row| self.get_foreign_keys_info(row))?;
|
||||
|
||||
for row in rows {
|
||||
foreign_keys.push(row?);
|
||||
}
|
||||
|
||||
Ok(foreign_keys)
|
||||
}
|
||||
|
||||
fn get_index_info(&self, row: &Row) -> Result<DbIndex, rusqlite::Error> {
|
||||
let dbc = DbIndex {
|
||||
name: row.get("index_name")?,
|
||||
column_name: row.get("name")?,
|
||||
seqno: row.get("seqno")?,
|
||||
};
|
||||
Ok(dbc)
|
||||
}
|
||||
|
||||
pub fn get_indexes(
|
||||
&self,
|
||||
conn: &Connection,
|
||||
table: &DbTable,
|
||||
) -> Result<Vec<DbIndex>, rusqlite::Error> {
|
||||
let mut column_names = conn.prepare(&format!(
|
||||
"
|
||||
SELECT
|
||||
m.name AS index_name,
|
||||
p.*
|
||||
FROM
|
||||
sqlite_master m,
|
||||
pragma_index_info(m.name) p
|
||||
WHERE
|
||||
m.type = 'index'
|
||||
AND m.tbl_name = '{}'
|
||||
",
|
||||
&table.name,
|
||||
))?;
|
||||
|
||||
let mut indexes: Vec<DbIndex> = Vec::new();
|
||||
let rows = column_names.query_and_then([], |row| self.get_index_info(row))?;
|
||||
|
||||
for row in rows {
|
||||
indexes.push(row?);
|
||||
}
|
||||
|
||||
Ok(indexes)
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomValue for SQLiteDatabase {
|
||||
|
@ -329,7 +532,7 @@ fn read_entire_sqlite_db(conn: Connection, call_span: Span) -> Result<Value, rus
|
|||
span: call_span,
|
||||
})
|
||||
}
|
||||
fn convert_sqlite_row_to_nu_value(row: &Row, span: Span) -> Value {
|
||||
pub fn convert_sqlite_row_to_nu_value(row: &Row, span: Span) -> Value {
|
||||
let mut vals = Vec::new();
|
||||
let colnamestr = row.as_ref().column_names().to_vec();
|
||||
let colnames = colnamestr.iter().map(|s| s.to_string()).collect();
|
||||
|
@ -347,7 +550,7 @@ fn convert_sqlite_row_to_nu_value(row: &Row, span: Span) -> Value {
|
|||
}
|
||||
}
|
||||
|
||||
fn convert_sqlite_value_to_nu_value(value: ValueRef, span: Span) -> Value {
|
||||
pub fn convert_sqlite_value_to_nu_value(value: ValueRef, span: Span) -> Value {
|
||||
match value {
|
||||
ValueRef::Null => Value::Nothing { span },
|
||||
ValueRef::Integer(i) => Value::Int { val: i, span },
|
||||
|
@ -469,3 +672,82 @@ mod test {
|
|||
assert_eq!(converted_db, expected);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------
|
||||
pub fn open_connection_in_memory() -> Result<Connection, ShellError> {
|
||||
let db = match Connection::open_in_memory() {
|
||||
Ok(conn) => conn,
|
||||
Err(err) => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Failed to open SQLite connection in memory".into(),
|
||||
err.to_string(),
|
||||
Some(Span::test_data()),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
pub fn open_and_read_sqlite_db(
|
||||
path: &Path,
|
||||
call_span: Span,
|
||||
) -> Result<Value, nu_protocol::ShellError> {
|
||||
let path = path.to_string_lossy().to_string();
|
||||
|
||||
match Connection::open(path) {
|
||||
Ok(conn) => match read_sqlite_db(conn, call_span) {
|
||||
Ok(data) => Ok(data),
|
||||
Err(err) => Err(ShellError::GenericError(
|
||||
"Failed to read from SQLite database".into(),
|
||||
err.to_string(),
|
||||
Some(call_span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
},
|
||||
Err(err) => Err(ShellError::GenericError(
|
||||
"Failed to open SQLite database".into(),
|
||||
err.to_string(),
|
||||
Some(call_span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_sqlite_db(conn: Connection, call_span: Span) -> Result<Value, rusqlite::Error> {
|
||||
let mut table_names: Vec<String> = Vec::new();
|
||||
let mut tables: Vec<Value> = Vec::new();
|
||||
|
||||
let mut get_table_names =
|
||||
conn.prepare("SELECT name from sqlite_master where type = 'table'")?;
|
||||
let rows = get_table_names.query_map([], |row| row.get(0))?;
|
||||
|
||||
for row in rows {
|
||||
let table_name: String = row?;
|
||||
table_names.push(table_name.clone());
|
||||
|
||||
let mut rows = Vec::new();
|
||||
let mut table_stmt = conn.prepare(&format!("select * from [{}]", table_name))?;
|
||||
let mut table_rows = table_stmt.query([])?;
|
||||
while let Some(table_row) = table_rows.next()? {
|
||||
rows.push(convert_sqlite_row_to_nu_value(table_row, call_span))
|
||||
}
|
||||
|
||||
let table_record = Value::List {
|
||||
vals: rows,
|
||||
span: call_span,
|
||||
};
|
||||
|
||||
tables.push(table_record);
|
||||
}
|
||||
|
||||
Ok(Value::Record {
|
||||
cols: table_names,
|
||||
vals: tables,
|
||||
span: call_span,
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue