mirror of
https://github.com/nushell/nushell
synced 2024-12-26 21:13:19 +00:00
Remove sqlparser SQLite commands (#7040)
This commit is contained in:
parent
c259ef41bd
commit
5ee096232c
37 changed files with 30 additions and 4088 deletions
|
@ -146,8 +146,8 @@ features = [
|
|||
trash-support = ["trash"]
|
||||
which-support = ["which"]
|
||||
plugin = ["nu-parser/plugin"]
|
||||
dataframe = ["polars", "num"]
|
||||
database = ["sqlparser", "rusqlite"]
|
||||
dataframe = ["polars", "num", "sqlparser"]
|
||||
database = ["rusqlite"] # TODO: given that rusqlite is included in reedline, should we just always include it?
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = { version = "0.16.1", default-features = false }
|
||||
|
|
|
@ -1,182 +0,0 @@
|
|||
use crate::database::values::dsl::ExprDb;
|
||||
|
||||
use super::super::SQLiteDatabase;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{BinaryOperator, Expr, Query, Select, SetExpr, Statement};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AndDb;
|
||||
|
||||
impl Command for AndDb {
|
||||
fn name(&self) -> &str {
|
||||
"and"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Includes an AND clause for a query"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("where", SyntaxShape::Any, "Where expression on the table")
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "where"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Selects a column from a database with an AND clause",
|
||||
example: r#"open db.sqlite
|
||||
| from table table_1
|
||||
| select a
|
||||
| where ((field a) > 1)
|
||||
| and ((field b) == 1)
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a FROM table_1 WHERE a > 1 AND b = 1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Creates a AND clause combined with an expression AND",
|
||||
example: r#"open db.sqlite
|
||||
| from table table_1
|
||||
| select a
|
||||
| where ((field a) > 1 | and ((field a) < 10))
|
||||
| and ((field b) == 1)
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a FROM table_1 WHERE (a > 1 AND a < 10) AND b = 1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let value: Value = call.req(engine_state, stack, 0)?;
|
||||
let expr = ExprDb::try_from_value(&value)?.into_native();
|
||||
|
||||
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||
match db.statement.as_mut() {
|
||||
Some(statement) => match statement {
|
||||
Statement::Query(query) => modify_query(query, expr, call.head)?,
|
||||
s => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Connection doesn't define a query".into(),
|
||||
format!("Expected a connection with query. Got {}", s),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Connection without statement".into(),
|
||||
"The connection needs a statement defined".into(),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
fn modify_query(query: &mut Box<Query>, expression: Expr, span: Span) -> Result<(), ShellError> {
|
||||
match *query.body {
|
||||
SetExpr::Select(ref mut select) => modify_select(select, expression, span)?,
|
||||
_ => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Query without a select".into(),
|
||||
"Missing a WHERE clause before an AND clause".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn modify_select(select: &mut Box<Select>, expression: Expr, span: Span) -> Result<(), ShellError> {
|
||||
let new_expression = match &select.selection {
|
||||
Some(expr) => Ok(Expr::BinaryOp {
|
||||
left: Box::new(expr.clone()),
|
||||
op: BinaryOperator::And,
|
||||
right: Box::new(expression),
|
||||
}),
|
||||
None => Err(ShellError::GenericError(
|
||||
"Query without a select".into(),
|
||||
"Missing a WHERE clause before an AND clause".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
}?;
|
||||
|
||||
select.as_mut().selection = Some(new_expression);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::expressions::{AndExpr, FieldExpr};
|
||||
use super::super::{FromDb, ProjectionDb, WhereDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(AndDb {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
Box::new(WhereDb {}),
|
||||
Box::new(FieldExpr {}),
|
||||
Box::new(AndExpr {}),
|
||||
])
|
||||
}
|
||||
}
|
|
@ -1,173 +0,0 @@
|
|||
use crate::SQLiteDatabase;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{Ident, SetExpr, Statement, TableAlias, TableFactor};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AliasDb;
|
||||
|
||||
impl Command for AliasDb {
|
||||
fn name(&self) -> &str {
|
||||
"as"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("alias", SyntaxShape::String, "alias name")
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates an alias for a column selection"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Creates an alias for a selected table",
|
||||
example: r#"open db.sqlite
|
||||
| from table table_1
|
||||
| select a
|
||||
| as t1
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a FROM table_1 AS t1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Creates an alias for a derived table",
|
||||
example: r#"open db.sqlite
|
||||
| from table (
|
||||
open db.sqlite
|
||||
| from table table_a
|
||||
| select a b
|
||||
)
|
||||
| select a
|
||||
| as t1
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a FROM (SELECT a, b FROM table_a) AS t1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "alias", "column"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let alias: String = call.req(engine_state, stack, 0)?;
|
||||
|
||||
let db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||
alias_db(db, alias, call)
|
||||
}
|
||||
}
|
||||
|
||||
fn alias_db(
|
||||
mut db: SQLiteDatabase,
|
||||
new_alias: String,
|
||||
call: &Call,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
match db.statement.as_mut() {
|
||||
None => Err(ShellError::GenericError(
|
||||
"Error creating alias".into(),
|
||||
"there is no statement defined yet".into(),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
Some(statement) => match statement {
|
||||
Statement::Query(query) => match &mut *query.body {
|
||||
SetExpr::Select(select) => {
|
||||
select.as_mut().from.iter_mut().for_each(|table| {
|
||||
let new_alias = Some(TableAlias {
|
||||
name: Ident {
|
||||
value: new_alias.clone(),
|
||||
quote_style: None,
|
||||
},
|
||||
columns: Vec::new(),
|
||||
});
|
||||
|
||||
if let TableFactor::Table { ref mut alias, .. } = table.relation {
|
||||
*alias = new_alias;
|
||||
} else if let TableFactor::Derived { ref mut alias, .. } = table.relation {
|
||||
*alias = new_alias;
|
||||
} else if let TableFactor::TableFunction { ref mut alias, .. } =
|
||||
table.relation
|
||||
{
|
||||
*alias = new_alias;
|
||||
}
|
||||
});
|
||||
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
_ => Err(ShellError::GenericError(
|
||||
"Error creating alias".into(),
|
||||
"Query has no select from defined".into(),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
},
|
||||
s => Err(ShellError::GenericError(
|
||||
"Connection doesn't define a query".into(),
|
||||
format!("Expected a connection with query. Got {}", s),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::{FromDb, ProjectionDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(AliasDb {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
])
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type,
|
||||
};
|
||||
|
||||
use super::super::SQLiteDatabase;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CollectDb;
|
||||
|
||||
impl Command for CollectDb {
|
||||
fn name(&self) -> &str {
|
||||
"collect"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Any)
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Collects a query from a database database connection"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Collect from a select query",
|
||||
example: "open foo.db | from table table_1 db | select a | collect",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||
|
||||
db.collect(call.head)
|
||||
.map(IntoPipelineData::into_pipeline_data)
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
use crate::{database::values::definitions::ConnectionDb, SQLiteDatabase};
|
||||
use nu_protocol::{ShellError, Value};
|
||||
use sqlparser::ast::{ObjectName, Statement, TableAlias, TableFactor};
|
||||
|
||||
pub fn value_into_table_factor(
|
||||
table: Value,
|
||||
connection: &ConnectionDb,
|
||||
alias: Option<TableAlias>,
|
||||
) -> Result<TableFactor, ShellError> {
|
||||
match table {
|
||||
Value::String { val, .. } => {
|
||||
let ident = sqlparser::ast::Ident {
|
||||
value: val,
|
||||
quote_style: None,
|
||||
};
|
||||
|
||||
Ok(TableFactor::Table {
|
||||
name: ObjectName(vec![ident]),
|
||||
alias,
|
||||
args: None,
|
||||
with_hints: Vec::new(),
|
||||
})
|
||||
}
|
||||
Value::CustomValue { span, .. } => {
|
||||
let db = SQLiteDatabase::try_from_value(table)?;
|
||||
|
||||
if &db.connection != connection {
|
||||
return Err(ShellError::GenericError(
|
||||
"Incompatible connections".into(),
|
||||
"trying to join on table with different connection".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
));
|
||||
}
|
||||
|
||||
match db.statement {
|
||||
Some(statement) => match statement {
|
||||
Statement::Query(query) => Ok(TableFactor::Derived {
|
||||
lateral: false,
|
||||
subquery: query,
|
||||
alias,
|
||||
}),
|
||||
s => Err(ShellError::GenericError(
|
||||
"Connection doesnt define a query".into(),
|
||||
format!("Expected a connection with query. Got {}", s),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
},
|
||||
None => Err(ShellError::GenericError(
|
||||
"Error creating derived table".into(),
|
||||
"there is no statement defined yet".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::UnsupportedInput(
|
||||
"String or connection".into(),
|
||||
table.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value,
|
||||
};
|
||||
|
||||
use super::super::SQLiteDatabase;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DescribeDb;
|
||||
|
||||
impl Command for DescribeDb {
|
||||
fn name(&self) -> &str {
|
||||
"describe"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Any)
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Describes connection and query of the DB object"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Describe SQLite database constructed query",
|
||||
example: "open foo.db | from table table_1 | select col_1 | describe",
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "foo.db".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT col_1 FROM table_1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "SQLite"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||
Ok(db.describe(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::{FromDb, ProjectionDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(DescribeDb {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
])
|
||||
}
|
||||
}
|
|
@ -1,210 +0,0 @@
|
|||
use crate::database::values::definitions::ConnectionDb;
|
||||
|
||||
use super::{super::SQLiteDatabase, conversions::value_into_table_factor};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{Ident, Query, Select, SetExpr, Statement, TableAlias, TableWithJoins};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FromDb;
|
||||
|
||||
impl Command for FromDb {
|
||||
fn name(&self) -> &str {
|
||||
"from table"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Select section from query statement for a DB"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required(
|
||||
"select",
|
||||
SyntaxShape::Any,
|
||||
"table of derived table to select from",
|
||||
)
|
||||
.named(
|
||||
"as",
|
||||
SyntaxShape::String,
|
||||
"Alias for the selected table",
|
||||
Some('a'),
|
||||
)
|
||||
.input_type(Type::Any)
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Selects a table from database",
|
||||
example: "open db.sqlite | from table table_a | describe",
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT FROM table_a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||
db.statement = match db.statement {
|
||||
None => Some(create_statement(&db.connection, engine_state, stack, call)?),
|
||||
Some(statement) => Some(modify_statement(
|
||||
&db.connection,
|
||||
statement,
|
||||
engine_state,
|
||||
stack,
|
||||
call,
|
||||
)?),
|
||||
};
|
||||
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
fn create_statement(
|
||||
connection: &ConnectionDb,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<Statement, ShellError> {
|
||||
let query = Query {
|
||||
with: None,
|
||||
body: Box::new(SetExpr::Select(Box::new(create_select(
|
||||
connection,
|
||||
engine_state,
|
||||
stack,
|
||||
call,
|
||||
)?))),
|
||||
order_by: Vec::new(),
|
||||
limit: None,
|
||||
offset: None,
|
||||
fetch: None,
|
||||
lock: None,
|
||||
};
|
||||
|
||||
Ok(Statement::Query(Box::new(query)))
|
||||
}
|
||||
|
||||
fn modify_statement(
|
||||
connection: &ConnectionDb,
|
||||
mut statement: Statement,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<Statement, ShellError> {
|
||||
match statement {
|
||||
Statement::Query(ref mut query) => {
|
||||
match *query.body {
|
||||
SetExpr::Select(ref mut select) => {
|
||||
let table = create_table(connection, engine_state, stack, call)?;
|
||||
select.from.push(table);
|
||||
}
|
||||
_ => {
|
||||
query.as_mut().body = Box::new(SetExpr::Select(Box::new(create_select(
|
||||
connection,
|
||||
engine_state,
|
||||
stack,
|
||||
call,
|
||||
)?)));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(statement)
|
||||
}
|
||||
s => Err(ShellError::GenericError(
|
||||
"Connection doesnt define a query".into(),
|
||||
format!("Expected a connection with query. Got {}", s),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_select(
|
||||
connection: &ConnectionDb,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<Select, ShellError> {
|
||||
Ok(Select {
|
||||
distinct: false,
|
||||
top: None,
|
||||
projection: Vec::new(),
|
||||
into: None,
|
||||
from: vec![create_table(connection, engine_state, stack, call)?],
|
||||
lateral_views: Vec::new(),
|
||||
selection: None,
|
||||
group_by: Vec::new(),
|
||||
cluster_by: Vec::new(),
|
||||
distribute_by: Vec::new(),
|
||||
sort_by: Vec::new(),
|
||||
having: None,
|
||||
qualify: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn create_table(
|
||||
connection: &ConnectionDb,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<TableWithJoins, ShellError> {
|
||||
let alias = call
|
||||
.get_flag::<String>(engine_state, stack, "as")?
|
||||
.map(|alias| TableAlias {
|
||||
name: Ident {
|
||||
value: alias,
|
||||
quote_style: None,
|
||||
},
|
||||
columns: Vec::new(),
|
||||
});
|
||||
|
||||
let select_table: Value = call.req(engine_state, stack, 0)?;
|
||||
let table_factor = value_into_table_factor(select_table, connection, alias)?;
|
||||
|
||||
let table = TableWithJoins {
|
||||
relation: table_factor,
|
||||
joins: Vec::new(),
|
||||
};
|
||||
|
||||
Ok(table)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![Box::new(FromDb {})])
|
||||
}
|
||||
}
|
|
@ -1,163 +0,0 @@
|
|||
use crate::database::values::dsl::ExprDb;
|
||||
|
||||
use super::super::SQLiteDatabase;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{SetExpr, Statement};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GroupByDb;
|
||||
|
||||
impl Command for GroupByDb {
|
||||
fn name(&self) -> &str {
|
||||
"group-by"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Group-by query"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.rest(
|
||||
"select",
|
||||
SyntaxShape::Any,
|
||||
"Select expression(s) on the table",
|
||||
)
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "select"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "groups by column a and calculates the max",
|
||||
example: r#"open db.sqlite
|
||||
| from table table_a
|
||||
| select (fn max a)
|
||||
| group-by a
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT max(a) FROM table_a GROUP BY a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "groups by column column a and counts records",
|
||||
example: r#"open db.sqlite
|
||||
| from table table_a
|
||||
| select (fn count *)
|
||||
| group-by a
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT count(*) FROM table_a GROUP BY a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||
let value = Value::List {
|
||||
vals,
|
||||
span: call.head,
|
||||
};
|
||||
let expressions = ExprDb::extract_exprs(value)?;
|
||||
|
||||
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||
match db.statement.as_mut() {
|
||||
Some(statement) => match statement {
|
||||
Statement::Query(ref mut query) => match &mut *query.body {
|
||||
SetExpr::Select(ref mut select) => select.group_by = expressions,
|
||||
s => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Connection doesnt define a select".into(),
|
||||
format!("Expected a connection with select query. Got {}", s),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
},
|
||||
s => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Connection doesnt define a query".into(),
|
||||
format!("Expected a connection with query. Got {}", s),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Connection without statement".into(),
|
||||
"The connection needs a statement defined".into(),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::expressions::{FieldExpr, FunctionExpr, OrExpr};
|
||||
use super::super::{FromDb, ProjectionDb, WhereDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(GroupByDb {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FunctionExpr {}),
|
||||
Box::new(FromDb {}),
|
||||
Box::new(WhereDb {}),
|
||||
Box::new(FieldExpr {}),
|
||||
Box::new(OrExpr {}),
|
||||
])
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
use super::super::SQLiteDatabase;
|
||||
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ToDataBase;
|
||||
|
||||
impl Command for ToDataBase {
|
||||
fn name(&self) -> &str {
|
||||
"into db"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Converts the input into an open db connection"
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
"This function is used as a hint to Nushell to optimize the pipeline for database queries."
|
||||
}
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_type(Type::Any)
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "convert"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Converts an open file into a db object.",
|
||||
example: "open db.sqlite | into db",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
|
@ -45,7 +45,7 @@ impl Command for IntoSqliteDb {
|
|||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert table into a sqlite database"
|
||||
"Convert table into a SQLite database"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
|
@ -54,22 +54,22 @@ impl Command for IntoSqliteDb {
|
|||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Convert ls entries into a sqlite database with 'main' as the table name",
|
||||
description: "Convert ls entries into a SQLite database with 'main' as the table name",
|
||||
example: "ls | into sqlite my_ls.db",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Convert ls entries into a sqlite database with 'my_table' as the table name",
|
||||
description: "Convert ls entries into a SQLite database with 'my_table' as the table name",
|
||||
example: "ls | into sqlite my_ls.db -t my_table",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Convert table literal into a sqlite database with 'main' as the table name",
|
||||
description: "Convert table literal into a SQLite database with 'main' as the table name",
|
||||
example: "[[name]; [-----] [someone] [=====] [somename] ['(((((']] | into sqlite filename.db",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Convert a variety of values in table literal form into a sqlite database",
|
||||
description: "Convert a variety of values in table literal form into a SQLite database",
|
||||
example: "[one 2 5.2 six true 100mib 25sec] | into sqlite variety.db",
|
||||
result: None,
|
||||
}]
|
||||
|
|
|
@ -1,248 +0,0 @@
|
|||
use super::{super::SQLiteDatabase, conversions::value_into_table_factor};
|
||||
use crate::database::values::{definitions::ConnectionDb, dsl::ExprDb};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{
|
||||
Ident, Join, JoinConstraint, JoinOperator, Select, SetExpr, Statement, TableAlias,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct JoinDb;
|
||||
|
||||
impl Command for JoinDb {
|
||||
fn name(&self) -> &str {
|
||||
"join"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Joins with another table or derived table. Default join type is inner"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required(
|
||||
"table",
|
||||
SyntaxShape::Any,
|
||||
"table or derived table to join on",
|
||||
)
|
||||
.required("on", SyntaxShape::Any, "expression to join tables")
|
||||
.named(
|
||||
"as",
|
||||
SyntaxShape::String,
|
||||
"Alias for the selected join",
|
||||
Some('a'),
|
||||
)
|
||||
.switch("left", "left outer join", Some('l'))
|
||||
.switch("right", "right outer join", Some('r'))
|
||||
.switch("outer", "full outer join", Some('o'))
|
||||
.switch("cross", "cross join", Some('c'))
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "joins two tables on col_b",
|
||||
example: r#"open db.sqlite
|
||||
| from table table_1 --as t1
|
||||
| join table_2 col_b --as t2
|
||||
| select col_a
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT col_a FROM table_1 AS t1 JOIN table_2 AS t2 ON col_b"
|
||||
.into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "joins a table with a derived table using aliases",
|
||||
example: r#"open db.sqlite
|
||||
| from table table_1 --as t1
|
||||
| join (
|
||||
open db.sqlite
|
||||
| from table table_2
|
||||
| select col_c
|
||||
) ((field t1.col_a) == (field t2.col_c)) --as t2 --right
|
||||
| select col_a
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT col_a FROM table_1 AS t1 RIGHT JOIN (SELECT col_c FROM table_2) AS t2 ON t1.col_a = t2.col_c"
|
||||
.into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||
|
||||
db.statement = match db.statement {
|
||||
Some(statement) => Some(modify_statement(
|
||||
&db.connection,
|
||||
statement,
|
||||
engine_state,
|
||||
stack,
|
||||
call,
|
||||
)?),
|
||||
None => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Error creating join".into(),
|
||||
"there is no statement defined yet".into(),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
fn modify_statement(
|
||||
connection: &ConnectionDb,
|
||||
mut statement: Statement,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<Statement, ShellError> {
|
||||
match statement {
|
||||
Statement::Query(ref mut query) => {
|
||||
match &mut *query.body {
|
||||
SetExpr::Select(ref mut select) => {
|
||||
modify_from(connection, select, engine_state, stack, call)?
|
||||
}
|
||||
s => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Connection doesnt define a select".into(),
|
||||
format!("Expected a connection with select. Got {}", s),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(statement)
|
||||
}
|
||||
s => Err(ShellError::GenericError(
|
||||
"Connection doesnt define a query".into(),
|
||||
format!("Expected a connection with query. Got {}", s),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn modify_from(
|
||||
connection: &ConnectionDb,
|
||||
select: &mut Select,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<(), ShellError> {
|
||||
match select.from.last_mut() {
|
||||
Some(table) => {
|
||||
let alias = call
|
||||
.get_flag::<String>(engine_state, stack, "as")?
|
||||
.map(|alias| TableAlias {
|
||||
name: Ident {
|
||||
value: alias,
|
||||
quote_style: None,
|
||||
},
|
||||
columns: Vec::new(),
|
||||
});
|
||||
|
||||
let join_table: Value = call.req(engine_state, stack, 0)?;
|
||||
let table_factor = value_into_table_factor(join_table, connection, alias)?;
|
||||
|
||||
let on_expr: Value = call.req(engine_state, stack, 1)?;
|
||||
let on_expr = ExprDb::try_from_value(&on_expr)?;
|
||||
|
||||
let join_on = if call.has_flag("left") {
|
||||
JoinOperator::LeftOuter(JoinConstraint::On(on_expr.into_native()))
|
||||
} else if call.has_flag("right") {
|
||||
JoinOperator::RightOuter(JoinConstraint::On(on_expr.into_native()))
|
||||
} else if call.has_flag("outer") {
|
||||
JoinOperator::FullOuter(JoinConstraint::On(on_expr.into_native()))
|
||||
} else {
|
||||
JoinOperator::Inner(JoinConstraint::On(on_expr.into_native()))
|
||||
};
|
||||
|
||||
let join = Join {
|
||||
relation: table_factor,
|
||||
join_operator: join_on,
|
||||
};
|
||||
|
||||
table.joins.push(join);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
None => Err(ShellError::GenericError(
|
||||
"Connection without table defined".into(),
|
||||
"Expected a table defined".into(),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::expressions::{FieldExpr, OrExpr};
|
||||
use super::super::{FromDb, ProjectionDb, WhereDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(JoinDb {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
Box::new(WhereDb {}),
|
||||
Box::new(FieldExpr {}),
|
||||
Box::new(OrExpr {}),
|
||||
])
|
||||
}
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
use super::super::SQLiteDatabase;
|
||||
use crate::database::values::dsl::ExprDb;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::Statement;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LimitDb;
|
||||
|
||||
impl Command for LimitDb {
|
||||
fn name(&self) -> &str {
|
||||
"limit"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Limit result from query"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required(
|
||||
"limit",
|
||||
SyntaxShape::Int,
|
||||
"Number of rows to extract for query",
|
||||
)
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "head", "tail"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Limits selection from table",
|
||||
example: r#"open db.sqlite
|
||||
| from table table_a
|
||||
| select a
|
||||
| limit 10
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a FROM table_a LIMIT 10".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let limit: Value = call.req(engine_state, stack, 0)?;
|
||||
let expr = ExprDb::try_from_value(&limit)?.into_native();
|
||||
|
||||
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||
match db.statement {
|
||||
Some(ref mut statement) => match statement {
|
||||
Statement::Query(query) => query.as_mut().limit = Some(expr),
|
||||
s => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Connection doesnt define a statement".into(),
|
||||
format!("Expected a connection with query. Got {}", s),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Connection without query".into(),
|
||||
"The connection needs a query defined".into(),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::expressions::{FieldExpr, OrExpr};
|
||||
use super::super::{FromDb, ProjectionDb, WhereDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(LimitDb {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
Box::new(WhereDb {}),
|
||||
Box::new(FieldExpr {}),
|
||||
Box::new(OrExpr {}),
|
||||
])
|
||||
}
|
||||
}
|
|
@ -1,46 +1,11 @@
|
|||
// Conversions between value and sqlparser objects
|
||||
pub mod conversions;
|
||||
|
||||
mod and;
|
||||
mod as_;
|
||||
mod collect;
|
||||
mod describe;
|
||||
mod from_table;
|
||||
mod group_by;
|
||||
mod into_db;
|
||||
mod into_sqlite;
|
||||
mod join;
|
||||
mod limit;
|
||||
mod open_db;
|
||||
mod or;
|
||||
mod order_by;
|
||||
mod query_db;
|
||||
mod schema;
|
||||
mod select;
|
||||
mod where_;
|
||||
|
||||
// Temporal module to create Query objects
|
||||
mod testing_db;
|
||||
use testing_db::TestingDb;
|
||||
|
||||
use and::AndDb;
|
||||
use as_::AliasDb;
|
||||
use collect::CollectDb;
|
||||
pub(crate) use describe::DescribeDb;
|
||||
pub(crate) use from_table::FromDb;
|
||||
use group_by::GroupByDb;
|
||||
pub(crate) use into_db::ToDataBase;
|
||||
use into_sqlite::IntoSqliteDb;
|
||||
use join::JoinDb;
|
||||
use limit::LimitDb;
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
use open_db::OpenDb;
|
||||
use or::OrDb;
|
||||
use order_by::OrderByDb;
|
||||
use query_db::QueryDb;
|
||||
use schema::SchemaDb;
|
||||
pub(crate) use select::ProjectionDb;
|
||||
use where_::WhereDb;
|
||||
|
||||
pub fn add_commands_decls(working_set: &mut StateWorkingSet) {
|
||||
macro_rules! bind_command {
|
||||
|
@ -53,24 +18,5 @@ pub fn add_commands_decls(working_set: &mut StateWorkingSet) {
|
|||
}
|
||||
|
||||
// Series commands
|
||||
bind_command!(
|
||||
ToDataBase,
|
||||
AliasDb,
|
||||
AndDb,
|
||||
CollectDb,
|
||||
DescribeDb,
|
||||
FromDb,
|
||||
GroupByDb,
|
||||
IntoSqliteDb,
|
||||
JoinDb,
|
||||
LimitDb,
|
||||
OpenDb,
|
||||
OrderByDb,
|
||||
OrDb,
|
||||
QueryDb,
|
||||
ProjectionDb,
|
||||
SchemaDb,
|
||||
TestingDb,
|
||||
WhereDb
|
||||
);
|
||||
bind_command!(IntoSqliteDb, QueryDb, SchemaDb);
|
||||
}
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
use super::super::SQLiteDatabase;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
||||
Type,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OpenDb;
|
||||
|
||||
impl Command for OpenDb {
|
||||
fn name(&self) -> &str {
|
||||
"open-db"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("query", SyntaxShape::Filepath, "SQLite file to be opened")
|
||||
.input_type(Type::Any)
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Open a database"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Creates a connection to a sqlite database based on the file name",
|
||||
example: r#"open-db file.sqlite"#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let path: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
||||
|
||||
SQLiteDatabase::try_from_path(path.item.as_path(), path.span)
|
||||
.map(|db| db.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
|
@ -1,182 +0,0 @@
|
|||
use crate::database::values::dsl::ExprDb;
|
||||
|
||||
use super::super::SQLiteDatabase;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{BinaryOperator, Expr, Query, Select, SetExpr, Statement};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OrDb;
|
||||
|
||||
impl Command for OrDb {
|
||||
fn name(&self) -> &str {
|
||||
"or"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Includes an OR clause for a query"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("where", SyntaxShape::Any, "Where expression on the table")
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "where"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "selects a column from a database with an OR clause",
|
||||
example: r#"open db.sqlite
|
||||
| from table table_1
|
||||
| select a
|
||||
| where ((field a) > 1)
|
||||
| or ((field b) == 1)
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a FROM table_1 WHERE a > 1 OR b = 1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Creates an OR clause in the column names and a column",
|
||||
example: r#"open db.sqlite
|
||||
| from table table_1
|
||||
| select a
|
||||
| where ((field a) > 1 | or ((field a) < 10))
|
||||
| or ((field b) == 1)
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a FROM table_1 WHERE (a > 1 OR a < 10) OR b = 1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let value: Value = call.req(engine_state, stack, 0)?;
|
||||
let expr = ExprDb::try_from_value(&value)?.into_native();
|
||||
|
||||
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||
match db.statement {
|
||||
Some(ref mut statement) => match statement {
|
||||
Statement::Query(query) => modify_query(query, expr, call.head)?,
|
||||
s => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Connection doesnt define a query".into(),
|
||||
format!("Expected a connection with query. Got {}", s),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Connection without statement".into(),
|
||||
"The connection needs a statement defined".into(),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
fn modify_query(query: &mut Box<Query>, expression: Expr, span: Span) -> Result<(), ShellError> {
|
||||
match *query.body {
|
||||
SetExpr::Select(ref mut select) => modify_select(select, expression, span)?,
|
||||
_ => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Query without a select".into(),
|
||||
"Missing a WHERE clause before an OR clause".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn modify_select(select: &mut Box<Select>, expression: Expr, span: Span) -> Result<(), ShellError> {
|
||||
let new_expression = match &select.selection {
|
||||
Some(expr) => Ok(Expr::BinaryOp {
|
||||
left: Box::new(expr.clone()),
|
||||
op: BinaryOperator::Or,
|
||||
right: Box::new(expression),
|
||||
}),
|
||||
None => Err(ShellError::GenericError(
|
||||
"Query without a select".into(),
|
||||
"Missing a WHERE clause before an OR clause".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
}?;
|
||||
|
||||
select.as_mut().selection = Some(new_expression);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::expressions::{FieldExpr, OrExpr};
|
||||
use super::super::{FromDb, ProjectionDb, WhereDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(OrDb {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
Box::new(WhereDb {}),
|
||||
Box::new(FieldExpr {}),
|
||||
Box::new(OrExpr {}),
|
||||
])
|
||||
}
|
||||
}
|
|
@ -1,218 +0,0 @@
|
|||
use crate::database::values::dsl::ExprDb;
|
||||
|
||||
use super::super::SQLiteDatabase;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{Expr, OrderByExpr, Statement};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OrderByDb;
|
||||
|
||||
impl Command for OrderByDb {
|
||||
fn name(&self) -> &str {
|
||||
"order-by"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Orders by query"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.switch("ascending", "Order by ascending values", Some('a'))
|
||||
.switch("nulls-first", "Show nulls first in order", Some('n'))
|
||||
.rest(
|
||||
"select",
|
||||
SyntaxShape::Any,
|
||||
"Select expression(s) on the table",
|
||||
)
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "orders query by a column",
|
||||
example: r#"open db.sqlite
|
||||
| from table table_a
|
||||
| select a
|
||||
| order-by a
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a FROM table_a ORDER BY a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "orders query by column a ascending and by column b",
|
||||
example: r#"open db.sqlite
|
||||
| from table table_a
|
||||
| select a
|
||||
| order-by a --ascending
|
||||
| order-by b
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a FROM table_a ORDER BY a ASC, b".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let asc = call.has_flag("ascending");
|
||||
let nulls_first = call.has_flag("nulls-first");
|
||||
let expressions: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||
let expressions = Value::List {
|
||||
vals: expressions,
|
||||
span: call.head,
|
||||
};
|
||||
let expressions = ExprDb::extract_exprs(expressions)?;
|
||||
let expressions: Vec<OrderByExpr> = expressions
|
||||
.into_iter()
|
||||
.map(|expr| OrderByExpr {
|
||||
expr,
|
||||
asc: if asc { Some(asc) } else { None },
|
||||
nulls_first: if nulls_first { Some(nulls_first) } else { None },
|
||||
})
|
||||
.collect();
|
||||
|
||||
let value = input.into_value(call.head);
|
||||
|
||||
if let Ok(expr) = ExprDb::try_from_value(&value) {
|
||||
update_expressions(expr, expressions, call)
|
||||
} else if let Ok(db) = SQLiteDatabase::try_from_value(value.clone()) {
|
||||
update_connection(db, expressions, call)
|
||||
} else {
|
||||
Err(ShellError::CantConvert(
|
||||
"expression or query".into(),
|
||||
value.get_type().to_string(),
|
||||
value.span()?,
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_expressions(
|
||||
mut expr: ExprDb,
|
||||
mut expressions: Vec<OrderByExpr>,
|
||||
call: &Call,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
match expr.as_mut() {
|
||||
Expr::Function(function) => match &mut function.over {
|
||||
Some(over) => over.order_by.append(&mut expressions),
|
||||
None => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Expression doesnt define a partition to order".into(),
|
||||
"Expected an expression with partition".into(),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
},
|
||||
s => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Expression doesnt define a function".into(),
|
||||
format!("Expected an expression with a function. Got {}", s),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(expr.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn update_connection(
|
||||
mut db: SQLiteDatabase,
|
||||
mut expressions: Vec<OrderByExpr>,
|
||||
call: &Call,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
match db.statement.as_mut() {
|
||||
Some(statement) => match statement {
|
||||
Statement::Query(query) => {
|
||||
query.order_by.append(&mut expressions);
|
||||
}
|
||||
s => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Connection doesnt define a query".into(),
|
||||
format!("Expected a connection with query. Got {}", s),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Connection without statement".into(),
|
||||
"The connection needs a statement defined".into(),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::expressions::{FieldExpr, OrExpr};
|
||||
use super::super::{FromDb, ProjectionDb, WhereDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(OrderByDb {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
Box::new(WhereDb {}),
|
||||
Box::new(FieldExpr {}),
|
||||
Box::new(OrExpr {}),
|
||||
])
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@ impl Command for SchemaDb {
|
|||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Show sqlite database information, including its schema."
|
||||
"Show SQLite database information, including its schema."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -54,7 +54,7 @@ impl Command for SchemaDb {
|
|||
|
||||
cols.push("db_filename".into());
|
||||
vals.push(Value::String {
|
||||
val: sqlite_db.connection.to_string(),
|
||||
val: sqlite_db.path.to_string_lossy().into(),
|
||||
span,
|
||||
});
|
||||
|
||||
|
|
|
@ -1,183 +0,0 @@
|
|||
use super::{super::values::dsl::SelectDb, super::SQLiteDatabase};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{Query, Select, SelectItem, SetExpr, Statement};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ProjectionDb;
|
||||
|
||||
impl Command for ProjectionDb {
|
||||
fn name(&self) -> &str {
|
||||
"select"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates a select statement for a DB"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.rest(
|
||||
"select",
|
||||
SyntaxShape::Any,
|
||||
"Select expression(s) on the table",
|
||||
)
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "selects a column from a database",
|
||||
example: "open db.sqlite | into db | select a | describe",
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "selects columns from a database using alias",
|
||||
example: r#"open db.sqlite
|
||||
| into db
|
||||
| select (field a | as new_a) b c
|
||||
| from table table_1
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a AS new_a, b, c FROM table_1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||
let value = Value::List {
|
||||
vals,
|
||||
span: call.head,
|
||||
};
|
||||
let projection = SelectDb::extract_selects(value)?;
|
||||
|
||||
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||
db.statement = match db.statement {
|
||||
None => Some(create_statement(projection)),
|
||||
Some(statement) => Some(modify_statement(statement, projection, call.head)?),
|
||||
};
|
||||
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
fn create_statement(expressions: Vec<SelectItem>) -> Statement {
|
||||
let query = Query {
|
||||
with: None,
|
||||
body: Box::new(SetExpr::Select(Box::new(create_select(expressions)))),
|
||||
order_by: Vec::new(),
|
||||
limit: None,
|
||||
offset: None,
|
||||
fetch: None,
|
||||
lock: None,
|
||||
};
|
||||
|
||||
Statement::Query(Box::new(query))
|
||||
}
|
||||
|
||||
fn modify_statement(
|
||||
mut statement: Statement,
|
||||
expressions: Vec<SelectItem>,
|
||||
span: Span,
|
||||
) -> Result<Statement, ShellError> {
|
||||
match statement {
|
||||
Statement::Query(ref mut query) => {
|
||||
match *query.body {
|
||||
SetExpr::Select(ref mut select) => select.as_mut().projection = expressions,
|
||||
_ => {
|
||||
query.as_mut().body =
|
||||
Box::new(SetExpr::Select(Box::new(create_select(expressions))));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(statement)
|
||||
}
|
||||
s => Err(ShellError::GenericError(
|
||||
"Connection doesn't define a statement".into(),
|
||||
format!("Expected a connection with query. Got {}", s),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_select(projection: Vec<SelectItem>) -> Select {
|
||||
Select {
|
||||
distinct: false,
|
||||
top: None,
|
||||
projection,
|
||||
into: None,
|
||||
from: Vec::new(),
|
||||
lateral_views: Vec::new(),
|
||||
selection: None,
|
||||
group_by: Vec::new(),
|
||||
cluster_by: Vec::new(),
|
||||
distribute_by: Vec::new(),
|
||||
sort_by: Vec::new(),
|
||||
having: None,
|
||||
qualify: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::expressions::{AliasExpr, FieldExpr};
|
||||
use super::super::FromDb;
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
Box::new(FieldExpr {}),
|
||||
Box::new(AliasExpr {}),
|
||||
])
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
||||
Value,
|
||||
};
|
||||
use sqlparser::dialect::GenericDialect;
|
||||
use sqlparser::parser::Parser;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TestingDb;
|
||||
|
||||
impl Command for TestingDb {
|
||||
fn name(&self) -> &str {
|
||||
"testing-db"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required(
|
||||
"query",
|
||||
SyntaxShape::String,
|
||||
"SQL to execute to create the query object",
|
||||
)
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Temporal Command: Create query object"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "",
|
||||
example: "",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "SQLite"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let sql: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||
|
||||
let dialect = GenericDialect {}; // or AnsiDialect, or your own dialect ...
|
||||
|
||||
let ast = Parser::parse_sql(&dialect, sql.item.as_str()).map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Error creating AST".into(),
|
||||
e.to_string(),
|
||||
Some(sql.span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let value = match ast.get(0) {
|
||||
None => Value::nothing(call.head),
|
||||
Some(statement) => Value::String {
|
||||
val: format!("{:#?}", statement),
|
||||
span: call.head,
|
||||
},
|
||||
};
|
||||
|
||||
Ok(value.into_pipeline_data())
|
||||
}
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
use crate::database::values::dsl::ExprDb;
|
||||
|
||||
use super::super::SQLiteDatabase;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{Expr, Query, Select, SetExpr, Statement};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WhereDb;
|
||||
|
||||
impl Command for WhereDb {
|
||||
fn name(&self) -> &str {
|
||||
"where"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Includes a where statement for a query"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("where", SyntaxShape::Any, "Where expression on the table")
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "selects a column from a database with a where clause",
|
||||
example: r#"open db.sqlite
|
||||
| from table table_1
|
||||
| select a
|
||||
| where ((field a) > 1)
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a FROM table_1 WHERE a > 1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let value: Value = call.req(engine_state, stack, 0)?;
|
||||
let expr = ExprDb::try_from_value(&value)?.into_native();
|
||||
|
||||
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||
match db.statement.as_mut() {
|
||||
Some(statement) => match statement {
|
||||
Statement::Query(query) => modify_query(query, expr),
|
||||
s => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Connection doesnt define a query".into(),
|
||||
format!("Expected a connection with query. Got {}", s),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Connection without statement".into(),
|
||||
"The connection needs a statement defined".into(),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
fn modify_query(query: &mut Box<Query>, expression: Expr) {
|
||||
match *query.body {
|
||||
SetExpr::Select(ref mut select) => modify_select(select, expression),
|
||||
_ => {
|
||||
query.as_mut().body = Box::new(SetExpr::Select(Box::new(create_select(expression))));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn modify_select(select: &mut Box<Select>, expression: Expr) {
|
||||
select.as_mut().selection = Some(expression);
|
||||
}
|
||||
|
||||
fn create_select(expression: Expr) -> Select {
|
||||
Select {
|
||||
distinct: false,
|
||||
top: None,
|
||||
into: None,
|
||||
projection: Vec::new(),
|
||||
from: Vec::new(),
|
||||
lateral_views: Vec::new(),
|
||||
selection: Some(expression),
|
||||
group_by: Vec::new(),
|
||||
cluster_by: Vec::new(),
|
||||
distribute_by: Vec::new(),
|
||||
sort_by: Vec::new(),
|
||||
having: None,
|
||||
qualify: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::expressions::{FieldExpr, OrExpr};
|
||||
use super::super::{FromDb, ProjectionDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(WhereDb {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
Box::new(WhereDb {}),
|
||||
Box::new(FieldExpr {}),
|
||||
Box::new(OrExpr {}),
|
||||
])
|
||||
}
|
||||
}
|
|
@ -1,132 +0,0 @@
|
|||
use crate::database::values::dsl::{ExprDb, SelectDb};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{Ident, SelectItem};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AliasExpr;
|
||||
|
||||
impl Command for AliasExpr {
|
||||
fn name(&self) -> &str {
|
||||
"as"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("alias", SyntaxShape::String, "alias name")
|
||||
.input_type(Type::Custom("db-expression".into()))
|
||||
.output_type(Type::Custom("db-expression".into()))
|
||||
.category(Category::Custom("db-expression".into()))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates an alias for a column selection"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Creates an alias for a column selection",
|
||||
example: "field name_a | as new_a | into nu",
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["expression".into(), "alias".into()],
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: vec!["value".into(), "quoted_style".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "name_a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "None".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Record {
|
||||
cols: vec!["value".into(), "quoted_style".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "new_a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "None".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "alias", "column"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let alias: String = call.req(engine_state, stack, 0)?;
|
||||
|
||||
let value = input.into_value(call.head);
|
||||
if let Ok(expr) = ExprDb::try_from_value(&value) {
|
||||
alias_selection(expr.into_native().into(), alias, call)
|
||||
} else {
|
||||
let select = SelectDb::try_from_value(&value)?;
|
||||
alias_selection(select, alias, call)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn alias_selection(
|
||||
select: SelectDb,
|
||||
alias: String,
|
||||
call: &Call,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let select = match select.into_native() {
|
||||
SelectItem::UnnamedExpr(expr) => SelectItem::ExprWithAlias {
|
||||
expr,
|
||||
alias: Ident {
|
||||
value: alias,
|
||||
quote_style: None,
|
||||
},
|
||||
},
|
||||
SelectItem::ExprWithAlias { expr, .. } => SelectItem::ExprWithAlias {
|
||||
expr,
|
||||
alias: Ident {
|
||||
value: alias,
|
||||
quote_style: None,
|
||||
},
|
||||
},
|
||||
select => select,
|
||||
};
|
||||
|
||||
let select: SelectDb = select.into();
|
||||
Ok(select.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::FieldExpr;
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![Box::new(AliasExpr {}), Box::new(FieldExpr {})])
|
||||
}
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
use crate::database::values::dsl::ExprDb;
|
||||
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{BinaryOperator, Expr};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AndExpr;
|
||||
|
||||
impl Command for AndExpr {
|
||||
fn name(&self) -> &str {
|
||||
"and"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Includes an AND clause for an expression"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("and", SyntaxShape::Any, "AND expression")
|
||||
.input_type(Type::Custom("db-expression".into()))
|
||||
.output_type(Type::Custom("db-expression".into()))
|
||||
.category(Category::Custom("db-expression".into()))
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "expression"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Creates an AND expression",
|
||||
example: r#"(field a) > 1 | and ((field a) < 10) | into nu"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["left".into(), "op".into(), "right".into()],
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: vec!["left".into(), "op".into(), "right".into()],
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: vec!["value".into(), "quoted_style".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "None".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: ">".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "AND".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Record {
|
||||
cols: vec!["left".into(), "op".into(), "right".into()],
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: vec!["value".into(), "quoted_style".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "None".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "<".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "10".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let value: Value = call.req(engine_state, stack, 0)?;
|
||||
let expr = ExprDb::try_from_value(&value)?.into_native();
|
||||
|
||||
let expression = ExprDb::try_from_pipeline(input, call.head)?;
|
||||
let expression = Expr::BinaryOp {
|
||||
left: Box::new(expression.into_native()),
|
||||
op: BinaryOperator::And,
|
||||
right: Box::new(expr),
|
||||
};
|
||||
|
||||
let expression: ExprDb = Expr::Nested(Box::new(expression)).into();
|
||||
Ok(expression.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::FieldExpr;
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![Box::new(AndExpr {}), Box::new(FieldExpr {})])
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
use crate::database::values::dsl::{ExprDb, SelectDb};
|
||||
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExprAsNu;
|
||||
|
||||
impl Command for ExprAsNu {
|
||||
fn name(&self) -> &str {
|
||||
"into nu"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert a db expression into a nu value for access and exploration"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_type(Type::Custom("db-expression".into()))
|
||||
.output_type(Type::Any)
|
||||
.category(Category::Custom("db-expression".into()))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Convert a col expression into a nushell value",
|
||||
example: "field name_1 | into nu",
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["value".into(), "quoted_style".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "name_1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "None".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let value = input.into_value(call.head);
|
||||
if let Ok(expr) = ExprDb::try_from_value(&value) {
|
||||
Ok(expr.to_value(call.head).into_pipeline_data())
|
||||
} else {
|
||||
let select = SelectDb::try_from_value(&value)?;
|
||||
Ok(select.to_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::FieldExpr;
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![Box::new(ExprAsNu {}), Box::new(FieldExpr {})])
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
use crate::database::values::dsl::ExprDb;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FieldExpr;
|
||||
|
||||
impl Command for FieldExpr {
|
||||
fn name(&self) -> &str {
|
||||
"field"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("name", SyntaxShape::String, "column name")
|
||||
.input_type(Type::Any)
|
||||
.output_type(Type::Custom("db-expression".into()))
|
||||
.category(Category::Custom("db-expression".into()))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates column expression for database"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "column", "expression"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Creates a named field expression",
|
||||
example: "field name_1 | into nu",
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["value".into(), "quoted_style".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "name_1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "None".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let value: Value = call.req(engine_state, stack, 0)?;
|
||||
let expression = ExprDb::try_from_value(&value)?;
|
||||
|
||||
Ok(expression.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![Box::new(FieldExpr {})])
|
||||
}
|
||||
}
|
|
@ -1,157 +0,0 @@
|
|||
use crate::database::values::dsl::ExprDb;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{Expr, Function, FunctionArg, FunctionArgExpr, Ident, ObjectName};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FunctionExpr;
|
||||
|
||||
impl Command for FunctionExpr {
|
||||
fn name(&self) -> &str {
|
||||
"fn"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("name", SyntaxShape::String, "function name")
|
||||
.switch("distinct", "distict values", Some('d'))
|
||||
.rest("arguments", SyntaxShape::Any, "function arguments")
|
||||
.input_type(Type::Any)
|
||||
.output_type(Type::Custom("db-expression".into()))
|
||||
.category(Category::Custom("db-expression".into()))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates function expression for a select operation"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Creates a function expression",
|
||||
example: "fn count name_1 | into nu",
|
||||
result: Some(Value::Record {
|
||||
cols: vec![
|
||||
"name".into(),
|
||||
"args".into(),
|
||||
"over".into(),
|
||||
"distinct".into(),
|
||||
],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "count".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::List {
|
||||
vals: vec![Value::String {
|
||||
val: "name_1".into(),
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "None".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Bool {
|
||||
val: false,
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "orders query by a column",
|
||||
example: r#"open db.sqlite
|
||||
| from table table_a
|
||||
| select (fn lead col_a)
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT lead(col_a) FROM table_a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "function", "expression"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let name: String = call.req(engine_state, stack, 0)?;
|
||||
let vals: Vec<Value> = call.rest(engine_state, stack, 1)?;
|
||||
let value = Value::List {
|
||||
vals,
|
||||
span: call.head,
|
||||
};
|
||||
let expressions = ExprDb::extract_exprs(value)?;
|
||||
|
||||
let name: Vec<Ident> = name
|
||||
.split('.')
|
||||
.map(|part| Ident {
|
||||
value: part.to_string(),
|
||||
quote_style: None,
|
||||
})
|
||||
.collect();
|
||||
let name = ObjectName(name);
|
||||
|
||||
let args: Vec<FunctionArg> = expressions
|
||||
.into_iter()
|
||||
.map(|expr| {
|
||||
let arg = FunctionArgExpr::Expr(expr);
|
||||
|
||||
FunctionArg::Unnamed(arg)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let expression: ExprDb = Expr::Function(Function {
|
||||
name,
|
||||
args,
|
||||
over: None,
|
||||
distinct: call.has_flag("distinct"),
|
||||
special: false,
|
||||
})
|
||||
.into();
|
||||
|
||||
Ok(expression.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::commands::{FromDb, ProjectionDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(FunctionExpr {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
])
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
// Conversions between value and sqlparser objects
|
||||
mod alias;
|
||||
mod and;
|
||||
mod as_nu;
|
||||
mod field;
|
||||
mod function;
|
||||
mod or;
|
||||
mod over;
|
||||
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
|
||||
pub(crate) use alias::AliasExpr;
|
||||
pub(crate) use and::AndExpr;
|
||||
pub(crate) use as_nu::ExprAsNu;
|
||||
pub(crate) use field::FieldExpr;
|
||||
pub(crate) use function::FunctionExpr;
|
||||
pub(crate) use or::OrExpr;
|
||||
pub(crate) use over::OverExpr;
|
||||
|
||||
pub fn add_expressions_decls(working_set: &mut StateWorkingSet) {
|
||||
macro_rules! bind_command {
|
||||
( $command:expr ) => {
|
||||
working_set.add_decl(Box::new($command));
|
||||
};
|
||||
( $( $command:expr ),* ) => {
|
||||
$( working_set.add_decl(Box::new($command)); )*
|
||||
};
|
||||
}
|
||||
|
||||
// Series commands
|
||||
bind_command!(
|
||||
ExprAsNu,
|
||||
AliasExpr,
|
||||
AndExpr,
|
||||
FieldExpr,
|
||||
FunctionExpr,
|
||||
OrExpr,
|
||||
OverExpr
|
||||
);
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
use crate::database::values::dsl::ExprDb;
|
||||
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{BinaryOperator, Expr};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OrExpr;
|
||||
|
||||
impl Command for OrExpr {
|
||||
fn name(&self) -> &str {
|
||||
"or"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Includes an OR clause for an expression"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("or", SyntaxShape::Any, "OR expression")
|
||||
.input_type(Type::Custom("db-expression".into()))
|
||||
.output_type(Type::Custom("db-expression".into()))
|
||||
.category(Category::Custom("db-expression".into()))
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "expression"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Creates an AND expression",
|
||||
example: r#"(field a) > 1 | or ((field a) < 10) | into nu"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["left".into(), "op".into(), "right".into()],
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: vec!["left".into(), "op".into(), "right".into()],
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: vec!["value".into(), "quoted_style".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "None".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: ">".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "OR".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Record {
|
||||
cols: vec!["left".into(), "op".into(), "right".into()],
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: vec!["value".into(), "quoted_style".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "None".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "<".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "10".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let value: Value = call.req(engine_state, stack, 0)?;
|
||||
let expr = ExprDb::try_from_value(&value)?.into_native();
|
||||
|
||||
let expression = ExprDb::try_from_pipeline(input, call.head)?;
|
||||
let expression = Expr::BinaryOp {
|
||||
left: Box::new(expression.into_native()),
|
||||
op: BinaryOperator::Or,
|
||||
right: Box::new(expr),
|
||||
};
|
||||
|
||||
let expression: ExprDb = Expr::Nested(Box::new(expression)).into();
|
||||
Ok(expression.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::FieldExpr;
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![Box::new(OrExpr {}), Box::new(FieldExpr {})])
|
||||
}
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
use crate::database::values::dsl::ExprDb;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{Expr, WindowSpec};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OverExpr;
|
||||
|
||||
impl Command for OverExpr {
|
||||
fn name(&self) -> &str {
|
||||
"over"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.rest(
|
||||
"partition-by",
|
||||
SyntaxShape::Any,
|
||||
"columns to partition the window function",
|
||||
)
|
||||
.input_type(Type::Custom("db-expression".into()))
|
||||
.output_type(Type::Custom("db-expression".into()))
|
||||
.category(Category::Custom("db-expression".into()))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Adds a partition to an expression function"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Adds a partition to a function expression",
|
||||
example: "fn avg col_a | over col_b | into nu",
|
||||
result: Some(Value::Record {
|
||||
cols: vec![
|
||||
"name".into(),
|
||||
"args".into(),
|
||||
"over".into(),
|
||||
"distinct".into(),
|
||||
],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "avg".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::List {
|
||||
vals: vec![Value::String {
|
||||
val: "col_a".into(),
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "Some(WindowSpec { partition_by: [Identifier(Ident { value: \"col_b\", quote_style: None })], order_by: [], window_frame: None })".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Bool {
|
||||
val: false,
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "orders query by a column",
|
||||
example: r#"open db.sqlite
|
||||
| from table table_a
|
||||
| select (fn lead col_a | over col_b)
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.sqlite".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT lead(col_a) OVER (PARTITION BY col_b) FROM table_a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "expression"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||
let value = Value::List {
|
||||
vals,
|
||||
span: call.head,
|
||||
};
|
||||
let partitions = ExprDb::extract_exprs(value)?;
|
||||
|
||||
let mut expression = ExprDb::try_from_pipeline(input, call.head)?;
|
||||
match expression.as_mut() {
|
||||
Expr::Function(function) => {
|
||||
function.over = Some(WindowSpec {
|
||||
partition_by: partitions,
|
||||
order_by: Vec::new(),
|
||||
window_frame: None,
|
||||
});
|
||||
}
|
||||
s => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Expression doesnt define a function".into(),
|
||||
format!("Expected an expression with a function. Got {}", s),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(expression.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::commands::{FromDb, ProjectionDb};
|
||||
use super::super::FunctionExpr;
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(OverExpr {}),
|
||||
Box::new(FunctionExpr {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
])
|
||||
}
|
||||
}
|
|
@ -1,9 +1,7 @@
|
|||
mod commands;
|
||||
mod expressions;
|
||||
mod values;
|
||||
|
||||
use commands::add_commands_decls;
|
||||
use expressions::add_expressions_decls;
|
||||
|
||||
pub use values::{
|
||||
convert_sqlite_row_to_nu_value, convert_sqlite_value_to_nu_value, open_connection_in_memory,
|
||||
|
@ -14,8 +12,4 @@ use nu_protocol::engine::StateWorkingSet;
|
|||
|
||||
pub fn add_database_decls(working_set: &mut StateWorkingSet) {
|
||||
add_commands_decls(working_set);
|
||||
add_expressions_decls(working_set);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_database;
|
||||
|
|
|
@ -1,141 +0,0 @@
|
|||
use std::path::Path;
|
||||
|
||||
use super::commands::{DescribeDb, ToDataBase};
|
||||
use super::expressions::ExprAsNu;
|
||||
use crate::SQLiteDatabase;
|
||||
use nu_engine::{eval_block, CallExt};
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::{
|
||||
engine::{Command, EngineState, Stack, StateWorkingSet},
|
||||
Category, IntoPipelineData, PipelineData, Signature, Span, Type,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CustomOpen;
|
||||
|
||||
impl Command for CustomOpen {
|
||||
fn name(&self) -> &str {
|
||||
"open"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Mock open file command"
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build(self.name())
|
||||
.required(
|
||||
"filename",
|
||||
nu_protocol::SyntaxShape::String,
|
||||
"the filename to use",
|
||||
)
|
||||
.input_type(Type::Any)
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &nu_protocol::ast::Call,
|
||||
_input: nu_protocol::PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let path: String = call.req(engine_state, stack, 0)?;
|
||||
let path = Path::new(&path);
|
||||
|
||||
let db = SQLiteDatabase::new(path);
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_database(cmds: Vec<Box<dyn Command + 'static>>) {
|
||||
if cmds.is_empty() {
|
||||
panic!("Empty commands vector")
|
||||
}
|
||||
|
||||
// The first element in the cmds vector must be the one tested
|
||||
let examples = cmds[0].examples();
|
||||
let mut engine_state = Box::new(EngineState::new());
|
||||
|
||||
let delta = {
|
||||
// Base functions that are needed for testing
|
||||
// Try to keep this working set small to keep tests running as fast as possible
|
||||
let mut working_set = StateWorkingSet::new(&*engine_state);
|
||||
working_set.add_decl(Box::new(DescribeDb {}));
|
||||
working_set.add_decl(Box::new(ToDataBase {}));
|
||||
working_set.add_decl(Box::new(CustomOpen {}));
|
||||
working_set.add_decl(Box::new(ExprAsNu {}));
|
||||
|
||||
// Adding the command that is being tested to the working set
|
||||
for cmd in cmds {
|
||||
working_set.add_decl(cmd);
|
||||
}
|
||||
|
||||
working_set.render()
|
||||
};
|
||||
|
||||
engine_state
|
||||
.merge_delta(delta)
|
||||
.expect("Error merging delta");
|
||||
|
||||
for example in examples {
|
||||
// Skip tests that don't have results to compare to
|
||||
if example.result.is_none() {
|
||||
continue;
|
||||
}
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(&*engine_state);
|
||||
let (output, err) = parse(
|
||||
&mut working_set,
|
||||
None,
|
||||
example.example.as_bytes(),
|
||||
false,
|
||||
&[],
|
||||
);
|
||||
|
||||
if let Some(err) = err {
|
||||
panic!("test parse error in `{}`: {:?}", example.example, err)
|
||||
}
|
||||
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
engine_state
|
||||
.merge_delta(delta)
|
||||
.expect("Error merging delta");
|
||||
|
||||
let mut stack = Stack::new();
|
||||
|
||||
match eval_block(
|
||||
&engine_state,
|
||||
&mut stack,
|
||||
&block,
|
||||
PipelineData::new(Span::test_data()),
|
||||
true,
|
||||
true,
|
||||
) {
|
||||
Err(err) => panic!("test eval error in `{}`: {:?}", example.example, err),
|
||||
Ok(result) => {
|
||||
let result = result.into_value(Span::test_data());
|
||||
println!("input: {}", example.example);
|
||||
println!("result: {:?}", result);
|
||||
println!("done: {:?}", start.elapsed());
|
||||
|
||||
// Note. Value implements PartialEq for Bool, Int, Float, String and Block
|
||||
// If the command you are testing requires to compare another case, then
|
||||
// you need to define its equality in the Value struct
|
||||
if let Some(expected) = example.result {
|
||||
if result != expected {
|
||||
panic!(
|
||||
"the example result is different to expected value: {:?} != {:?}",
|
||||
result, expected
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,3 @@
|
|||
use nu_protocol::{ShellError, Span};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fmt::Display, path::PathBuf};
|
||||
|
||||
pub mod db;
|
||||
pub mod db_column;
|
||||
pub mod db_constraint;
|
||||
|
@ -10,24 +6,3 @@ pub mod db_index;
|
|||
pub mod db_row;
|
||||
pub mod db_schema;
|
||||
pub mod db_table;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub enum ConnectionDb {
|
||||
Path(PathBuf),
|
||||
}
|
||||
|
||||
impl Display for ConnectionDb {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Path(path) => write!(f, "{}", path.to_str().unwrap_or("")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectionDb {
|
||||
pub fn as_path(&self, _span: Span) -> Result<&PathBuf, ShellError> {
|
||||
match self {
|
||||
Self::Path(path) => Ok(path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,373 +0,0 @@
|
|||
use nu_protocol::{
|
||||
ast::{Operator, PathMember},
|
||||
CustomValue, PipelineData, ShellError, Span, Type, Value,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlparser::ast::{BinaryOperator, Expr, Ident};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ExprDb(Expr);
|
||||
|
||||
// Referenced access to the native expression
|
||||
impl AsRef<Expr> for ExprDb {
|
||||
fn as_ref(&self) -> &Expr {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<Expr> for ExprDb {
|
||||
fn as_mut(&mut self) -> &mut Expr {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Expr> for ExprDb {
|
||||
fn from(expr: Expr) -> Self {
|
||||
Self(expr)
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomValue for ExprDb {
|
||||
fn clone_value(&self, span: Span) -> Value {
|
||||
let cloned = Self(self.0.clone());
|
||||
|
||||
Value::CustomValue {
|
||||
val: Box::new(cloned),
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
fn value_string(&self) -> String {
|
||||
self.typetag_name().to_string()
|
||||
}
|
||||
|
||||
fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
|
||||
Ok(self.to_value(span))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn follow_path_int(&self, count: usize, span: Span) -> Result<Value, ShellError> {
|
||||
let path = PathMember::Int { val: count, span };
|
||||
|
||||
ExprDb::expr_to_value(self.as_ref(), span).follow_cell_path(&[path], false)
|
||||
}
|
||||
|
||||
fn follow_path_string(&self, column_name: String, span: Span) -> Result<Value, ShellError> {
|
||||
let path = PathMember::String {
|
||||
val: column_name,
|
||||
span,
|
||||
};
|
||||
|
||||
ExprDb::expr_to_value(self.as_ref(), span).follow_cell_path(&[path], false)
|
||||
}
|
||||
|
||||
fn typetag_name(&self) -> &'static str {
|
||||
"DB expresssion"
|
||||
}
|
||||
|
||||
fn typetag_deserialize(&self) {
|
||||
unimplemented!("typetag_deserialize")
|
||||
}
|
||||
|
||||
fn operation(
|
||||
&self,
|
||||
lhs_span: Span,
|
||||
operator: Operator,
|
||||
op: Span,
|
||||
right: &Value,
|
||||
) -> Result<Value, ShellError> {
|
||||
let right_expr = match right {
|
||||
Value::CustomValue { .. } => ExprDb::try_from_value(right).map(ExprDb::into_native),
|
||||
Value::String { val, .. } => Ok(Expr::Value(
|
||||
sqlparser::ast::Value::SingleQuotedString(val.clone()),
|
||||
)),
|
||||
Value::Int { val, .. } => Ok(Expr::Value(sqlparser::ast::Value::Number(
|
||||
format!("{}", val),
|
||||
false,
|
||||
))),
|
||||
Value::Bool { val, .. } => Ok(Expr::Value(sqlparser::ast::Value::Boolean(*val))),
|
||||
_ => Err(ShellError::OperatorMismatch {
|
||||
op_span: op,
|
||||
lhs_ty: Type::Custom(self.typetag_name().into()),
|
||||
lhs_span,
|
||||
rhs_ty: right.get_type(),
|
||||
rhs_span: right.span()?,
|
||||
}),
|
||||
}?;
|
||||
|
||||
let sql_operator = match operator {
|
||||
Operator::Equal => Ok(BinaryOperator::Eq),
|
||||
Operator::NotEqual => Ok(BinaryOperator::NotEq),
|
||||
Operator::LessThan => Ok(BinaryOperator::Lt),
|
||||
Operator::GreaterThan => Ok(BinaryOperator::Gt),
|
||||
Operator::LessThanOrEqual => Ok(BinaryOperator::LtEq),
|
||||
Operator::GreaterThanOrEqual => Ok(BinaryOperator::GtEq),
|
||||
Operator::RegexMatch => Ok(BinaryOperator::PGRegexMatch),
|
||||
Operator::NotRegexMatch => Ok(BinaryOperator::PGRegexNotMatch),
|
||||
Operator::Plus => Ok(BinaryOperator::Plus),
|
||||
Operator::Minus => Ok(BinaryOperator::Minus),
|
||||
Operator::Multiply => Ok(BinaryOperator::Multiply),
|
||||
Operator::Divide => Ok(BinaryOperator::Divide),
|
||||
Operator::Modulo => Ok(BinaryOperator::Modulo),
|
||||
Operator::FloorDivision => Ok(BinaryOperator::Divide),
|
||||
Operator::And => Ok(BinaryOperator::And),
|
||||
Operator::Or => Ok(BinaryOperator::Or),
|
||||
Operator::In
|
||||
| Operator::NotIn
|
||||
| Operator::Pow
|
||||
| Operator::BitOr
|
||||
| Operator::BitXor
|
||||
| Operator::BitAnd
|
||||
| Operator::ShiftLeft
|
||||
| Operator::ShiftRight
|
||||
| Operator::StartsWith
|
||||
| Operator::EndsWith
|
||||
| Operator::Append => Err(ShellError::UnsupportedOperator(operator, op)),
|
||||
}?;
|
||||
|
||||
let expr = Expr::BinaryOp {
|
||||
left: Box::new(self.as_ref().clone()),
|
||||
op: sql_operator,
|
||||
right: Box::new(right_expr),
|
||||
};
|
||||
|
||||
Ok(ExprDb(expr).into_value(lhs_span))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExprDb {
|
||||
pub fn try_from_value(value: &Value) -> Result<Self, ShellError> {
|
||||
match value {
|
||||
Value::CustomValue { val, span } => match val.as_any().downcast_ref::<Self>() {
|
||||
Some(expr) => Ok(Self(expr.0.clone())),
|
||||
None => Err(ShellError::CantConvert(
|
||||
"db expression".into(),
|
||||
"non-expression".into(),
|
||||
*span,
|
||||
None,
|
||||
)),
|
||||
},
|
||||
Value::String { val, .. } => Ok(Expr::Identifier(Ident {
|
||||
value: val.clone(),
|
||||
quote_style: None,
|
||||
})
|
||||
.into()),
|
||||
Value::Int { val, .. } => {
|
||||
Ok(Expr::Value(sqlparser::ast::Value::Number(format!("{}", val), false)).into())
|
||||
}
|
||||
x => Err(ShellError::CantConvert(
|
||||
"database".into(),
|
||||
x.get_type().to_string(),
|
||||
x.span()?,
|
||||
None,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_from_pipeline(input: PipelineData, span: Span) -> Result<Self, ShellError> {
|
||||
let value = input.into_value(span);
|
||||
Self::try_from_value(&value)
|
||||
}
|
||||
|
||||
pub fn into_value(self, span: Span) -> Value {
|
||||
Value::CustomValue {
|
||||
val: Box::new(self),
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_native(self) -> Expr {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn to_value(&self, span: Span) -> Value {
|
||||
ExprDb::expr_to_value(self.as_ref(), span)
|
||||
}
|
||||
|
||||
// Convenient function to extrac multiple Expr that could be inside a nushell Value
|
||||
pub fn extract_exprs(value: Value) -> Result<Vec<Expr>, ShellError> {
|
||||
ExtractedExpr::extract_exprs(value).map(ExtractedExpr::into_exprs)
|
||||
}
|
||||
}
|
||||
|
||||
enum ExtractedExpr {
|
||||
Single(Expr),
|
||||
List(Vec<ExtractedExpr>),
|
||||
}
|
||||
|
||||
impl ExtractedExpr {
|
||||
fn into_exprs(self) -> Vec<Expr> {
|
||||
match self {
|
||||
Self::Single(expr) => vec![expr],
|
||||
Self::List(exprs) => exprs
|
||||
.into_iter()
|
||||
.flat_map(ExtractedExpr::into_exprs)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_exprs(value: Value) -> Result<ExtractedExpr, ShellError> {
|
||||
match value {
|
||||
Value::String { val, .. } => {
|
||||
let expr = Expr::Identifier(Ident {
|
||||
value: val,
|
||||
quote_style: None,
|
||||
});
|
||||
|
||||
Ok(ExtractedExpr::Single(expr))
|
||||
}
|
||||
Value::Int { val, .. } => {
|
||||
let expr = Expr::Value(sqlparser::ast::Value::Number(format!("{}", val), false));
|
||||
|
||||
Ok(ExtractedExpr::Single(expr))
|
||||
}
|
||||
Value::Bool { val, .. } => {
|
||||
let expr = Expr::Value(sqlparser::ast::Value::Boolean(val));
|
||||
|
||||
Ok(ExtractedExpr::Single(expr))
|
||||
}
|
||||
Value::CustomValue { .. } => {
|
||||
let expr = ExprDb::try_from_value(&value)?.into_native();
|
||||
Ok(ExtractedExpr::Single(expr))
|
||||
}
|
||||
Value::List { vals, .. } => vals
|
||||
.into_iter()
|
||||
.map(Self::extract_exprs)
|
||||
.collect::<Result<Vec<ExtractedExpr>, ShellError>>()
|
||||
.map(ExtractedExpr::List),
|
||||
x => Err(ShellError::CantConvert(
|
||||
"selection".into(),
|
||||
x.get_type().to_string(),
|
||||
x.span()?,
|
||||
None,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExprDb {
|
||||
pub fn expr_to_value(expr: &Expr, span: Span) -> Value {
|
||||
match expr {
|
||||
Expr::Identifier(ident) => {
|
||||
let cols = vec!["value".into(), "quoted_style".into()];
|
||||
let val = Value::String {
|
||||
val: ident.value.to_string(),
|
||||
span,
|
||||
};
|
||||
let style = Value::String {
|
||||
val: format!("{:?}", ident.quote_style),
|
||||
span,
|
||||
};
|
||||
|
||||
Value::Record {
|
||||
cols,
|
||||
vals: vec![val, style],
|
||||
span,
|
||||
}
|
||||
}
|
||||
Expr::Value(value) => Value::String {
|
||||
val: format!("{}", value),
|
||||
span,
|
||||
},
|
||||
Expr::BinaryOp { left, op, right } => {
|
||||
let cols = vec!["left".into(), "op".into(), "right".into()];
|
||||
let left = ExprDb::expr_to_value(left.as_ref(), span);
|
||||
let right = ExprDb::expr_to_value(right.as_ref(), span);
|
||||
let op = Value::String {
|
||||
val: format!("{}", op),
|
||||
span,
|
||||
};
|
||||
|
||||
let vals = vec![left, op, right];
|
||||
|
||||
Value::Record { cols, vals, span }
|
||||
}
|
||||
Expr::Function(function) => {
|
||||
let cols = vec![
|
||||
"name".into(),
|
||||
"args".into(),
|
||||
"over".into(),
|
||||
"distinct".into(),
|
||||
];
|
||||
let name = Value::String {
|
||||
val: function.name.to_string(),
|
||||
span,
|
||||
};
|
||||
|
||||
let args: Vec<Value> = function
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg| Value::String {
|
||||
val: arg.to_string(),
|
||||
span,
|
||||
})
|
||||
.collect();
|
||||
let args = Value::List { vals: args, span };
|
||||
|
||||
let over = Value::String {
|
||||
val: format!("{:?}", function.over),
|
||||
span,
|
||||
};
|
||||
|
||||
let distinct = Value::Bool {
|
||||
val: function.distinct,
|
||||
span,
|
||||
};
|
||||
|
||||
let vals = vec![name, args, over, distinct];
|
||||
Value::Record { cols, vals, span }
|
||||
}
|
||||
Expr::Nested(expr) => ExprDb::expr_to_value(expr, span),
|
||||
Expr::CompoundIdentifier(_) => todo!(),
|
||||
Expr::IsNull(_) => todo!(),
|
||||
Expr::IsNotNull(_) => todo!(),
|
||||
Expr::IsDistinctFrom(_, _) => todo!(),
|
||||
Expr::IsNotDistinctFrom(_, _) => todo!(),
|
||||
Expr::InList { .. } => todo!(),
|
||||
Expr::InSubquery { .. } => todo!(),
|
||||
Expr::InUnnest { .. } => todo!(),
|
||||
Expr::Between { .. } => todo!(),
|
||||
Expr::UnaryOp { .. } => todo!(),
|
||||
Expr::Cast { .. } => todo!(),
|
||||
Expr::TryCast { .. } => todo!(),
|
||||
Expr::Extract { .. } => todo!(),
|
||||
Expr::Substring { .. } => todo!(),
|
||||
Expr::Trim { .. } => todo!(),
|
||||
Expr::Collate { .. } => todo!(),
|
||||
Expr::TypedString { .. } => todo!(),
|
||||
Expr::MapAccess { .. } => todo!(),
|
||||
Expr::Case { .. } => todo!(),
|
||||
Expr::Exists { .. } => todo!(),
|
||||
Expr::Subquery(_) => todo!(),
|
||||
Expr::ListAgg(_) => todo!(),
|
||||
Expr::GroupingSets(_) => todo!(),
|
||||
Expr::Cube(_) => todo!(),
|
||||
Expr::Rollup(_) => todo!(),
|
||||
Expr::Tuple(_) => todo!(),
|
||||
Expr::ArrayIndex { .. } => todo!(),
|
||||
Expr::Array(_) => todo!(),
|
||||
Expr::JsonAccess { .. } => todo!(),
|
||||
Expr::CompositeAccess { .. } => todo!(),
|
||||
Expr::IsFalse(_) => todo!(),
|
||||
Expr::IsNotFalse(_) => todo!(),
|
||||
Expr::IsTrue(_) => todo!(),
|
||||
Expr::IsNotTrue(_) => todo!(),
|
||||
Expr::IsUnknown(_) => todo!(),
|
||||
Expr::IsNotUnknown(_) => todo!(),
|
||||
Expr::Like { .. } => todo!(),
|
||||
Expr::ILike { .. } => todo!(),
|
||||
Expr::SimilarTo { .. } => todo!(),
|
||||
Expr::AnyOp(_) => todo!(),
|
||||
Expr::AllOp(_) => todo!(),
|
||||
Expr::SafeCast { .. } => todo!(),
|
||||
Expr::AtTimeZone { .. } => todo!(),
|
||||
Expr::Position { .. } => todo!(),
|
||||
Expr::Overlay { .. } => todo!(),
|
||||
Expr::AggregateExpressionWithFilter { .. } => todo!(),
|
||||
Expr::ArraySubquery(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
mod expression;
|
||||
mod select_item;
|
||||
|
||||
pub(crate) use expression::ExprDb;
|
||||
pub(crate) use select_item::SelectDb;
|
|
@ -1,263 +0,0 @@
|
|||
use super::ExprDb;
|
||||
use nu_protocol::{ast::PathMember, CustomValue, ShellError, Span, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlparser::ast::{Expr, Ident, ObjectName, SelectItem};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SelectDb(SelectItem);
|
||||
|
||||
// Referenced access to the native expression
|
||||
impl AsRef<SelectItem> for SelectDb {
|
||||
fn as_ref(&self) -> &SelectItem {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<SelectItem> for SelectDb {
|
||||
fn as_mut(&mut self) -> &mut SelectItem {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SelectItem> for SelectDb {
|
||||
fn from(selection: SelectItem) -> Self {
|
||||
Self(selection)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Expr> for SelectDb {
|
||||
fn from(expr: Expr) -> Self {
|
||||
SelectItem::UnnamedExpr(expr).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomValue for SelectDb {
|
||||
fn clone_value(&self, span: Span) -> Value {
|
||||
let cloned = Self(self.0.clone());
|
||||
|
||||
Value::CustomValue {
|
||||
val: Box::new(cloned),
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
fn value_string(&self) -> String {
|
||||
self.typetag_name().to_string()
|
||||
}
|
||||
|
||||
fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
|
||||
Ok(self.to_value(span))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn follow_path_int(&self, count: usize, span: Span) -> Result<Value, ShellError> {
|
||||
let path = PathMember::Int { val: count, span };
|
||||
|
||||
SelectDb::select_to_value(self.as_ref(), span).follow_cell_path(&[path], false)
|
||||
}
|
||||
|
||||
fn follow_path_string(&self, column_name: String, span: Span) -> Result<Value, ShellError> {
|
||||
let path = PathMember::String {
|
||||
val: column_name,
|
||||
span,
|
||||
};
|
||||
SelectDb::select_to_value(self.as_ref(), span).follow_cell_path(&[path], false)
|
||||
}
|
||||
|
||||
fn typetag_name(&self) -> &'static str {
|
||||
"DB selection"
|
||||
}
|
||||
|
||||
fn typetag_deserialize(&self) {
|
||||
unimplemented!("typetag_deserialize")
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectDb {
|
||||
pub fn try_from_value(value: &Value) -> Result<Self, ShellError> {
|
||||
match value {
|
||||
Value::CustomValue { val, span } => match val.as_any().downcast_ref::<Self>() {
|
||||
Some(expr) => Ok(Self(expr.0.clone())),
|
||||
None => Err(ShellError::CantConvert(
|
||||
"db selection".into(),
|
||||
"non-expression".into(),
|
||||
*span,
|
||||
None,
|
||||
)),
|
||||
},
|
||||
Value::String { val, .. } => match val.as_str() {
|
||||
"*" => Ok(SelectItem::Wildcard.into()),
|
||||
name if (name.contains('.') && name.contains('*')) => {
|
||||
let parts: Vec<Ident> = name
|
||||
.split('.')
|
||||
.filter(|part| part != &"*")
|
||||
.map(|part| Ident {
|
||||
value: part.to_string(),
|
||||
quote_style: None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(SelectItem::QualifiedWildcard(ObjectName(parts)).into())
|
||||
}
|
||||
name if name.contains('.') => {
|
||||
let parts: Vec<Ident> = name
|
||||
.split('.')
|
||||
.map(|part| Ident {
|
||||
value: part.to_string(),
|
||||
quote_style: None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let expr = Expr::CompoundIdentifier(parts);
|
||||
Ok(SelectItem::UnnamedExpr(expr).into())
|
||||
}
|
||||
_ => {
|
||||
let expr = Expr::Identifier(Ident {
|
||||
value: val.clone(),
|
||||
quote_style: None,
|
||||
});
|
||||
|
||||
Ok(SelectItem::UnnamedExpr(expr).into())
|
||||
}
|
||||
},
|
||||
x => Err(ShellError::CantConvert(
|
||||
"selection".into(),
|
||||
x.get_type().to_string(),
|
||||
x.span()?,
|
||||
None,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_value(self, span: Span) -> Value {
|
||||
Value::CustomValue {
|
||||
val: Box::new(self),
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_native(self) -> SelectItem {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn to_value(&self, span: Span) -> Value {
|
||||
SelectDb::select_to_value(self.as_ref(), span)
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectDb {
|
||||
fn select_to_value(select: &SelectItem, span: Span) -> Value {
|
||||
match select {
|
||||
SelectItem::UnnamedExpr(expr) => ExprDb::expr_to_value(expr, span),
|
||||
SelectItem::ExprWithAlias { expr, alias } => {
|
||||
let expr = ExprDb::expr_to_value(expr, span);
|
||||
|
||||
let val = Value::String {
|
||||
val: alias.value.to_string(),
|
||||
span,
|
||||
};
|
||||
let style = Value::String {
|
||||
val: format!("{:?}", alias.quote_style),
|
||||
span,
|
||||
};
|
||||
|
||||
let cols = vec!["value".into(), "quoted_style".into()];
|
||||
let alias = Value::Record {
|
||||
cols,
|
||||
vals: vec![val, style],
|
||||
span,
|
||||
};
|
||||
|
||||
let cols = vec!["expression".into(), "alias".into()];
|
||||
Value::Record {
|
||||
cols,
|
||||
vals: vec![expr, alias],
|
||||
span,
|
||||
}
|
||||
}
|
||||
SelectItem::QualifiedWildcard(object) => {
|
||||
let vals: Vec<Value> = object
|
||||
.0
|
||||
.iter()
|
||||
.map(|ident| Value::String {
|
||||
val: ident.value.clone(),
|
||||
span,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Value::List { vals, span }
|
||||
}
|
||||
SelectItem::Wildcard => Value::String {
|
||||
val: "*".into(),
|
||||
span,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Convenient function to extrac multiple SelectItem that could be inside a
|
||||
// nushell Value
|
||||
pub fn extract_selects(value: Value) -> Result<Vec<SelectItem>, ShellError> {
|
||||
ExtractedSelect::extract_selects(value).map(ExtractedSelect::into_selects)
|
||||
}
|
||||
}
|
||||
|
||||
// Enum to represent the parsing of the selects from Value
|
||||
enum ExtractedSelect {
|
||||
Single(SelectItem),
|
||||
List(Vec<ExtractedSelect>),
|
||||
}
|
||||
|
||||
impl ExtractedSelect {
|
||||
fn into_selects(self) -> Vec<SelectItem> {
|
||||
match self {
|
||||
Self::Single(select) => vec![select],
|
||||
Self::List(selects) => selects
|
||||
.into_iter()
|
||||
.flat_map(ExtractedSelect::into_selects)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_selects(value: Value) -> Result<ExtractedSelect, ShellError> {
|
||||
match value {
|
||||
Value::String { val, .. } => {
|
||||
let expr = Expr::Identifier(Ident {
|
||||
value: val,
|
||||
quote_style: None,
|
||||
});
|
||||
|
||||
Ok(ExtractedSelect::Single(SelectItem::UnnamedExpr(expr)))
|
||||
}
|
||||
Value::CustomValue { .. } => {
|
||||
if let Ok(expr) = ExprDb::try_from_value(&value) {
|
||||
Ok(ExtractedSelect::Single(SelectItem::UnnamedExpr(
|
||||
expr.into_native(),
|
||||
)))
|
||||
} else if let Ok(select) = SelectDb::try_from_value(&value) {
|
||||
Ok(ExtractedSelect::Single(select.into_native()))
|
||||
} else {
|
||||
Err(ShellError::CantConvert(
|
||||
"selection".into(),
|
||||
value.get_type().to_string(),
|
||||
value.span()?,
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
Value::List { vals, .. } => vals
|
||||
.into_iter()
|
||||
.map(Self::extract_selects)
|
||||
.collect::<Result<Vec<ExtractedSelect>, ShellError>>()
|
||||
.map(ExtractedSelect::List),
|
||||
x => Err(ShellError::CantConvert(
|
||||
"selection".into(),
|
||||
x.get_type().to_string(),
|
||||
x.span()?,
|
||||
None,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
pub mod definitions;
|
||||
pub mod dsl;
|
||||
pub mod sqlite;
|
||||
|
||||
pub use sqlite::{
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
use super::definitions::ConnectionDb;
|
||||
use crate::database::values::definitions::{
|
||||
use super::definitions::{
|
||||
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, Serialize};
|
||||
use sqlparser::ast::Statement;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::Read,
|
||||
|
@ -20,15 +19,13 @@ pub struct SQLiteDatabase {
|
|||
// I considered storing a SQLite connection here, but decided against it because
|
||||
// 1) YAGNI, 2) it's not obvious how cloning a connection could work, 3) state
|
||||
// management gets tricky quick. Revisit this approach if we find a compelling use case.
|
||||
pub connection: ConnectionDb,
|
||||
pub statement: Option<Statement>,
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
impl SQLiteDatabase {
|
||||
pub fn new(path: &Path) -> Self {
|
||||
Self {
|
||||
connection: ConnectionDb::Path(PathBuf::from(path)),
|
||||
statement: None,
|
||||
path: PathBuf::from(path),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,8 +49,7 @@ impl SQLiteDatabase {
|
|||
match value {
|
||||
Value::CustomValue { val, span } => match val.as_any().downcast_ref::<Self>() {
|
||||
Some(db) => Ok(Self {
|
||||
connection: db.connection.clone(),
|
||||
statement: db.statement.clone(),
|
||||
path: db.path.clone(),
|
||||
}),
|
||||
None => Err(ShellError::CantConvert(
|
||||
"database".into(),
|
||||
|
@ -84,7 +80,7 @@ impl SQLiteDatabase {
|
|||
}
|
||||
|
||||
pub fn query(&self, sql: &Spanned<String>, call_span: Span) -> Result<Value, ShellError> {
|
||||
let db = open_sqlite_db(self.connection.as_path(call_span)?, call_span)?;
|
||||
let db = open_sqlite_db(&self.path, call_span)?;
|
||||
run_sql_query(db, sql).map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Failed to query SQLite database".into(),
|
||||
|
@ -96,58 +92,8 @@ impl SQLiteDatabase {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn collect(&self, call_span: Span) -> Result<Value, ShellError> {
|
||||
let sql = match &self.statement {
|
||||
Some(statement) => Ok(format!("{}", statement)),
|
||||
None => Err(ShellError::GenericError(
|
||||
"Error collecting from db".into(),
|
||||
"No query found in connection".into(),
|
||||
Some(call_span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
}?;
|
||||
|
||||
let sql = Spanned {
|
||||
item: sql,
|
||||
span: call_span,
|
||||
};
|
||||
|
||||
let db = open_sqlite_db(self.connection.as_path(call_span)?, call_span)?;
|
||||
run_sql_query(db, &sql).map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Failed to query SQLite database".into(),
|
||||
e.to_string(),
|
||||
Some(sql.span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn describe(&self, span: Span) -> Value {
|
||||
let cols = vec!["connection".to_string(), "query".to_string()];
|
||||
let connection = Value::String {
|
||||
val: self.connection.to_string(),
|
||||
span,
|
||||
};
|
||||
|
||||
let query = match &self.statement {
|
||||
Some(statement) => format!("{statement}"),
|
||||
None => "".into(),
|
||||
};
|
||||
|
||||
let query = Value::String { val: query, span };
|
||||
|
||||
Value::Record {
|
||||
cols,
|
||||
vals: vec![connection, query],
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_connection(&self) -> Result<Connection, rusqlite::Error> {
|
||||
let conn = match Connection::open(self.connection.to_string()) {
|
||||
let conn = match Connection::open(&self.path) {
|
||||
Ok(conn) => conn,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
|
@ -156,7 +102,6 @@ impl SQLiteDatabase {
|
|||
}
|
||||
|
||||
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| {
|
||||
|
@ -351,8 +296,7 @@ impl SQLiteDatabase {
|
|||
impl CustomValue for SQLiteDatabase {
|
||||
fn clone_value(&self, span: Span) -> Value {
|
||||
let cloned = SQLiteDatabase {
|
||||
connection: self.connection.clone(),
|
||||
statement: self.statement.clone(),
|
||||
path: self.path.clone(),
|
||||
};
|
||||
|
||||
Value::CustomValue {
|
||||
|
@ -366,9 +310,7 @@ impl CustomValue for SQLiteDatabase {
|
|||
}
|
||||
|
||||
fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
|
||||
match self.statement {
|
||||
None => {
|
||||
let db = open_sqlite_db(self.connection.as_path(span)?, span)?;
|
||||
let db = open_sqlite_db(&self.path, span)?;
|
||||
read_entire_sqlite_db(db, span).map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Failed to read from SQLite database".into(),
|
||||
|
@ -379,9 +321,6 @@ impl CustomValue for SQLiteDatabase {
|
|||
)
|
||||
})
|
||||
}
|
||||
Some(_) => self.collect(span),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
|
@ -393,7 +332,7 @@ impl CustomValue for SQLiteDatabase {
|
|||
}
|
||||
|
||||
fn follow_path_string(&self, _column_name: String, span: Span) -> Result<Value, ShellError> {
|
||||
let db = open_sqlite_db(self.connection.as_path(span)?, span)?;
|
||||
let db = open_sqlite_db(&self.path, span)?;
|
||||
|
||||
read_single_table(db, _column_name, span).map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
|
|
|
@ -59,7 +59,7 @@ fn fails_if_passing_engine_custom_values_to_plugins() {
|
|||
let actual = nu_with_plugins!(
|
||||
cwd: "tests/fixtures/formats",
|
||||
plugin: ("nu_plugin_custom_values"),
|
||||
"open-db sample.db | custom-value update"
|
||||
"open sample.db | custom-value update"
|
||||
);
|
||||
|
||||
assert!(actual
|
||||
|
|
Loading…
Reference in a new issue