Db commands without DB (#5838)

* database commands without db

* database command tests
This commit is contained in:
Fernando Herrera 2022-06-21 12:14:29 -05:00 committed by GitHub
parent 848ff8453b
commit 7164929c61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 2007 additions and 529 deletions

View File

@ -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 {}),
])
}
}

View File

@ -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 {}),
])
}
}

View File

@ -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())
}
}

View File

@ -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,
}]
}

View File

@ -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())
}
}

View File

@ -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 {}),
])
}
}

View File

@ -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 {})])
}
}

View File

@ -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())
}
}

View File

@ -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 {}),
])
}
}

View File

@ -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 {}),
])
}
}

View File

@ -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 {}),
])
}
}

View File

@ -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,

View File

@ -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,

View File

@ -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 {}),
])
}
}

View File

@ -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 {}),
])
}
}

View File

@ -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())
}
}

View File

@ -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,
}]
}

View File

@ -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,
}]
}

View File

@ -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 {}),
])
}
}

View File

@ -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> {

View 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())
}
}

View File

@ -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 {}),
])
}
}

View 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 {})])
}
}

View 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 {})])
}
}

View 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 {})])
}
}

View 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 {})])
}
}

View 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 {}),
])
}
}

View 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
);
}

View 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 {})])
}
}

View 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 {}),
])
}
}

View File

@ -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;

View 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
)
}
}
}
}
}
}

View File

@ -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"
"#
));