mirror of
https://github.com/nushell/nushell
synced 2025-01-15 14:44:14 +00:00
Db commands without DB (#5838)
* database commands without db * database command tests
This commit is contained in:
parent
848ff8453b
commit
7164929c61
33 changed files with 2007 additions and 529 deletions
|
@ -1,21 +1,19 @@
|
||||||
use crate::{
|
use crate::SQLiteDatabase;
|
||||||
database::values::dsl::{ExprDb, SelectDb},
|
|
||||||
SQLiteDatabase,
|
|
||||||
};
|
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||||
|
Type, Value,
|
||||||
};
|
};
|
||||||
use sqlparser::ast::{Ident, SelectItem, SetExpr, Statement, TableAlias, TableFactor};
|
use sqlparser::ast::{Ident, SetExpr, Statement, TableAlias, TableFactor};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AliasExpr;
|
pub struct AliasDb;
|
||||||
|
|
||||||
impl Command for AliasExpr {
|
impl Command for AliasDb {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"db as"
|
"as"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
|
@ -29,11 +27,67 @@ impl Command for AliasExpr {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
description: "Creates an alias for a column selection",
|
Example {
|
||||||
example: "db col name_a | db as new_a",
|
description: "Creates an alias for a selected table",
|
||||||
result: None,
|
example: r#"open db.mysql
|
||||||
}]
|
| into db
|
||||||
|
| select a
|
||||||
|
| from table_1
|
||||||
|
| as t1
|
||||||
|
| describe"#,
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["connection".into(), "query".into()],
|
||||||
|
vals: vec![
|
||||||
|
Value::String {
|
||||||
|
val: "db.mysql".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.mysql
|
||||||
|
| into db
|
||||||
|
| select a
|
||||||
|
| from (
|
||||||
|
open db.mysql
|
||||||
|
| into db
|
||||||
|
| select a b
|
||||||
|
| from table_a
|
||||||
|
)
|
||||||
|
| as t1
|
||||||
|
| describe"#,
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["connection".into(), "query".into()],
|
||||||
|
vals: vec![
|
||||||
|
Value::String {
|
||||||
|
val: "db.mysql".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 input_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
@ -48,52 +102,12 @@ impl Command for AliasExpr {
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let alias: String = call.req(engine_state, stack, 0)?;
|
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) {
|
let db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||||
alias_selection(expr.into_native().into(), alias, call)
|
alias_db(db, alias, call)
|
||||||
} else if let Ok(select) = SelectDb::try_from_value(&value) {
|
|
||||||
alias_selection(select, alias, call)
|
|
||||||
} else if let Ok(db) = SQLiteDatabase::try_from_value(value.clone()) {
|
|
||||||
alias_db(db, alias, call)
|
|
||||||
} else {
|
|
||||||
Err(ShellError::CantConvert(
|
|
||||||
"expression or query".into(),
|
|
||||||
value.get_type().to_string(),
|
|
||||||
value.span()?,
|
|
||||||
None,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alias_db(
|
fn alias_db(
|
||||||
mut db: SQLiteDatabase,
|
mut db: SQLiteDatabase,
|
||||||
new_alias: String,
|
new_alias: String,
|
||||||
|
@ -142,7 +156,7 @@ fn alias_db(
|
||||||
},
|
},
|
||||||
s => {
|
s => {
|
||||||
return Err(ShellError::GenericError(
|
return Err(ShellError::GenericError(
|
||||||
"Connection doesnt define a query".into(),
|
"Connection doesn't define a query".into(),
|
||||||
format!("Expected a connection with query. Got {}", s),
|
format!("Expected a connection with query. Got {}", s),
|
||||||
Some(call.head),
|
Some(call.head),
|
||||||
None,
|
None,
|
||||||
|
@ -152,3 +166,19 @@ fn alias_db(
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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 {}),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||||
Value,
|
Type, Value,
|
||||||
};
|
};
|
||||||
use sqlparser::ast::{BinaryOperator, Expr, Query, Select, SetExpr, Statement};
|
use sqlparser::ast::{BinaryOperator, Expr, Query, Select, SetExpr, Statement};
|
||||||
|
|
||||||
|
@ -15,11 +15,11 @@ pub struct AndDb;
|
||||||
|
|
||||||
impl Command for AndDb {
|
impl Command for AndDb {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"db and"
|
"and"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Includes an AND clause for a query or expression"
|
"Includes an AND clause for a query"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
|
@ -32,26 +32,63 @@ impl Command for AndDb {
|
||||||
vec!["database", "where"]
|
vec!["database", "where"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn input_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "selects a column from a database with a where clause",
|
description: "Selects a column from a database with an AND clause",
|
||||||
example: r#"db open db.mysql
|
example: r#"open db.mysql
|
||||||
| db select a
|
| into db
|
||||||
| db from table_1
|
| select a
|
||||||
| db where ((db col a) > 1)
|
| from table_1
|
||||||
| db and ((db col b) == 1)
|
| where ((field a) > 1)
|
||||||
| db describe"#,
|
| and ((field b) == 1)
|
||||||
result: None,
|
| describe"#,
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["connection".into(), "query".into()],
|
||||||
|
vals: vec![
|
||||||
|
Value::String {
|
||||||
|
val: "db.mysql".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 {
|
Example {
|
||||||
description: "Creates a nested where clause",
|
description: "Creates a AND clause combined with an expression AND",
|
||||||
example: r#"db open db.mysql
|
example: r#"open db.mysql
|
||||||
| db select a
|
| into db
|
||||||
| db from table_1
|
| select a
|
||||||
| db where ((db col a) > 1 | db and ((db col a) < 10))
|
| from table_1
|
||||||
| db describe"#,
|
| where ((field a) > 1 | and ((field a) < 10))
|
||||||
result: None,
|
| and ((field b) == 1)
|
||||||
|
| describe"#,
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["connection".into(), "query".into()],
|
||||||
|
vals: vec![
|
||||||
|
Value::String {
|
||||||
|
val: "db.mysql".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(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -66,51 +103,32 @@ impl Command for AndDb {
|
||||||
let value: Value = call.req(engine_state, stack, 0)?;
|
let value: Value = call.req(engine_state, stack, 0)?;
|
||||||
let expr = ExprDb::try_from_value(&value)?.into_native();
|
let expr = ExprDb::try_from_value(&value)?.into_native();
|
||||||
|
|
||||||
let value = input.into_value(call.head);
|
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||||
if let Ok(expression) = ExprDb::try_from_value(&value) {
|
match db.statement.as_mut() {
|
||||||
let expression = Expr::BinaryOp {
|
Some(statement) => match statement {
|
||||||
left: Box::new(expression.into_native()),
|
Statement::Query(query) => modify_query(query, expr, call.head)?,
|
||||||
op: BinaryOperator::And,
|
s => {
|
||||||
right: Box::new(expr),
|
|
||||||
};
|
|
||||||
|
|
||||||
let expression: ExprDb = Expr::Nested(Box::new(expression)).into();
|
|
||||||
|
|
||||||
Ok(expression.into_value(call.head).into_pipeline_data())
|
|
||||||
} else if let Ok(mut db) = SQLiteDatabase::try_from_value(value.clone()) {
|
|
||||||
match db.statement.as_mut() {
|
|
||||||
Some(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(
|
return Err(ShellError::GenericError(
|
||||||
"Connection without statement".into(),
|
"Connection doesn't define a query".into(),
|
||||||
"The connection needs a statement defined".into(),
|
format!("Expected a connection with query. Got {}", s),
|
||||||
Some(call.head),
|
Some(call.head),
|
||||||
None,
|
None,
|
||||||
Vec::new(),
|
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())
|
Ok(db.into_value(call.head).into_pipeline_data())
|
||||||
} else {
|
|
||||||
Err(ShellError::CantConvert(
|
|
||||||
"expression or query".into(),
|
|
||||||
value.get_type().to_string(),
|
|
||||||
value.span()?,
|
|
||||||
None,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,3 +168,23 @@ fn modify_select(select: &mut Box<Select>, expression: Expr, span: Span) -> Resu
|
||||||
select.as_mut().selection = Some(new_expression);
|
select.as_mut().selection = Some(new_expression);
|
||||||
Ok(())
|
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,51 +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, SyntaxShape, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ColExpr;
|
|
||||||
|
|
||||||
impl Command for ColExpr {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"db col"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build(self.name())
|
|
||||||
.required("name", SyntaxShape::String, "column name")
|
|
||||||
.category(Category::Custom("database".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Creates column expression for database"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![Example {
|
|
||||||
description: "Creates a named column expression",
|
|
||||||
example: "db col name_1",
|
|
||||||
result: None,
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["database", "column", "expression"]
|
|
||||||
}
|
|
||||||
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::super::SQLiteDatabase;
|
use super::super::SQLiteDatabase;
|
||||||
|
@ -11,7 +11,7 @@ pub struct CollectDb;
|
||||||
|
|
||||||
impl Command for CollectDb {
|
impl Command for CollectDb {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"db collect"
|
"collect"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
|
@ -19,13 +19,21 @@ impl Command for CollectDb {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Query a database using SQL."
|
"Collects a query from a database database connection"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Any
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Collect from a select query",
|
description: "Collect from a select query",
|
||||||
example: "open foo.db | db select a | db from table_1 | db collect",
|
example: "open foo.db | into db | select a | from table_1 | collect",
|
||||||
result: None,
|
result: None,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
use nu_engine::get_full_help;
|
|
||||||
use nu_protocol::{
|
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, IntoPipelineData, PipelineData, ShellError, Signature, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Database;
|
|
||||||
|
|
||||||
impl Command for Database {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"db"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Database commands"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build(self.name()).category(Category::Custom("database".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
_input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
Ok(Value::String {
|
|
||||||
val: get_full_help(
|
|
||||||
&Database.signature(),
|
|
||||||
&Database.examples(),
|
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
),
|
|
||||||
span: call.head,
|
|
||||||
}
|
|
||||||
.into_pipeline_data())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::super::SQLiteDatabase;
|
use super::super::SQLiteDatabase;
|
||||||
|
@ -11,7 +11,7 @@ pub struct DescribeDb;
|
||||||
|
|
||||||
impl Command for DescribeDb {
|
impl Command for DescribeDb {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"db describe"
|
"describe"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
|
@ -22,16 +22,37 @@ impl Command for DescribeDb {
|
||||||
"Describes connection and query of the DB object"
|
"Describes connection and query of the DB object"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn input_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Any
|
||||||
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Describe SQLite database constructed query",
|
description: "Describe SQLite database constructed query",
|
||||||
example: "db open foo.db | db select table_1 | db describe",
|
example: "open foo.db | into db | select col_1 | from table_1 | describe",
|
||||||
result: None,
|
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> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["database", "SQLite"]
|
vec!["database", "SQLite", "describe"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
|
@ -45,3 +66,19 @@ impl Command for DescribeDb {
|
||||||
Ok(db.describe(call.head).into_pipeline_data())
|
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 {}),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,8 @@ use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||||
|
Type, Value,
|
||||||
};
|
};
|
||||||
use sqlparser::ast::{Ident, Query, Select, SetExpr, Statement, TableAlias, TableWithJoins};
|
use sqlparser::ast::{Ident, Query, Select, SetExpr, Statement, TableAlias, TableWithJoins};
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ pub struct FromDb;
|
||||||
|
|
||||||
impl Command for FromDb {
|
impl Command for FromDb {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"db from"
|
"from"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
|
@ -41,11 +42,32 @@ impl Command for FromDb {
|
||||||
vec!["database", "from"]
|
vec!["database", "from"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn input_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Selects table from database",
|
description: "Selects a table from database",
|
||||||
example: "db open db.mysql | db from table_a",
|
example: "open db.mysql | into db | from table_a | describe",
|
||||||
result: None,
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["connection".into(), "query".into()],
|
||||||
|
vals: vec![
|
||||||
|
Value::String {
|
||||||
|
val: "db.mysql".into(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
Value::String {
|
||||||
|
val: "SELECT FROM table_a".into(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,3 +202,14 @@ fn create_table(
|
||||||
|
|
||||||
Ok(table)
|
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,85 +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, SyntaxShape, Value,
|
|
||||||
};
|
|
||||||
use sqlparser::ast::{Expr, Function, FunctionArg, FunctionArgExpr, Ident, ObjectName};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct FunctionExpr;
|
|
||||||
|
|
||||||
impl Command for FunctionExpr {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"db 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")
|
|
||||||
.category(Category::Custom("database".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: "db fn count name_1",
|
|
||||||
result: None,
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
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"),
|
|
||||||
})
|
|
||||||
.into();
|
|
||||||
|
|
||||||
Ok(expression.into_value(call.head).into_pipeline_data())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,7 +5,8 @@ use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||||
|
Type, Value,
|
||||||
};
|
};
|
||||||
use sqlparser::ast::{SetExpr, Statement};
|
use sqlparser::ast::{SetExpr, Statement};
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ pub struct GroupByDb;
|
||||||
|
|
||||||
impl Command for GroupByDb {
|
impl Command for GroupByDb {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"db group-by"
|
"group-by"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
|
@ -35,16 +36,63 @@ impl Command for GroupByDb {
|
||||||
vec!["database", "select"]
|
vec!["database", "select"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn input_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
description: "orders query by a column",
|
Example {
|
||||||
example: r#"db open db.mysql
|
description: "groups by column a and calculates the max",
|
||||||
| db from table_a
|
example: r#"open db.mysql
|
||||||
| db select a
|
| into db
|
||||||
| db group-by a
|
| from table_a
|
||||||
| db describe"#,
|
| select (fn max a)
|
||||||
result: None,
|
| group-by a
|
||||||
}]
|
| describe"#,
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["connection".into(), "query".into()],
|
||||||
|
vals: vec![
|
||||||
|
Value::String {
|
||||||
|
val: "db.mysql".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.mysql
|
||||||
|
| into db
|
||||||
|
| from 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.mysql".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(
|
fn run(
|
||||||
|
@ -100,3 +148,24 @@ impl Command for GroupByDb {
|
||||||
Ok(db.into_value(call.head).into_pipeline_data())
|
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 {}),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,8 @@ use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||||
|
Type, Value,
|
||||||
};
|
};
|
||||||
use sqlparser::ast::{
|
use sqlparser::ast::{
|
||||||
Ident, Join, JoinConstraint, JoinOperator, Select, SetExpr, Statement, TableAlias,
|
Ident, Join, JoinConstraint, JoinOperator, Select, SetExpr, Statement, TableAlias,
|
||||||
|
@ -15,7 +16,7 @@ pub struct JoinDb;
|
||||||
|
|
||||||
impl Command for JoinDb {
|
impl Command for JoinDb {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"db join"
|
"join"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
|
@ -47,12 +48,70 @@ impl Command for JoinDb {
|
||||||
vec!["database", "join"]
|
vec!["database", "join"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn input_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
description: "",
|
Example {
|
||||||
example: "",
|
description: "joins two tables on col_b",
|
||||||
result: None,
|
example: r#"open db.mysql
|
||||||
}]
|
| into db
|
||||||
|
| select col_a
|
||||||
|
| from table_1 --as t1
|
||||||
|
| join table_2 col_b --as t2
|
||||||
|
| describe"#,
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["connection".into(), "query".into()],
|
||||||
|
vals: vec![
|
||||||
|
Value::String {
|
||||||
|
val: "db.mysql".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.mysql
|
||||||
|
| into db
|
||||||
|
| select col_a
|
||||||
|
| from table_1 --as t1
|
||||||
|
| join (
|
||||||
|
open db.mysql
|
||||||
|
| into db
|
||||||
|
| select col_c
|
||||||
|
| from table_2
|
||||||
|
) ((field t1.col_a) == (field t2.col_c)) --as t2 --right
|
||||||
|
| describe"#,
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["connection".into(), "query".into()],
|
||||||
|
vals: vec![
|
||||||
|
Value::String {
|
||||||
|
val: "db.mysql".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(
|
fn run(
|
||||||
|
@ -176,3 +235,23 @@ fn modify_from(
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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 {}),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,8 @@ use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||||
|
Type, Value,
|
||||||
};
|
};
|
||||||
use sqlparser::ast::Statement;
|
use sqlparser::ast::Statement;
|
||||||
|
|
||||||
|
@ -13,7 +14,7 @@ pub struct LimitDb;
|
||||||
|
|
||||||
impl Command for LimitDb {
|
impl Command for LimitDb {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"db limit"
|
"limit"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
|
@ -37,15 +38,37 @@ impl Command for LimitDb {
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Limits selection from table",
|
description: "Limits selection from table",
|
||||||
example: r#"db open db.mysql
|
example: r#"open db.mysql
|
||||||
| db from table_a
|
| into db
|
||||||
| db select a
|
| from table_a
|
||||||
| db limit 10
|
| select a
|
||||||
| db describe"#,
|
| limit 10
|
||||||
result: None,
|
| describe"#,
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["connection".into(), "query".into()],
|
||||||
|
vals: vec![
|
||||||
|
Value::String {
|
||||||
|
val: "db.mysql".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 input_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
|
@ -84,3 +107,23 @@ impl Command for LimitDb {
|
||||||
Ok(db.into_value(call.head).into_pipeline_data())
|
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 {}),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,22 +3,19 @@ pub mod conversions;
|
||||||
|
|
||||||
mod alias;
|
mod alias;
|
||||||
mod and;
|
mod and;
|
||||||
mod col;
|
|
||||||
mod collect;
|
mod collect;
|
||||||
mod command;
|
|
||||||
mod describe;
|
mod describe;
|
||||||
mod from;
|
mod from;
|
||||||
mod function;
|
|
||||||
mod group_by;
|
mod group_by;
|
||||||
mod join;
|
mod join;
|
||||||
mod limit;
|
mod limit;
|
||||||
mod open;
|
mod open;
|
||||||
mod or;
|
mod or;
|
||||||
mod order_by;
|
mod order_by;
|
||||||
mod over;
|
|
||||||
mod query;
|
mod query;
|
||||||
mod schema;
|
mod schema;
|
||||||
mod select;
|
mod select;
|
||||||
|
mod to_db;
|
||||||
mod where_;
|
mod where_;
|
||||||
|
|
||||||
// Temporal module to create Query objects
|
// Temporal module to create Query objects
|
||||||
|
@ -27,27 +24,24 @@ use testing::TestingDb;
|
||||||
|
|
||||||
use nu_protocol::engine::StateWorkingSet;
|
use nu_protocol::engine::StateWorkingSet;
|
||||||
|
|
||||||
use alias::AliasExpr;
|
use alias::AliasDb;
|
||||||
use and::AndDb;
|
use and::AndDb;
|
||||||
use col::ColExpr;
|
|
||||||
use collect::CollectDb;
|
use collect::CollectDb;
|
||||||
use command::Database;
|
pub(crate) use describe::DescribeDb;
|
||||||
use describe::DescribeDb;
|
pub(crate) use from::FromDb;
|
||||||
use from::FromDb;
|
|
||||||
use function::FunctionExpr;
|
|
||||||
use group_by::GroupByDb;
|
use group_by::GroupByDb;
|
||||||
use join::JoinDb;
|
use join::JoinDb;
|
||||||
use limit::LimitDb;
|
use limit::LimitDb;
|
||||||
use open::OpenDb;
|
use open::OpenDb;
|
||||||
use or::OrDb;
|
use or::OrDb;
|
||||||
use order_by::OrderByDb;
|
use order_by::OrderByDb;
|
||||||
use over::OverExpr;
|
|
||||||
use query::QueryDb;
|
use query::QueryDb;
|
||||||
use schema::SchemaDb;
|
use schema::SchemaDb;
|
||||||
use select::ProjectionDb;
|
pub(crate) use select::ProjectionDb;
|
||||||
|
pub(crate) use to_db::ToDataBase;
|
||||||
use where_::WhereDb;
|
use where_::WhereDb;
|
||||||
|
|
||||||
pub fn add_database_decls(working_set: &mut StateWorkingSet) {
|
pub fn add_commands_decls(working_set: &mut StateWorkingSet) {
|
||||||
macro_rules! bind_command {
|
macro_rules! bind_command {
|
||||||
( $command:expr ) => {
|
( $command:expr ) => {
|
||||||
working_set.add_decl(Box::new($command));
|
working_set.add_decl(Box::new($command));
|
||||||
|
@ -59,21 +53,18 @@ pub fn add_database_decls(working_set: &mut StateWorkingSet) {
|
||||||
|
|
||||||
// Series commands
|
// Series commands
|
||||||
bind_command!(
|
bind_command!(
|
||||||
AliasExpr,
|
ToDataBase,
|
||||||
|
AliasDb,
|
||||||
AndDb,
|
AndDb,
|
||||||
ColExpr,
|
|
||||||
CollectDb,
|
CollectDb,
|
||||||
Database,
|
|
||||||
DescribeDb,
|
DescribeDb,
|
||||||
FromDb,
|
FromDb,
|
||||||
FunctionExpr,
|
|
||||||
GroupByDb,
|
GroupByDb,
|
||||||
JoinDb,
|
JoinDb,
|
||||||
LimitDb,
|
LimitDb,
|
||||||
OpenDb,
|
OpenDb,
|
||||||
OrderByDb,
|
OrderByDb,
|
||||||
OrDb,
|
OrDb,
|
||||||
OverExpr,
|
|
||||||
QueryDb,
|
QueryDb,
|
||||||
ProjectionDb,
|
ProjectionDb,
|
||||||
SchemaDb,
|
SchemaDb,
|
||||||
|
|
|
@ -4,6 +4,7 @@ use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
||||||
|
Type,
|
||||||
};
|
};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
@ -12,7 +13,7 @@ pub struct OpenDb;
|
||||||
|
|
||||||
impl Command for OpenDb {
|
impl Command for OpenDb {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"db open"
|
"open-db"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
|
@ -31,12 +32,20 @@ impl Command for OpenDb {
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Open a sqlite file",
|
description: "Creates a connection to a sqlite database based on the file name",
|
||||||
example: r#"db open file.sqlite"#,
|
example: r#"open-db file.sqlite"#,
|
||||||
result: None,
|
result: None,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn input_type(&self) -> Type {
|
||||||
|
Type::Any
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
|
|
|
@ -6,7 +6,7 @@ use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||||
Value,
|
Type, Value,
|
||||||
};
|
};
|
||||||
use sqlparser::ast::{BinaryOperator, Expr, Query, Select, SetExpr, Statement};
|
use sqlparser::ast::{BinaryOperator, Expr, Query, Select, SetExpr, Statement};
|
||||||
|
|
||||||
|
@ -15,11 +15,11 @@ pub struct OrDb;
|
||||||
|
|
||||||
impl Command for OrDb {
|
impl Command for OrDb {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"db or"
|
"or"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Includes an OR clause for a query or expression"
|
"Includes an OR clause for a query"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
|
@ -32,26 +32,63 @@ impl Command for OrDb {
|
||||||
vec!["database", "where"]
|
vec!["database", "where"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn input_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "selects a column from a database with a where clause",
|
description: "selects a column from a database with an OR clause",
|
||||||
example: r#"db open db.mysql
|
example: r#"open db.mysql
|
||||||
| db select a
|
| into db
|
||||||
| db from table_1
|
| select a
|
||||||
| db where ((db col a) > 1)
|
| from table_1
|
||||||
| db or ((db col b) == 1)
|
| where ((field a) > 1)
|
||||||
| db describe"#,
|
| or ((field b) == 1)
|
||||||
result: None,
|
| describe"#,
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["connection".into(), "query".into()],
|
||||||
|
vals: vec![
|
||||||
|
Value::String {
|
||||||
|
val: "db.mysql".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 {
|
Example {
|
||||||
description: "Creates a nested where clause",
|
description: "Creates an OR clause in the column names and a column",
|
||||||
example: r#"db open db.mysql
|
example: r#"open db.mysql
|
||||||
| db select a
|
| into db
|
||||||
| db from table_1
|
| select a
|
||||||
| db where ((db col a) > 1 | db or ((db col a) < 10))
|
| from table_1
|
||||||
| db describe"#,
|
| where ((field a) > 1 | or ((field a) < 10))
|
||||||
result: None,
|
| or ((field b) == 1)
|
||||||
|
| describe"#,
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["connection".into(), "query".into()],
|
||||||
|
vals: vec![
|
||||||
|
Value::String {
|
||||||
|
val: "db.mysql".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(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -66,51 +103,32 @@ impl Command for OrDb {
|
||||||
let value: Value = call.req(engine_state, stack, 0)?;
|
let value: Value = call.req(engine_state, stack, 0)?;
|
||||||
let expr = ExprDb::try_from_value(&value)?.into_native();
|
let expr = ExprDb::try_from_value(&value)?.into_native();
|
||||||
|
|
||||||
let value = input.into_value(call.head);
|
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||||
if let Ok(expression) = ExprDb::try_from_value(&value) {
|
match db.statement {
|
||||||
let expression = Expr::BinaryOp {
|
Some(ref mut statement) => match statement {
|
||||||
left: Box::new(expression.into_native()),
|
Statement::Query(query) => modify_query(query, expr, call.head)?,
|
||||||
op: BinaryOperator::Or,
|
s => {
|
||||||
right: Box::new(expr),
|
|
||||||
};
|
|
||||||
|
|
||||||
let expression: ExprDb = Expr::Nested(Box::new(expression)).into();
|
|
||||||
|
|
||||||
Ok(expression.into_value(call.head).into_pipeline_data())
|
|
||||||
} else if let Ok(mut db) = SQLiteDatabase::try_from_value(value.clone()) {
|
|
||||||
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(
|
return Err(ShellError::GenericError(
|
||||||
"Connection without statement".into(),
|
"Connection doesnt define a query".into(),
|
||||||
"The connection needs a statement defined".into(),
|
format!("Expected a connection with query. Got {}", s),
|
||||||
Some(call.head),
|
Some(call.head),
|
||||||
None,
|
None,
|
||||||
Vec::new(),
|
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())
|
Ok(db.into_value(call.head).into_pipeline_data())
|
||||||
} else {
|
|
||||||
Err(ShellError::CantConvert(
|
|
||||||
"expression or query".into(),
|
|
||||||
value.get_type().to_string(),
|
|
||||||
value.span()?,
|
|
||||||
None,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,3 +168,23 @@ fn modify_select(select: &mut Box<Select>, expression: Expr, span: Span) -> Resu
|
||||||
select.as_mut().selection = Some(new_expression);
|
select.as_mut().selection = Some(new_expression);
|
||||||
Ok(())
|
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 {}),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,8 @@ use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||||
|
Type, Value,
|
||||||
};
|
};
|
||||||
use sqlparser::ast::{Expr, OrderByExpr, Statement};
|
use sqlparser::ast::{Expr, OrderByExpr, Statement};
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ pub struct OrderByDb;
|
||||||
|
|
||||||
impl Command for OrderByDb {
|
impl Command for OrderByDb {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"db order-by"
|
"order-by"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
|
@ -34,19 +35,67 @@ impl Command for OrderByDb {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["database", "select"]
|
vec!["database", "order-by"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
description: "orders query by a column",
|
Example {
|
||||||
example: r#"db open db.mysql
|
description: "orders query by a column",
|
||||||
| db from table_a
|
example: r#"open db.mysql
|
||||||
| db select a
|
| into db
|
||||||
| db order-by a
|
| from table_a
|
||||||
| db describe"#,
|
| select a
|
||||||
result: None,
|
| order-by a
|
||||||
}]
|
| describe"#,
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["connection".into(), "query".into()],
|
||||||
|
vals: vec![
|
||||||
|
Value::String {
|
||||||
|
val: "db.mysql".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.mysql
|
||||||
|
| into db
|
||||||
|
| from 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.mysql".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(
|
fn run(
|
||||||
|
@ -155,3 +204,23 @@ fn update_connection(
|
||||||
|
|
||||||
Ok(db.into_value(call.head).into_pipeline_data())
|
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 {}),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,80 +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, SyntaxShape, Value,
|
|
||||||
};
|
|
||||||
use sqlparser::ast::{Expr, WindowSpec};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct OverExpr;
|
|
||||||
|
|
||||||
impl Command for OverExpr {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"db over"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build(self.name())
|
|
||||||
.rest(
|
|
||||||
"partition-by",
|
|
||||||
SyntaxShape::Any,
|
|
||||||
"columns to partition the window function",
|
|
||||||
)
|
|
||||||
.category(Category::Custom("database".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 expresssion",
|
|
||||||
example: "db function avg col_a | db over col_b",
|
|
||||||
result: None,
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["database", "column", "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())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,6 +3,7 @@ use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
||||||
|
Type,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::super::SQLiteDatabase;
|
use super::super::SQLiteDatabase;
|
||||||
|
@ -12,7 +13,7 @@ pub struct QueryDb;
|
||||||
|
|
||||||
impl Command for QueryDb {
|
impl Command for QueryDb {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"db query"
|
"query"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
|
@ -29,10 +30,18 @@ impl Command for QueryDb {
|
||||||
"Query a database using SQL."
|
"Query a database using SQL."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn input_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Any
|
||||||
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Get 1 table out of a SQLite database",
|
description: "Execute a query statement using the database connection",
|
||||||
example: r#"db open foo.db | db query "SELECT * FROM Bar""#,
|
example: r#"open foo.db | into db | query "SELECT * FROM Bar""#,
|
||||||
result: None,
|
result: None,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::database::values::definitions::{db::Db, db_row::DbRow, db_table::DbTa
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Value,
|
Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
||||||
};
|
};
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -11,7 +11,7 @@ pub struct SchemaDb;
|
||||||
|
|
||||||
impl Command for SchemaDb {
|
impl Command for SchemaDb {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"db schema"
|
"schema"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
|
@ -19,13 +19,21 @@ impl Command for SchemaDb {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Show database information, including its schema."
|
"Show sqlite database information, including its schema."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Any
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Show the schema of a SQLite database",
|
description: "Show the schema of a SQLite database",
|
||||||
example: r#"open foo.db | db schema"#,
|
example: r#"open foo.db | into db | schema"#,
|
||||||
result: None,
|
result: None,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||||
Value,
|
Type, Value,
|
||||||
};
|
};
|
||||||
use sqlparser::ast::{Query, Select, SelectItem, SetExpr, Statement};
|
use sqlparser::ast::{Query, Select, SelectItem, SetExpr, Statement};
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ pub struct ProjectionDb;
|
||||||
|
|
||||||
impl Command for ProjectionDb {
|
impl Command for ProjectionDb {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"db select"
|
"select"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
|
@ -34,17 +34,55 @@ impl Command for ProjectionDb {
|
||||||
vec!["database", "select"]
|
vec!["database", "select"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn input_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "selects a column from a database",
|
description: "selects a column from a database",
|
||||||
example: "db open db.mysql | db select a | db describe",
|
example: "open db.mysql | into db | select a | describe",
|
||||||
result: None,
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["connection".into(), "query".into()],
|
||||||
|
vals: vec![
|
||||||
|
Value::String {
|
||||||
|
val: "db.mysql".into(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
Value::String {
|
||||||
|
val: "SELECT a".into(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "selects columns from a database",
|
description: "selects columns from a database using alias",
|
||||||
example: "db open db.mysql | db select a b c | db describe",
|
example: r#"open db.mysql
|
||||||
result: None,
|
| into db
|
||||||
|
| select (field a | as new_a) b c
|
||||||
|
| from table_1
|
||||||
|
| describe"#,
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["connection".into(), "query".into()],
|
||||||
|
vals: vec![
|
||||||
|
Value::String {
|
||||||
|
val: "db.mysql".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(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -104,7 +142,7 @@ fn modify_statement(
|
||||||
Ok(statement)
|
Ok(statement)
|
||||||
}
|
}
|
||||||
s => Err(ShellError::GenericError(
|
s => Err(ShellError::GenericError(
|
||||||
"Connection doesnt define a statement".into(),
|
"Connection doesn't define a statement".into(),
|
||||||
format!("Expected a connection with query. Got {}", s),
|
format!("Expected a connection with query. Got {}", s),
|
||||||
Some(span),
|
Some(span),
|
||||||
None,
|
None,
|
||||||
|
@ -129,3 +167,21 @@ fn create_select(projection: Vec<SelectItem>) -> Select {
|
||||||
having: None,
|
having: 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 {}),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ pub struct TestingDb;
|
||||||
|
|
||||||
impl Command for TestingDb {
|
impl Command for TestingDb {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"db testing"
|
"testing-db"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
|
@ -27,7 +27,7 @@ impl Command for TestingDb {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Create query object"
|
"Temporal Command: Create query object"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
|
55
crates/nu-command/src/database/commands/to_db.rs
Normal file
55
crates/nu-command/src/database/commands/to_db.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
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 into an open db connection"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name()).category(Category::Custom("database".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["database", "into", "db"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Converts an open file into a db object",
|
||||||
|
example: "open db.mysql | into db",
|
||||||
|
result: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_type(&self) -> Type {
|
||||||
|
Type::Any
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,8 @@ use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||||
|
Type, Value,
|
||||||
};
|
};
|
||||||
use sqlparser::ast::{Expr, Query, Select, SetExpr, Statement};
|
use sqlparser::ast::{Expr, Query, Select, SetExpr, Statement};
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ pub struct WhereDb;
|
||||||
|
|
||||||
impl Command for WhereDb {
|
impl Command for WhereDb {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"db where"
|
"where"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
|
@ -31,15 +32,37 @@ impl Command for WhereDb {
|
||||||
vec!["database", "where"]
|
vec!["database", "where"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn input_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "selects a column from a database with a where clause",
|
description: "selects a column from a database with a where clause",
|
||||||
example: r#"db open db.mysql
|
example: r#"open db.mysql
|
||||||
| db select a
|
| into db
|
||||||
| db from table_1
|
| select a
|
||||||
| db where ((db col a) > 1)
|
| from table_1
|
||||||
| db describe"#,
|
| where ((field a) > 1)
|
||||||
result: None,
|
| describe"#,
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["connection".into(), "query".into()],
|
||||||
|
vals: vec![
|
||||||
|
Value::String {
|
||||||
|
val: "db.mysql".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(),
|
||||||
|
}),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,3 +134,23 @@ fn create_select(expression: Expr) -> Select {
|
||||||
having: None,
|
having: 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 {}),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
138
crates/nu-command/src/database/expressions/alias.rs
Normal file
138
crates/nu-command/src/database/expressions/alias.rs
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
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")
|
||||||
|
.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 input_type(&self) -> Type {
|
||||||
|
Type::Custom("db-expression".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Custom("db-expression".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {})])
|
||||||
|
}
|
||||||
|
}
|
147
crates/nu-command/src/database/expressions/and.rs
Normal file
147
crates/nu-command/src/database/expressions/and.rs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
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")
|
||||||
|
.category(Category::Custom("db-expression".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["database", "and", "expression"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_type(&self) -> Type {
|
||||||
|
Type::Custom("db-expression".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Custom("db-expression".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {})])
|
||||||
|
}
|
||||||
|
}
|
81
crates/nu-command/src/database/expressions/as_nu.rs
Normal file
81
crates/nu-command/src/database/expressions/as_nu.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
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()).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 input_type(&self) -> Type {
|
||||||
|
Type::Custom("db-expression".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Any
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {})])
|
||||||
|
}
|
||||||
|
}
|
84
crates/nu-command/src/database/expressions/field.rs
Normal file
84
crates/nu-command/src/database/expressions/field.rs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
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")
|
||||||
|
.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 input_type(&self) -> Type {
|
||||||
|
Type::Any
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Custom("db-expression".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {})])
|
||||||
|
}
|
||||||
|
}
|
163
crates/nu-command/src/database/expressions/function.rs
Normal file
163
crates/nu-command/src/database/expressions/function.rs
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
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")
|
||||||
|
.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.mysql
|
||||||
|
| into db
|
||||||
|
| select (fn lead col_a)
|
||||||
|
| from table_a
|
||||||
|
| describe"#,
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["connection".into(), "query".into()],
|
||||||
|
vals: vec![
|
||||||
|
Value::String {
|
||||||
|
val: "db.mysql".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 input_type(&self) -> Type {
|
||||||
|
Type::Any
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Custom("db-expression".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
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"),
|
||||||
|
})
|
||||||
|
.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 {}),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
40
crates/nu-command/src/database/expressions/mod.rs
Normal file
40
crates/nu-command/src/database/expressions/mod.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// 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
|
||||||
|
);
|
||||||
|
}
|
147
crates/nu-command/src/database/expressions/or.rs
Normal file
147
crates/nu-command/src/database/expressions/or.rs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
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")
|
||||||
|
.category(Category::Custom("db-expression".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["database", "or", "expression"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_type(&self) -> Type {
|
||||||
|
Type::Custom("db-expression".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Custom("db-expression".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {})])
|
||||||
|
}
|
||||||
|
}
|
159
crates/nu-command/src/database/expressions/over.rs
Normal file
159
crates/nu-command/src/database/expressions/over.rs
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
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",
|
||||||
|
)
|
||||||
|
.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.mysql
|
||||||
|
| into db
|
||||||
|
| select (fn lead col_a | over col_b)
|
||||||
|
| from table_a
|
||||||
|
| describe"#,
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["connection".into(), "query".into()],
|
||||||
|
vals: vec![
|
||||||
|
Value::String {
|
||||||
|
val: "db.mysql".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 input_type(&self) -> Type {
|
||||||
|
Type::Custom("db-expression".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Custom("db-expression".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["database", "over", "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,8 +1,21 @@
|
||||||
mod commands;
|
mod commands;
|
||||||
|
mod expressions;
|
||||||
mod values;
|
mod values;
|
||||||
|
|
||||||
pub use commands::add_database_decls;
|
use commands::add_commands_decls;
|
||||||
|
use expressions::add_expressions_decls;
|
||||||
|
|
||||||
pub use values::{
|
pub use values::{
|
||||||
convert_sqlite_row_to_nu_value, convert_sqlite_value_to_nu_value, open_connection_in_memory,
|
convert_sqlite_row_to_nu_value, convert_sqlite_value_to_nu_value, open_connection_in_memory,
|
||||||
SQLiteDatabase,
|
SQLiteDatabase,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
144
crates/nu-command/src/database/test_database.rs
Normal file
144
crates/nu-command/src/database/test_database.rs
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
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",
|
||||||
|
)
|
||||||
|
.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())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_type(&self) -> Type {
|
||||||
|
Type::Any
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_type(&self) -> Type {
|
||||||
|
Type::Custom("database".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
};
|
||||||
|
|
||||||
|
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
||||||
|
let _ = engine_state.merge_delta(delta, None, &cwd);
|
||||||
|
|
||||||
|
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())
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = engine_state.merge_delta(delta, None, &cwd);
|
||||||
|
|
||||||
|
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,12 +1,14 @@
|
||||||
use nu_test_support::{nu, pipeline};
|
use nu_test_support::{nu, pipeline};
|
||||||
|
|
||||||
|
#[cfg(feature = "database")]
|
||||||
#[test]
|
#[test]
|
||||||
fn can_query_single_table() {
|
fn can_query_single_table() {
|
||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: "tests/fixtures/formats", pipeline(
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
r#"
|
r#"
|
||||||
open sample.db
|
open sample.db
|
||||||
| db query "select * from strings"
|
| into db
|
||||||
|
| query "select * from strings"
|
||||||
| where x =~ ell
|
| where x =~ ell
|
||||||
| length
|
| length
|
||||||
"#
|
"#
|
||||||
|
@ -15,25 +17,28 @@ fn can_query_single_table() {
|
||||||
assert_eq!(actual.out, "4");
|
assert_eq!(actual.out, "4");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "database")]
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_sql_fails() {
|
fn invalid_sql_fails() {
|
||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: "tests/fixtures/formats", pipeline(
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
r#"
|
r#"
|
||||||
open sample.db
|
open sample.db
|
||||||
| db query "select *asdfasdf"
|
| into db
|
||||||
|
| query "select *asdfasdf"
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
|
|
||||||
assert!(actual.err.contains("syntax error"));
|
assert!(actual.err.contains("syntax error"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "database")]
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_input_fails() {
|
fn invalid_input_fails() {
|
||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: "tests/fixtures/formats", pipeline(
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
r#"
|
r#"
|
||||||
"foo" | db query "select * from asdf"
|
"foo" | into db | query "select * from asdf"
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue