diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index cf0edbf35c..a9a9793a27 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -146,8 +146,8 @@ features = [ trash-support = ["trash"] which-support = ["which"] plugin = ["nu-parser/plugin"] -dataframe = ["polars", "num"] -database = ["sqlparser", "rusqlite"] +dataframe = ["polars", "num", "sqlparser"] +database = ["rusqlite"] # TODO: given that rusqlite is included in reedline, should we just always include it? [build-dependencies] shadow-rs = { version = "0.16.1", default-features = false } diff --git a/crates/nu-command/src/database/commands/and.rs b/crates/nu-command/src/database/commands/and.rs deleted file mode 100644 index ba67171112..0000000000 --- a/crates/nu-command/src/database/commands/and.rs +++ /dev/null @@ -1,182 +0,0 @@ -use crate::database::values::dsl::ExprDb; - -use super::super::SQLiteDatabase; -use nu_engine::CallExt; -use nu_protocol::{ - ast::Call, - engine::{Command, EngineState, Stack}, - Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, - Type, Value, -}; -use sqlparser::ast::{BinaryOperator, Expr, Query, Select, SetExpr, Statement}; - -#[derive(Clone)] -pub struct AndDb; - -impl Command for AndDb { - fn name(&self) -> &str { - "and" - } - - fn usage(&self) -> &str { - "Includes an AND clause for a query" - } - - fn signature(&self) -> Signature { - Signature::build(self.name()) - .required("where", SyntaxShape::Any, "Where expression on the table") - .input_type(Type::Custom("database".into())) - .output_type(Type::Custom("database".into())) - .category(Category::Custom("database".into())) - } - - fn search_terms(&self) -> Vec<&str> { - vec!["database", "where"] - } - - fn examples(&self) -> Vec { - vec![ - Example { - description: "Selects a column from a database with an AND clause", - example: r#"open db.sqlite - | from table table_1 - | select a - | where ((field a) > 1) - | and ((field b) == 1) - | describe"#, - result: Some(Value::Record { - cols: vec!["connection".into(), "query".into()], - vals: vec![ - Value::String { - val: "db.sqlite".into(), - span: Span::test_data(), - }, - Value::String { - val: "SELECT a FROM table_1 WHERE a > 1 AND b = 1".into(), - span: Span::test_data(), - }, - ], - span: Span::test_data(), - }), - }, - Example { - description: "Creates a AND clause combined with an expression AND", - example: r#"open db.sqlite - | from table table_1 - | select a - | where ((field a) > 1 | and ((field a) < 10)) - | and ((field b) == 1) - | describe"#, - result: Some(Value::Record { - cols: vec!["connection".into(), "query".into()], - vals: vec![ - Value::String { - val: "db.sqlite".into(), - span: Span::test_data(), - }, - Value::String { - val: "SELECT a FROM table_1 WHERE (a > 1 AND a < 10) AND b = 1".into(), - span: Span::test_data(), - }, - ], - span: Span::test_data(), - }), - }, - ] - } - - fn run( - &self, - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - input: PipelineData, - ) -> Result { - let value: Value = call.req(engine_state, stack, 0)?; - let expr = ExprDb::try_from_value(&value)?.into_native(); - - let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?; - match db.statement.as_mut() { - Some(statement) => match statement { - Statement::Query(query) => modify_query(query, expr, call.head)?, - s => { - return Err(ShellError::GenericError( - "Connection doesn't define a query".into(), - format!("Expected a connection with query. Got {}", s), - Some(call.head), - None, - Vec::new(), - )) - } - }, - None => { - return Err(ShellError::GenericError( - "Connection without statement".into(), - "The connection needs a statement defined".into(), - Some(call.head), - None, - Vec::new(), - )) - } - }; - - Ok(db.into_value(call.head).into_pipeline_data()) - } -} - -fn modify_query(query: &mut Box, expression: Expr, span: Span) -> Result<(), ShellError> { - match *query.body { - SetExpr::Select(ref mut select) => modify_select(select, expression, span)?, - _ => { - return Err(ShellError::GenericError( - "Query without a select".into(), - "Missing a WHERE clause before an AND clause".into(), - Some(span), - None, - Vec::new(), - )) - } - }; - - Ok(()) -} - -fn modify_select(select: &mut Box, expression: Expr, span: Span) -> Result<(), ShellError> { - let new_expression = match &select.selection { - Some(expr) => Ok(Expr::BinaryOp { - left: Box::new(expr.clone()), - op: BinaryOperator::Or, - right: Box::new(expression), - }), - None => Err(ShellError::GenericError( - "Query without a select".into(), - "Missing a WHERE clause before an OR clause".into(), - Some(span), - None, - Vec::new(), - )), - }?; - - select.as_mut().selection = Some(new_expression); - Ok(()) -} - -#[cfg(test)] -mod test { - use super::super::super::expressions::{FieldExpr, OrExpr}; - use super::super::{FromDb, ProjectionDb, WhereDb}; - use super::*; - use crate::database::test_database::test_database; - - #[test] - fn test_examples() { - test_database(vec![ - Box::new(OrDb {}), - Box::new(ProjectionDb {}), - Box::new(FromDb {}), - Box::new(WhereDb {}), - Box::new(FieldExpr {}), - Box::new(OrExpr {}), - ]) - } -} diff --git a/crates/nu-command/src/database/commands/order_by.rs b/crates/nu-command/src/database/commands/order_by.rs deleted file mode 100644 index d7bd084457..0000000000 --- a/crates/nu-command/src/database/commands/order_by.rs +++ /dev/null @@ -1,218 +0,0 @@ -use crate::database::values::dsl::ExprDb; - -use super::super::SQLiteDatabase; -use nu_engine::CallExt; -use nu_protocol::{ - ast::Call, - engine::{Command, EngineState, Stack}, - Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, - Type, Value, -}; -use sqlparser::ast::{Expr, OrderByExpr, Statement}; - -#[derive(Clone)] -pub struct OrderByDb; - -impl Command for OrderByDb { - fn name(&self) -> &str { - "order-by" - } - - fn usage(&self) -> &str { - "Orders by query" - } - - fn signature(&self) -> Signature { - Signature::build(self.name()) - .switch("ascending", "Order by ascending values", Some('a')) - .switch("nulls-first", "Show nulls first in order", Some('n')) - .rest( - "select", - SyntaxShape::Any, - "Select expression(s) on the table", - ) - .input_type(Type::Custom("database".into())) - .output_type(Type::Custom("database".into())) - .category(Category::Custom("database".into())) - } - - fn search_terms(&self) -> Vec<&str> { - vec!["database"] - } - - fn examples(&self) -> Vec { - vec![ - Example { - description: "orders query by a column", - example: r#"open db.sqlite - | from table table_a - | select a - | order-by a - | describe"#, - result: Some(Value::Record { - cols: vec!["connection".into(), "query".into()], - vals: vec![ - Value::String { - val: "db.sqlite".into(), - span: Span::test_data(), - }, - Value::String { - val: "SELECT a FROM table_a ORDER BY a".into(), - span: Span::test_data(), - }, - ], - span: Span::test_data(), - }), - }, - Example { - description: "orders query by column a ascending and by column b", - example: r#"open db.sqlite - | from table table_a - | select a - | order-by a --ascending - | order-by b - | describe"#, - result: Some(Value::Record { - cols: vec!["connection".into(), "query".into()], - vals: vec![ - Value::String { - val: "db.sqlite".into(), - span: Span::test_data(), - }, - Value::String { - val: "SELECT a FROM table_a ORDER BY a ASC, b".into(), - span: Span::test_data(), - }, - ], - span: Span::test_data(), - }), - }, - ] - } - - fn run( - &self, - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - input: PipelineData, - ) -> Result { - let asc = call.has_flag("ascending"); - let nulls_first = call.has_flag("nulls-first"); - let expressions: Vec = call.rest(engine_state, stack, 0)?; - let expressions = Value::List { - vals: expressions, - span: call.head, - }; - let expressions = ExprDb::extract_exprs(expressions)?; - let expressions: Vec = expressions - .into_iter() - .map(|expr| OrderByExpr { - expr, - asc: if asc { Some(asc) } else { None }, - nulls_first: if nulls_first { Some(nulls_first) } else { None }, - }) - .collect(); - - let value = input.into_value(call.head); - - if let Ok(expr) = ExprDb::try_from_value(&value) { - update_expressions(expr, expressions, call) - } else if let Ok(db) = SQLiteDatabase::try_from_value(value.clone()) { - update_connection(db, expressions, call) - } else { - Err(ShellError::CantConvert( - "expression or query".into(), - value.get_type().to_string(), - value.span()?, - None, - )) - } - } -} - -fn update_expressions( - mut expr: ExprDb, - mut expressions: Vec, - call: &Call, -) -> Result { - match expr.as_mut() { - Expr::Function(function) => match &mut function.over { - Some(over) => over.order_by.append(&mut expressions), - None => { - return Err(ShellError::GenericError( - "Expression doesnt define a partition to order".into(), - "Expected an expression with partition".into(), - Some(call.head), - None, - Vec::new(), - )) - } - }, - s => { - return Err(ShellError::GenericError( - "Expression doesnt define a function".into(), - format!("Expected an expression with a function. Got {}", s), - Some(call.head), - None, - Vec::new(), - )) - } - }; - - Ok(expr.into_value(call.head).into_pipeline_data()) -} - -fn update_connection( - mut db: SQLiteDatabase, - mut expressions: Vec, - call: &Call, -) -> Result { - match db.statement.as_mut() { - Some(statement) => match statement { - Statement::Query(query) => { - query.order_by.append(&mut expressions); - } - s => { - return Err(ShellError::GenericError( - "Connection doesnt define a query".into(), - format!("Expected a connection with query. Got {}", s), - Some(call.head), - None, - Vec::new(), - )) - } - }, - None => { - return Err(ShellError::GenericError( - "Connection without statement".into(), - "The connection needs a statement defined".into(), - Some(call.head), - None, - Vec::new(), - )) - } - }; - - Ok(db.into_value(call.head).into_pipeline_data()) -} - -#[cfg(test)] -mod test { - use super::super::super::expressions::{FieldExpr, OrExpr}; - use super::super::{FromDb, ProjectionDb, WhereDb}; - use super::*; - use crate::database::test_database::test_database; - - #[test] - fn test_examples() { - test_database(vec![ - Box::new(OrderByDb {}), - Box::new(ProjectionDb {}), - Box::new(FromDb {}), - Box::new(WhereDb {}), - Box::new(FieldExpr {}), - Box::new(OrExpr {}), - ]) - } -} diff --git a/crates/nu-command/src/database/commands/schema.rs b/crates/nu-command/src/database/commands/schema.rs index 5e6e002379..faacf348e9 100644 --- a/crates/nu-command/src/database/commands/schema.rs +++ b/crates/nu-command/src/database/commands/schema.rs @@ -22,7 +22,7 @@ impl Command for SchemaDb { } fn usage(&self) -> &str { - "Show sqlite database information, including its schema." + "Show SQLite database information, including its schema." } fn examples(&self) -> Vec { @@ -54,7 +54,7 @@ impl Command for SchemaDb { cols.push("db_filename".into()); vals.push(Value::String { - val: sqlite_db.connection.to_string(), + val: sqlite_db.path.to_string_lossy().into(), span, }); diff --git a/crates/nu-command/src/database/commands/select.rs b/crates/nu-command/src/database/commands/select.rs deleted file mode 100644 index b7ad3bfd56..0000000000 --- a/crates/nu-command/src/database/commands/select.rs +++ /dev/null @@ -1,183 +0,0 @@ -use super::{super::values::dsl::SelectDb, super::SQLiteDatabase}; -use nu_engine::CallExt; -use nu_protocol::{ - ast::Call, - engine::{Command, EngineState, Stack}, - Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, - Type, Value, -}; -use sqlparser::ast::{Query, Select, SelectItem, SetExpr, Statement}; - -#[derive(Clone)] -pub struct ProjectionDb; - -impl Command for ProjectionDb { - fn name(&self) -> &str { - "select" - } - - fn usage(&self) -> &str { - "Creates a select statement for a DB" - } - - fn signature(&self) -> Signature { - Signature::build(self.name()) - .rest( - "select", - SyntaxShape::Any, - "Select expression(s) on the table", - ) - .input_type(Type::Custom("database".into())) - .output_type(Type::Custom("database".into())) - .category(Category::Custom("database".into())) - } - - fn search_terms(&self) -> Vec<&str> { - vec!["database"] - } - - fn examples(&self) -> Vec { - vec![ - Example { - description: "selects a column from a database", - example: "open db.sqlite | into db | select a | describe", - result: Some(Value::Record { - cols: vec!["connection".into(), "query".into()], - vals: vec![ - Value::String { - val: "db.sqlite".into(), - span: Span::test_data(), - }, - Value::String { - val: "SELECT a".into(), - span: Span::test_data(), - }, - ], - span: Span::test_data(), - }), - }, - Example { - description: "selects columns from a database using alias", - example: r#"open db.sqlite - | into db - | select (field a | as new_a) b c - | from table table_1 - | describe"#, - result: Some(Value::Record { - cols: vec!["connection".into(), "query".into()], - vals: vec![ - Value::String { - val: "db.sqlite".into(), - span: Span::test_data(), - }, - Value::String { - val: "SELECT a AS new_a, b, c FROM table_1".into(), - span: Span::test_data(), - }, - ], - span: Span::test_data(), - }), - }, - ] - } - - fn run( - &self, - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - input: PipelineData, - ) -> Result { - let vals: Vec = call.rest(engine_state, stack, 0)?; - let value = Value::List { - vals, - span: call.head, - }; - let projection = SelectDb::extract_selects(value)?; - - let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?; - db.statement = match db.statement { - None => Some(create_statement(projection)), - Some(statement) => Some(modify_statement(statement, projection, call.head)?), - }; - - Ok(db.into_value(call.head).into_pipeline_data()) - } -} - -fn create_statement(expressions: Vec) -> Statement { - let query = Query { - with: None, - body: Box::new(SetExpr::Select(Box::new(create_select(expressions)))), - order_by: Vec::new(), - limit: None, - offset: None, - fetch: None, - lock: None, - }; - - Statement::Query(Box::new(query)) -} - -fn modify_statement( - mut statement: Statement, - expressions: Vec, - span: Span, -) -> Result { - match statement { - Statement::Query(ref mut query) => { - match *query.body { - SetExpr::Select(ref mut select) => select.as_mut().projection = expressions, - _ => { - query.as_mut().body = - Box::new(SetExpr::Select(Box::new(create_select(expressions)))); - } - }; - - Ok(statement) - } - s => Err(ShellError::GenericError( - "Connection doesn't define a statement".into(), - format!("Expected a connection with query. Got {}", s), - Some(span), - None, - Vec::new(), - )), - } -} - -fn create_select(projection: Vec) -> Select { - Select { - distinct: false, - top: None, - projection, - into: None, - from: Vec::new(), - lateral_views: Vec::new(), - selection: None, - group_by: Vec::new(), - cluster_by: Vec::new(), - distribute_by: Vec::new(), - sort_by: Vec::new(), - having: None, - qualify: None, - } -} - -#[cfg(test)] -mod test { - use super::super::super::expressions::{AliasExpr, FieldExpr}; - use super::super::FromDb; - use super::*; - use crate::database::test_database::test_database; - - #[test] - fn test_examples() { - test_database(vec![ - Box::new(ProjectionDb {}), - Box::new(FromDb {}), - Box::new(FieldExpr {}), - Box::new(AliasExpr {}), - ]) - } -} diff --git a/crates/nu-command/src/database/commands/testing_db.rs b/crates/nu-command/src/database/commands/testing_db.rs deleted file mode 100644 index 4fd286e299..0000000000 --- a/crates/nu-command/src/database/commands/testing_db.rs +++ /dev/null @@ -1,76 +0,0 @@ -use nu_engine::CallExt; -use nu_protocol::{ - ast::Call, - engine::{Command, EngineState, Stack}, - Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape, - Value, -}; -use sqlparser::dialect::GenericDialect; -use sqlparser::parser::Parser; - -#[derive(Clone)] -pub struct TestingDb; - -impl Command for TestingDb { - fn name(&self) -> &str { - "testing-db" - } - - fn signature(&self) -> Signature { - Signature::build(self.name()) - .required( - "query", - SyntaxShape::String, - "SQL to execute to create the query object", - ) - .category(Category::Custom("database".into())) - } - - fn usage(&self) -> &str { - "Temporal Command: Create query object" - } - - fn examples(&self) -> Vec { - vec![Example { - description: "", - example: "", - result: None, - }] - } - - fn search_terms(&self) -> Vec<&str> { - vec!["database", "SQLite"] - } - - fn run( - &self, - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - _input: PipelineData, - ) -> Result { - let sql: Spanned = call.req(engine_state, stack, 0)?; - - let dialect = GenericDialect {}; // or AnsiDialect, or your own dialect ... - - let ast = Parser::parse_sql(&dialect, sql.item.as_str()).map_err(|e| { - ShellError::GenericError( - "Error creating AST".into(), - e.to_string(), - Some(sql.span), - None, - Vec::new(), - ) - })?; - - let value = match ast.get(0) { - None => Value::nothing(call.head), - Some(statement) => Value::String { - val: format!("{:#?}", statement), - span: call.head, - }, - }; - - Ok(value.into_pipeline_data()) - } -} diff --git a/crates/nu-command/src/database/commands/where_.rs b/crates/nu-command/src/database/commands/where_.rs deleted file mode 100644 index 733f8cf207..0000000000 --- a/crates/nu-command/src/database/commands/where_.rs +++ /dev/null @@ -1,150 +0,0 @@ -use crate::database::values::dsl::ExprDb; - -use super::super::SQLiteDatabase; -use nu_engine::CallExt; -use nu_protocol::{ - ast::Call, - engine::{Command, EngineState, Stack}, - Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, - Type, Value, -}; -use sqlparser::ast::{Expr, Query, Select, SetExpr, Statement}; - -#[derive(Clone)] -pub struct WhereDb; - -impl Command for WhereDb { - fn name(&self) -> &str { - "where" - } - - fn usage(&self) -> &str { - "Includes a where statement for a query" - } - - fn signature(&self) -> Signature { - Signature::build(self.name()) - .required("where", SyntaxShape::Any, "Where expression on the table") - .input_type(Type::Custom("database".into())) - .output_type(Type::Custom("database".into())) - .category(Category::Custom("database".into())) - } - - fn search_terms(&self) -> Vec<&str> { - vec!["database"] - } - - fn examples(&self) -> Vec { - vec![Example { - description: "selects a column from a database with a where clause", - example: r#"open db.sqlite - | from table table_1 - | select a - | where ((field a) > 1) - | describe"#, - result: Some(Value::Record { - cols: vec!["connection".into(), "query".into()], - vals: vec![ - Value::String { - val: "db.sqlite".into(), - span: Span::test_data(), - }, - Value::String { - val: "SELECT a FROM table_1 WHERE a > 1".into(), - span: Span::test_data(), - }, - ], - span: Span::test_data(), - }), - }] - } - - fn run( - &self, - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - input: PipelineData, - ) -> Result { - let value: Value = call.req(engine_state, stack, 0)?; - let expr = ExprDb::try_from_value(&value)?.into_native(); - - let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?; - match db.statement.as_mut() { - Some(statement) => match statement { - Statement::Query(query) => modify_query(query, expr), - s => { - return Err(ShellError::GenericError( - "Connection doesnt define a query".into(), - format!("Expected a connection with query. Got {}", s), - Some(call.head), - None, - Vec::new(), - )) - } - }, - None => { - return Err(ShellError::GenericError( - "Connection without statement".into(), - "The connection needs a statement defined".into(), - Some(call.head), - None, - Vec::new(), - )) - } - }; - - Ok(db.into_value(call.head).into_pipeline_data()) - } -} - -fn modify_query(query: &mut Box, expression: Expr) { - match *query.body { - SetExpr::Select(ref mut select) => modify_select(select, expression), - _ => { - query.as_mut().body = Box::new(SetExpr::Select(Box::new(create_select(expression)))); - } - }; -} - -fn modify_select(select: &mut Box