mirror of
https://github.com/nushell/nushell
synced 2024-12-27 13:33:16 +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::{
|
||||
database::values::dsl::{ExprDb, SelectDb},
|
||||
SQLiteDatabase,
|
||||
};
|
||||
use crate::SQLiteDatabase;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
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)]
|
||||
pub struct AliasExpr;
|
||||
pub struct AliasDb;
|
||||
|
||||
impl Command for AliasExpr {
|
||||
impl Command for AliasDb {
|
||||
fn name(&self) -> &str {
|
||||
"db as"
|
||||
"as"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
|
@ -29,11 +27,67 @@ impl Command for AliasExpr {
|
|||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Creates an alias for a column selection",
|
||||
example: "db col name_a | db as new_a",
|
||||
result: None,
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "Creates an alias for a selected table",
|
||||
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> {
|
||||
|
@ -48,52 +102,12 @@ impl Command for AliasExpr {
|
|||
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 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,
|
||||
))
|
||||
}
|
||||
let db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||
alias_db(db, 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())
|
||||
}
|
||||
|
||||
fn alias_db(
|
||||
mut db: SQLiteDatabase,
|
||||
new_alias: String,
|
||||
|
@ -142,7 +156,7 @@ fn alias_db(
|
|||
},
|
||||
s => {
|
||||
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),
|
||||
Some(call.head),
|
||||
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,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Value,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{BinaryOperator, Expr, Query, Select, SetExpr, Statement};
|
||||
|
||||
|
@ -15,11 +15,11 @@ pub struct AndDb;
|
|||
|
||||
impl Command for AndDb {
|
||||
fn name(&self) -> &str {
|
||||
"db and"
|
||||
"and"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Includes an AND clause for a query or expression"
|
||||
"Includes an AND clause for a query"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
|
@ -32,26 +32,63 @@ impl Command for AndDb {
|
|||
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> {
|
||||
vec![
|
||||
Example {
|
||||
description: "selects a column from a database with a where clause",
|
||||
example: r#"db open db.mysql
|
||||
| db select a
|
||||
| db from table_1
|
||||
| db where ((db col a) > 1)
|
||||
| db and ((db col b) == 1)
|
||||
| db describe"#,
|
||||
result: None,
|
||||
description: "Selects a column from a database with an AND clause",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| select a
|
||||
| from table_1
|
||||
| where ((field a) > 1)
|
||||
| and ((field b) == 1)
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.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 {
|
||||
description: "Creates a nested where clause",
|
||||
example: r#"db open db.mysql
|
||||
| db select a
|
||||
| db from table_1
|
||||
| db where ((db col a) > 1 | db and ((db col a) < 10))
|
||||
| db describe"#,
|
||||
result: None,
|
||||
description: "Creates a AND clause combined with an expression AND",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| select a
|
||||
| from table_1
|
||||
| where ((field a) > 1 | and ((field a) < 10))
|
||||
| and ((field b) == 1)
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.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 expr = ExprDb::try_from_value(&value)?.into_native();
|
||||
|
||||
let value = input.into_value(call.head);
|
||||
if let Ok(expression) = ExprDb::try_from_value(&value) {
|
||||
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())
|
||||
} 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 => {
|
||||
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||
match db.statement.as_mut() {
|
||||
Some(statement) => match statement {
|
||||
Statement::Query(query) => modify_query(query, expr, call.head)?,
|
||||
s => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Connection without statement".into(),
|
||||
"The connection needs a statement defined".into(),
|
||||
"Connection doesn't define a query".into(),
|
||||
format!("Expected a connection with query. Got {}", s),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
};
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Connection without statement".into(),
|
||||
"The connection needs a statement defined".into(),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
} else {
|
||||
Err(ShellError::CantConvert(
|
||||
"expression or query".into(),
|
||||
value.get_type().to_string(),
|
||||
value.span()?,
|
||||
None,
|
||||
))
|
||||
}
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,3 +168,23 @@ fn modify_select(select: &mut Box<Select>, expression: Expr, span: Span) -> Resu
|
|||
select.as_mut().selection = Some(new_expression);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::expressions::{AndExpr, FieldExpr};
|
||||
use super::super::{FromDb, ProjectionDb, WhereDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(AndDb {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
Box::new(WhereDb {}),
|
||||
Box::new(FieldExpr {}),
|
||||
Box::new(AndExpr {}),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,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::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature,
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type,
|
||||
};
|
||||
|
||||
use super::super::SQLiteDatabase;
|
||||
|
@ -11,7 +11,7 @@ pub struct CollectDb;
|
|||
|
||||
impl Command for CollectDb {
|
||||
fn name(&self) -> &str {
|
||||
"db collect"
|
||||
"collect"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
|
@ -19,13 +19,21 @@ impl Command for CollectDb {
|
|||
}
|
||||
|
||||
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> {
|
||||
vec![Example {
|
||||
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,
|
||||
}]
|
||||
}
|
||||
|
|
|
@ -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::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature,
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value,
|
||||
};
|
||||
|
||||
use super::super::SQLiteDatabase;
|
||||
|
@ -11,7 +11,7 @@ pub struct DescribeDb;
|
|||
|
||||
impl Command for DescribeDb {
|
||||
fn name(&self) -> &str {
|
||||
"db describe"
|
||||
"describe"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
|
@ -22,16 +22,37 @@ impl Command for DescribeDb {
|
|||
"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> {
|
||||
vec![Example {
|
||||
description: "Describe SQLite database constructed query",
|
||||
example: "db open foo.db | db select table_1 | db describe",
|
||||
result: None,
|
||||
example: "open foo.db | into db | select col_1 | from table_1 | describe",
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "foo.db".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT col_1 FROM table_1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "SQLite"]
|
||||
vec!["database", "SQLite", "describe"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -45,3 +66,19 @@ impl Command for DescribeDb {
|
|||
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::{
|
||||
ast::Call,
|
||||
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};
|
||||
|
||||
|
@ -14,7 +15,7 @@ pub struct FromDb;
|
|||
|
||||
impl Command for FromDb {
|
||||
fn name(&self) -> &str {
|
||||
"db from"
|
||||
"from"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
|
@ -41,11 +42,32 @@ impl Command for FromDb {
|
|||
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> {
|
||||
vec![Example {
|
||||
description: "Selects table from database",
|
||||
example: "db open db.mysql | db from table_a",
|
||||
result: None,
|
||||
description: "Selects a table from database",
|
||||
example: "open db.mysql | into db | 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 FROM table_a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
|
@ -180,3 +202,14 @@ fn create_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::{
|
||||
ast::Call,
|
||||
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};
|
||||
|
||||
|
@ -14,7 +15,7 @@ pub struct GroupByDb;
|
|||
|
||||
impl Command for GroupByDb {
|
||||
fn name(&self) -> &str {
|
||||
"db group-by"
|
||||
"group-by"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
|
@ -35,16 +36,63 @@ impl Command for GroupByDb {
|
|||
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> {
|
||||
vec![Example {
|
||||
description: "orders query by a column",
|
||||
example: r#"db open db.mysql
|
||||
| db from table_a
|
||||
| db select a
|
||||
| db group-by a
|
||||
| db describe"#,
|
||||
result: None,
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "groups by column a and calculates the max",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| from table_a
|
||||
| select (fn max a)
|
||||
| group-by a
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.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(
|
||||
|
@ -100,3 +148,24 @@ impl Command for GroupByDb {
|
|||
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::{
|
||||
ast::Call,
|
||||
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, Join, JoinConstraint, JoinOperator, Select, SetExpr, Statement, TableAlias,
|
||||
|
@ -15,7 +16,7 @@ pub struct JoinDb;
|
|||
|
||||
impl Command for JoinDb {
|
||||
fn name(&self) -> &str {
|
||||
"db join"
|
||||
"join"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
|
@ -47,12 +48,70 @@ impl Command for JoinDb {
|
|||
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> {
|
||||
vec![Example {
|
||||
description: "",
|
||||
example: "",
|
||||
result: None,
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "joins two tables on col_b",
|
||||
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(
|
||||
|
@ -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::{
|
||||
ast::Call,
|
||||
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;
|
||||
|
||||
|
@ -13,7 +14,7 @@ pub struct LimitDb;
|
|||
|
||||
impl Command for LimitDb {
|
||||
fn name(&self) -> &str {
|
||||
"db limit"
|
||||
"limit"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
|
@ -37,15 +38,37 @@ impl Command for LimitDb {
|
|||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Limits selection from table",
|
||||
example: r#"db open db.mysql
|
||||
| db from table_a
|
||||
| db select a
|
||||
| db limit 10
|
||||
| db describe"#,
|
||||
result: None,
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| from table_a
|
||||
| select a
|
||||
| limit 10
|
||||
| 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(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
@ -84,3 +107,23 @@ impl Command for LimitDb {
|
|||
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 and;
|
||||
mod col;
|
||||
mod collect;
|
||||
mod command;
|
||||
mod describe;
|
||||
mod from;
|
||||
mod function;
|
||||
mod group_by;
|
||||
mod join;
|
||||
mod limit;
|
||||
mod open;
|
||||
mod or;
|
||||
mod order_by;
|
||||
mod over;
|
||||
mod query;
|
||||
mod schema;
|
||||
mod select;
|
||||
mod to_db;
|
||||
mod where_;
|
||||
|
||||
// Temporal module to create Query objects
|
||||
|
@ -27,27 +24,24 @@ use testing::TestingDb;
|
|||
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
|
||||
use alias::AliasExpr;
|
||||
use alias::AliasDb;
|
||||
use and::AndDb;
|
||||
use col::ColExpr;
|
||||
use collect::CollectDb;
|
||||
use command::Database;
|
||||
use describe::DescribeDb;
|
||||
use from::FromDb;
|
||||
use function::FunctionExpr;
|
||||
pub(crate) use describe::DescribeDb;
|
||||
pub(crate) use from::FromDb;
|
||||
use group_by::GroupByDb;
|
||||
use join::JoinDb;
|
||||
use limit::LimitDb;
|
||||
use open::OpenDb;
|
||||
use or::OrDb;
|
||||
use order_by::OrderByDb;
|
||||
use over::OverExpr;
|
||||
use query::QueryDb;
|
||||
use schema::SchemaDb;
|
||||
use select::ProjectionDb;
|
||||
pub(crate) use select::ProjectionDb;
|
||||
pub(crate) use to_db::ToDataBase;
|
||||
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 {
|
||||
( $command:expr ) => {
|
||||
working_set.add_decl(Box::new($command));
|
||||
|
@ -59,21 +53,18 @@ pub fn add_database_decls(working_set: &mut StateWorkingSet) {
|
|||
|
||||
// Series commands
|
||||
bind_command!(
|
||||
AliasExpr,
|
||||
ToDataBase,
|
||||
AliasDb,
|
||||
AndDb,
|
||||
ColExpr,
|
||||
CollectDb,
|
||||
Database,
|
||||
DescribeDb,
|
||||
FromDb,
|
||||
FunctionExpr,
|
||||
GroupByDb,
|
||||
JoinDb,
|
||||
LimitDb,
|
||||
OpenDb,
|
||||
OrderByDb,
|
||||
OrDb,
|
||||
OverExpr,
|
||||
QueryDb,
|
||||
ProjectionDb,
|
||||
SchemaDb,
|
||||
|
|
|
@ -4,6 +4,7 @@ use nu_protocol::{
|
|||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
||||
Type,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
@ -12,7 +13,7 @@ pub struct OpenDb;
|
|||
|
||||
impl Command for OpenDb {
|
||||
fn name(&self) -> &str {
|
||||
"db open"
|
||||
"open-db"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
|
@ -31,12 +32,20 @@ impl Command for OpenDb {
|
|||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Open a sqlite file",
|
||||
example: r#"db open file.sqlite"#,
|
||||
description: "Creates a connection to a sqlite database based on the file name",
|
||||
example: r#"open-db file.sqlite"#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn input_type(&self) -> Type {
|
||||
Type::Any
|
||||
}
|
||||
|
||||
fn output_type(&self) -> Type {
|
||||
Type::Custom("database".into())
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
|
|
@ -6,7 +6,7 @@ use nu_protocol::{
|
|||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Value,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{BinaryOperator, Expr, Query, Select, SetExpr, Statement};
|
||||
|
||||
|
@ -15,11 +15,11 @@ pub struct OrDb;
|
|||
|
||||
impl Command for OrDb {
|
||||
fn name(&self) -> &str {
|
||||
"db or"
|
||||
"or"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Includes an OR clause for a query or expression"
|
||||
"Includes an OR clause for a query"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
|
@ -32,26 +32,63 @@ impl Command for OrDb {
|
|||
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> {
|
||||
vec![
|
||||
Example {
|
||||
description: "selects a column from a database with a where clause",
|
||||
example: r#"db open db.mysql
|
||||
| db select a
|
||||
| db from table_1
|
||||
| db where ((db col a) > 1)
|
||||
| db or ((db col b) == 1)
|
||||
| db describe"#,
|
||||
result: None,
|
||||
description: "selects a column from a database with an OR clause",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| select a
|
||||
| from table_1
|
||||
| where ((field a) > 1)
|
||||
| or ((field b) == 1)
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.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 {
|
||||
description: "Creates a nested where clause",
|
||||
example: r#"db open db.mysql
|
||||
| db select a
|
||||
| db from table_1
|
||||
| db where ((db col a) > 1 | db or ((db col a) < 10))
|
||||
| db describe"#,
|
||||
result: None,
|
||||
description: "Creates an OR clause in the column names and a column",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| select a
|
||||
| from table_1
|
||||
| where ((field a) > 1 | or ((field a) < 10))
|
||||
| or ((field b) == 1)
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.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 expr = ExprDb::try_from_value(&value)?.into_native();
|
||||
|
||||
let value = input.into_value(call.head);
|
||||
if let Ok(expression) = ExprDb::try_from_value(&value) {
|
||||
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())
|
||||
} 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 => {
|
||||
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||
match db.statement {
|
||||
Some(ref mut statement) => match statement {
|
||||
Statement::Query(query) => modify_query(query, expr, call.head)?,
|
||||
s => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Connection without statement".into(),
|
||||
"The connection needs a statement defined".into(),
|
||||
"Connection doesnt define a query".into(),
|
||||
format!("Expected a connection with query. Got {}", s),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
};
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Connection without statement".into(),
|
||||
"The connection needs a statement defined".into(),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
} else {
|
||||
Err(ShellError::CantConvert(
|
||||
"expression or query".into(),
|
||||
value.get_type().to_string(),
|
||||
value.span()?,
|
||||
None,
|
||||
))
|
||||
}
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,3 +168,23 @@ fn modify_select(select: &mut Box<Select>, expression: Expr, span: Span) -> Resu
|
|||
select.as_mut().selection = Some(new_expression);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::expressions::{FieldExpr, OrExpr};
|
||||
use super::super::{FromDb, ProjectionDb, WhereDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(OrDb {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
Box::new(WhereDb {}),
|
||||
Box::new(FieldExpr {}),
|
||||
Box::new(OrExpr {}),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@ use nu_engine::CallExt;
|
|||
use nu_protocol::{
|
||||
ast::Call,
|
||||
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};
|
||||
|
||||
|
@ -14,7 +15,7 @@ pub struct OrderByDb;
|
|||
|
||||
impl Command for OrderByDb {
|
||||
fn name(&self) -> &str {
|
||||
"db order-by"
|
||||
"order-by"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
|
@ -34,19 +35,67 @@ impl Command for OrderByDb {
|
|||
}
|
||||
|
||||
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> {
|
||||
vec![Example {
|
||||
description: "orders query by a column",
|
||||
example: r#"db open db.mysql
|
||||
| db from table_a
|
||||
| db select a
|
||||
| db order-by a
|
||||
| db describe"#,
|
||||
result: None,
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "orders query by a column",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| from table_a
|
||||
| select a
|
||||
| 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(
|
||||
|
@ -155,3 +204,23 @@ fn update_connection(
|
|||
|
||||
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,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
||||
Type,
|
||||
};
|
||||
|
||||
use super::super::SQLiteDatabase;
|
||||
|
@ -12,7 +13,7 @@ pub struct QueryDb;
|
|||
|
||||
impl Command for QueryDb {
|
||||
fn name(&self) -> &str {
|
||||
"db query"
|
||||
"query"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
|
@ -29,10 +30,18 @@ impl Command for QueryDb {
|
|||
"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> {
|
||||
vec![Example {
|
||||
description: "Get 1 table out of a SQLite database",
|
||||
example: r#"db open foo.db | db query "SELECT * FROM Bar""#,
|
||||
description: "Execute a query statement using the database connection",
|
||||
example: r#"open foo.db | into db | query "SELECT * FROM Bar""#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::database::values::definitions::{db::Db, db_row::DbRow, db_table::DbTa
|
|||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
||||
};
|
||||
use rusqlite::Connection;
|
||||
#[derive(Clone)]
|
||||
|
@ -11,7 +11,7 @@ pub struct SchemaDb;
|
|||
|
||||
impl Command for SchemaDb {
|
||||
fn name(&self) -> &str {
|
||||
"db schema"
|
||||
"schema"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
|
@ -19,13 +19,21 @@ impl Command for SchemaDb {
|
|||
}
|
||||
|
||||
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> {
|
||||
vec![Example {
|
||||
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,
|
||||
}]
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use nu_protocol::{
|
|||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Value,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{Query, Select, SelectItem, SetExpr, Statement};
|
||||
|
||||
|
@ -13,7 +13,7 @@ pub struct ProjectionDb;
|
|||
|
||||
impl Command for ProjectionDb {
|
||||
fn name(&self) -> &str {
|
||||
"db select"
|
||||
"select"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
|
@ -34,17 +34,55 @@ impl Command for ProjectionDb {
|
|||
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> {
|
||||
vec![
|
||||
Example {
|
||||
description: "selects a column from a database",
|
||||
example: "db open db.mysql | db select a | db describe",
|
||||
result: None,
|
||||
example: "open db.mysql | into db | select 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".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "selects columns from a database",
|
||||
example: "db open db.mysql | db select a b c | db describe",
|
||||
result: None,
|
||||
description: "selects columns from a database using alias",
|
||||
example: r#"open db.mysql
|
||||
| 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)
|
||||
}
|
||||
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),
|
||||
Some(span),
|
||||
None,
|
||||
|
@ -129,3 +167,21 @@ fn create_select(projection: Vec<SelectItem>) -> Select {
|
|||
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 {
|
||||
fn name(&self) -> &str {
|
||||
"db testing"
|
||||
"testing-db"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
|
@ -27,7 +27,7 @@ impl Command for TestingDb {
|
|||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Create query object"
|
||||
"Temporal Command: Create query object"
|
||||
}
|
||||
|
||||
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::{
|
||||
ast::Call,
|
||||
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};
|
||||
|
||||
|
@ -14,7 +15,7 @@ pub struct WhereDb;
|
|||
|
||||
impl Command for WhereDb {
|
||||
fn name(&self) -> &str {
|
||||
"db where"
|
||||
"where"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
|
@ -31,15 +32,37 @@ impl Command for WhereDb {
|
|||
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> {
|
||||
vec![Example {
|
||||
description: "selects a column from a database with a where clause",
|
||||
example: r#"db open db.mysql
|
||||
| db select a
|
||||
| db from table_1
|
||||
| db where ((db col a) > 1)
|
||||
| db describe"#,
|
||||
result: None,
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| select a
|
||||
| from table_1
|
||||
| where ((field a) > 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".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
|
@ -111,3 +134,23 @@ fn create_select(expression: Expr) -> Select {
|
|||
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 expressions;
|
||||
mod values;
|
||||
|
||||
pub use commands::add_database_decls;
|
||||
use commands::add_commands_decls;
|
||||
use expressions::add_expressions_decls;
|
||||
|
||||
pub use values::{
|
||||
convert_sqlite_row_to_nu_value, convert_sqlite_value_to_nu_value, open_connection_in_memory,
|
||||
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};
|
||||
|
||||
#[cfg(feature = "database")]
|
||||
#[test]
|
||||
fn can_query_single_table() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
open sample.db
|
||||
| db query "select * from strings"
|
||||
| into db
|
||||
| query "select * from strings"
|
||||
| where x =~ ell
|
||||
| length
|
||||
"#
|
||||
|
@ -15,25 +17,28 @@ fn can_query_single_table() {
|
|||
assert_eq!(actual.out, "4");
|
||||
}
|
||||
|
||||
#[cfg(feature = "database")]
|
||||
#[test]
|
||||
fn invalid_sql_fails() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
open sample.db
|
||||
| db query "select *asdfasdf"
|
||||
| into db
|
||||
| query "select *asdfasdf"
|
||||
"#
|
||||
));
|
||||
|
||||
assert!(actual.err.contains("syntax error"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "database")]
|
||||
#[test]
|
||||
fn invalid_input_fails() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
"foo" | db query "select * from asdf"
|
||||
"foo" | into db | query "select * from asdf"
|
||||
"#
|
||||
));
|
||||
|
||||
|
|
Loading…
Reference in a new issue