mirror of
https://github.com/nushell/nushell
synced 2025-01-15 22:54:16 +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 command;
|
||||||
mod describe;
|
mod describe;
|
||||||
mod from;
|
mod from;
|
||||||
|
mod info;
|
||||||
mod open;
|
mod open;
|
||||||
mod query;
|
mod query;
|
||||||
mod select;
|
mod select;
|
||||||
|
@ -11,6 +12,7 @@ use collect::CollectDb;
|
||||||
use command::Database;
|
use command::Database;
|
||||||
use describe::DescribeDb;
|
use describe::DescribeDb;
|
||||||
use from::FromDb;
|
use from::FromDb;
|
||||||
|
use info::InfoDb;
|
||||||
use nu_protocol::engine::StateWorkingSet;
|
use nu_protocol::engine::StateWorkingSet;
|
||||||
use open::OpenDb;
|
use open::OpenDb;
|
||||||
use query::QueryDb;
|
use query::QueryDb;
|
||||||
|
@ -27,5 +29,5 @@ pub fn add_database_decls(working_set: &mut StateWorkingSet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Series commands
|
// 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;
|
mod values;
|
||||||
|
|
||||||
pub use commands::add_database_decls;
|
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::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::Read,
|
io::Read,
|
||||||
path::{Path, PathBuf},
|
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();
|
const SQLITE_MAGIC_BYTES: &[u8] = "SQLite format 3\0".as_bytes();
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -184,6 +186,207 @@ impl SQLiteDatabase {
|
||||||
span,
|
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 {
|
impl CustomValue for SQLiteDatabase {
|
||||||
|
@ -329,7 +532,7 @@ fn read_entire_sqlite_db(conn: Connection, call_span: Span) -> Result<Value, rus
|
||||||
span: call_span,
|
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 mut vals = Vec::new();
|
||||||
let colnamestr = row.as_ref().column_names().to_vec();
|
let colnamestr = row.as_ref().column_names().to_vec();
|
||||||
let colnames = colnamestr.iter().map(|s| s.to_string()).collect();
|
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 {
|
match value {
|
||||||
ValueRef::Null => Value::Nothing { span },
|
ValueRef::Null => Value::Nothing { span },
|
||||||
ValueRef::Integer(i) => Value::Int { val: i, span },
|
ValueRef::Integer(i) => Value::Int { val: i, span },
|
||||||
|
@ -469,3 +672,82 @@ mod test {
|
||||||
assert_eq!(converted_db, expected);
|
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