Database commands (#5466)

* change query to statement

* internal functions and over definitions

* cargo fmt
This commit is contained in:
Fernando Herrera 2022-05-07 13:33:33 +01:00 committed by GitHub
parent 6cc66c8afd
commit 1cb449b2d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 630 additions and 198 deletions

View File

@ -8,7 +8,7 @@ use nu_protocol::{
engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape,
};
use sqlparser::ast::{Ident, SelectItem, SetExpr, TableAlias, TableFactor};
use sqlparser::ast::{Ident, SelectItem, SetExpr, Statement, TableAlias, TableFactor};
#[derive(Clone)]
pub struct AliasExpr;
@ -110,44 +110,56 @@ fn alias_db(
new_alias: String,
call: &Call,
) -> Result<PipelineData, ShellError> {
match db.query {
match db.statement.as_mut() {
None => Err(ShellError::GenericError(
"Error creating alias".into(),
"there is no query defined yet".into(),
"there is no statement defined yet".into(),
Some(call.head),
None,
Vec::new(),
)),
Some(ref mut query) => match &mut query.body {
SetExpr::Select(ref mut select) => {
select.as_mut().from.iter_mut().for_each(|table| {
let new_alias = Some(TableAlias {
name: Ident {
value: new_alias.clone(),
quote_style: None,
},
columns: Vec::new(),
Some(statement) => match statement {
Statement::Query(query) => match &mut query.body {
SetExpr::Select(select) => {
select.as_mut().from.iter_mut().for_each(|table| {
let new_alias = Some(TableAlias {
name: Ident {
value: new_alias.clone(),
quote_style: None,
},
columns: Vec::new(),
});
if let TableFactor::Table { ref mut alias, .. } = table.relation {
*alias = new_alias;
} else if let TableFactor::Derived { ref mut alias, .. } = table.relation {
*alias = new_alias;
} else if let TableFactor::TableFunction { ref mut alias, .. } =
table.relation
{
*alias = new_alias;
}
});
if let TableFactor::Table { ref mut alias, .. } = table.relation {
*alias = new_alias;
} else if let TableFactor::Derived { ref mut alias, .. } = table.relation {
*alias = new_alias;
} else if let TableFactor::TableFunction { ref mut alias, .. } = table.relation
{
*alias = new_alias;
}
});
Ok(db.into_value(call.head).into_pipeline_data())
Ok(db.into_value(call.head).into_pipeline_data())
}
_ => Err(ShellError::GenericError(
"Error creating alias".into(),
"Query has no select from defined".into(),
Some(call.head),
None,
Vec::new(),
)),
},
s => {
return Err(ShellError::GenericError(
"Connection doesnt define a query".into(),
format!("Expected a connection with query. Got {}", s),
Some(call.head),
None,
Vec::new(),
))
}
_ => Err(ShellError::GenericError(
"Error creating alias".into(),
"Query has no select from defined".into(),
Some(call.head),
None,
Vec::new(),
)),
},
}
}

View File

@ -8,7 +8,7 @@ use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
Value,
};
use sqlparser::ast::{BinaryOperator, Expr, Query, Select, SetExpr};
use sqlparser::ast::{BinaryOperator, Expr, Query, Select, SetExpr, Statement};
#[derive(Clone)]
pub struct AndDb;
@ -78,12 +78,23 @@ impl Command for AndDb {
Ok(expression.into_value(call.head).into_pipeline_data())
} else if let Ok(mut db) = SQLiteDatabase::try_from_value(value.clone()) {
db.query = match db.query {
Some(query) => Some(modify_query(query, expr, 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 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 query".into(),
"Missing query in the connection".into(),
"Connection without statement".into(),
"The connection needs a statement defined".into(),
Some(call.head),
None,
Vec::new(),
@ -103,26 +114,24 @@ impl Command for AndDb {
}
}
fn modify_query(mut query: Query, expression: Expr, span: Span) -> Result<Query, ShellError> {
query.body = match query.body {
SetExpr::Select(select) => Ok(SetExpr::Select(modify_select(select, expression, span)?)),
_ => Err(ShellError::GenericError(
"Query without a select".into(),
"Missing a WHERE clause before an AND clause".into(),
Some(span),
None,
Vec::new(),
)),
}?;
fn modify_query(query: &mut Box<Query>, expression: Expr, span: Span) -> Result<(), ShellError> {
match query.body {
SetExpr::Select(ref mut select) => modify_select(select, expression, span)?,
_ => {
return Err(ShellError::GenericError(
"Query without a select".into(),
"Missing a WHERE clause before an AND clause".into(),
Some(span),
None,
Vec::new(),
))
}
};
Ok(query)
Ok(())
}
fn modify_select(
mut select: Box<Select>,
expression: Expr,
span: Span,
) -> Result<Box<Select>, ShellError> {
fn modify_select(select: &mut Box<Select>, expression: Expr, span: Span) -> Result<(), ShellError> {
let new_expression = match &select.selection {
Some(expr) => Ok(Expr::BinaryOp {
left: Box::new(expr.clone()),
@ -139,5 +148,5 @@ fn modify_select(
}?;
select.as_mut().selection = Some(new_expression);
Ok(select)
Ok(())
}

View File

@ -27,7 +27,7 @@ impl Command for ColExpr {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Creates a named column expression",
example: "col name_1",
example: "db col name_1",
result: None,
}]
}

View File

@ -3,9 +3,11 @@ 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,
};
use sqlparser::ast::{
Ident, ObjectName, Query, Select, SetExpr, Statement, TableFactor, TableWithJoins,
};
use sqlparser::ast::{Ident, ObjectName, Query, Select, SetExpr, TableFactor, TableWithJoins};
#[derive(Clone)]
pub struct FromDb;
@ -51,17 +53,17 @@ impl Command for FromDb {
let table: String = call.req(engine_state, stack, 0)?;
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
db.query = match db.query {
None => Some(create_query(table)),
Some(query) => Some(modify_query(query, table)),
db.statement = match db.statement {
None => Some(create_statement(table)),
Some(statement) => Some(modify_statement(statement, table, call.head)?),
};
Ok(db.into_value(call.head).into_pipeline_data())
}
}
fn create_query(table: String) -> Query {
Query {
fn create_statement(table: String) -> Statement {
let query = Query {
with: None,
body: SetExpr::Select(Box::new(create_select(table))),
order_by: Vec::new(),
@ -69,21 +71,35 @@ fn create_query(table: String) -> Query {
offset: None,
fetch: None,
lock: None,
}
}
fn modify_query(mut query: Query, table: String) -> Query {
query.body = match query.body {
SetExpr::Select(select) => SetExpr::Select(modify_select(select, table)),
_ => SetExpr::Select(Box::new(create_select(table))),
};
query
Statement::Query(Box::new(query))
}
fn modify_select(mut select: Box<Select>, table: String) -> Box<Select> {
select.as_mut().from = create_from(table);
select
fn modify_statement(
mut statement: Statement,
table: String,
span: Span,
) -> Result<Statement, ShellError> {
match statement {
Statement::Query(ref mut query) => {
match query.body {
SetExpr::Select(ref mut select) => select.as_mut().from = create_from(table),
_ => {
query.as_mut().body = SetExpr::Select(Box::new(create_select(table)));
}
};
Ok(statement)
}
s => Err(ShellError::GenericError(
"Connection doesnt define a statement".into(),
format!("Expected a connection with query. Got {}", s),
Some(span),
None,
Vec::new(),
)),
}
}
fn create_select(table: String) -> Select {

View File

@ -0,0 +1,85 @@
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

@ -0,0 +1,102 @@
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, SyntaxShape, Value,
};
use sqlparser::ast::{SetExpr, Statement};
#[derive(Clone)]
pub struct GroupByDb;
impl Command for GroupByDb {
fn name(&self) -> &str {
"db group-by"
}
fn usage(&self) -> &str {
"Group by query"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.rest(
"select",
SyntaxShape::Any,
"Select expression(s) on the table",
)
.category(Category::Custom("database".into()))
}
fn search_terms(&self) -> Vec<&str> {
vec!["database", "select"]
}
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,
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
let value = Value::List {
vals,
span: call.head,
};
let expressions = ExprDb::extract_exprs(value)?;
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
match db.statement.as_mut() {
Some(statement) => match statement {
Statement::Query(ref mut query) => match &mut query.body {
SetExpr::Select(ref mut select) => select.group_by = expressions,
s => {
return Err(ShellError::GenericError(
"Connection doesnt define a select".into(),
format!("Expected a connection with select query. Got {}", s),
Some(call.head),
None,
Vec::new(),
))
}
},
s => {
return Err(ShellError::GenericError(
"Connection doesnt define a query".into(),
format!("Expected a connection with query. Got {}", s),
Some(call.head),
None,
Vec::new(),
))
}
},
None => {
return Err(ShellError::GenericError(
"Connection without statement".into(),
"The connection needs a statement defined".into(),
Some(call.head),
None,
Vec::new(),
))
}
};
Ok(db.into_value(call.head).into_pipeline_data())
}
}

View File

@ -6,6 +6,7 @@ use nu_protocol::{
engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
};
use sqlparser::ast::Statement;
#[derive(Clone)]
pub struct LimitDb;
@ -56,11 +57,19 @@ impl Command for LimitDb {
let expr = ExprDb::try_from_value(&limit)?.into_native();
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
db.query = match db.query {
Some(mut query) => {
query.limit = Some(expr);
Some(query)
}
match db.statement {
Some(ref mut statement) => match statement {
Statement::Query(query) => query.as_mut().limit = Some(expr),
s => {
return Err(ShellError::GenericError(
"Connection doesnt define a statement".into(),
format!("Expected a connection with query. Got {}", s),
Some(call.head),
None,
Vec::new(),
))
}
},
None => {
return Err(ShellError::GenericError(
"Connection without query".into(),

View File

@ -1,12 +1,17 @@
mod alias;
mod and;
mod col;
mod collect;
mod command;
mod describe;
mod from;
mod function;
mod group_by;
mod limit;
mod open;
mod or;
mod order_by;
mod over;
mod query;
mod schema;
mod select;
@ -18,21 +23,26 @@ use testing::TestingDb;
use nu_protocol::engine::StateWorkingSet;
use alias::AliasExpr;
use and::AndDb;
use col::ColExpr;
use collect::CollectDb;
use command::Database;
use describe::DescribeDb;
use from::FromDb;
use function::FunctionExpr;
use group_by::GroupByDb;
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;
use where_::WhereDb;
pub fn add_commands_decls(working_set: &mut StateWorkingSet) {
pub fn add_database_decls(working_set: &mut StateWorkingSet) {
macro_rules! bind_command {
( $command:expr ) => {
working_set.add_decl(Box::new($command));
@ -44,17 +54,22 @@ pub fn add_commands_decls(working_set: &mut StateWorkingSet) {
// Series commands
bind_command!(
AliasExpr,
AndDb,
CollectDb,
ColExpr,
Database,
DescribeDb,
FromDb,
FunctionExpr,
GroupByDb,
QueryDb,
LimitDb,
ProjectionDb,
OpenDb,
OrderByDb,
OrDb,
OverExpr,
SchemaDb,
TestingDb,
WhereDb

View File

@ -8,7 +8,7 @@ use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
Value,
};
use sqlparser::ast::{BinaryOperator, Expr, Query, Select, SetExpr};
use sqlparser::ast::{BinaryOperator, Expr, Query, Select, SetExpr, Statement};
#[derive(Clone)]
pub struct OrDb;
@ -78,12 +78,23 @@ impl Command for OrDb {
Ok(expression.into_value(call.head).into_pipeline_data())
} else if let Ok(mut db) = SQLiteDatabase::try_from_value(value.clone()) {
db.query = match db.query {
Some(query) => Some(modify_query(query, expr, call.head)?),
match db.statement {
Some(ref mut statement) => match statement {
Statement::Query(query) => modify_query(query, expr, call.head)?,
s => {
return Err(ShellError::GenericError(
"Connection doesnt define a query".into(),
format!("Expected a connection with query. Got {}", s),
Some(call.head),
None,
Vec::new(),
))
}
},
None => {
return Err(ShellError::GenericError(
"Connection without query".into(),
"Missing query in the connection".into(),
"Connection without statement".into(),
"The connection needs a statement defined".into(),
Some(call.head),
None,
Vec::new(),
@ -103,26 +114,24 @@ impl Command for OrDb {
}
}
fn modify_query(mut query: Query, expression: Expr, span: Span) -> Result<Query, ShellError> {
query.body = match query.body {
SetExpr::Select(select) => Ok(SetExpr::Select(modify_select(select, expression, span)?)),
_ => Err(ShellError::GenericError(
"Query without a select".into(),
"Missing a WHERE clause before an OR clause".into(),
Some(span),
None,
Vec::new(),
)),
}?;
fn modify_query(query: &mut Box<Query>, expression: Expr, span: Span) -> Result<(), ShellError> {
match query.body {
SetExpr::Select(ref mut select) => modify_select(select, expression, span)?,
_ => {
return Err(ShellError::GenericError(
"Query without a select".into(),
"Missing a WHERE clause before an OR clause".into(),
Some(span),
None,
Vec::new(),
))
}
};
Ok(query)
Ok(())
}
fn modify_select(
mut select: Box<Select>,
expression: Expr,
span: Span,
) -> Result<Box<Select>, ShellError> {
fn modify_select(select: &mut Box<Select>, expression: Expr, span: Span) -> Result<(), ShellError> {
let new_expression = match &select.selection {
Some(expr) => Ok(Expr::BinaryOp {
left: Box::new(expr.clone()),
@ -139,5 +148,5 @@ fn modify_select(
}?;
select.as_mut().selection = Some(new_expression);
Ok(select)
Ok(())
}

View File

@ -7,7 +7,7 @@ use nu_protocol::{
engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
};
use sqlparser::ast::OrderByExpr;
use sqlparser::ast::{Expr, OrderByExpr, Statement};
#[derive(Clone)]
pub struct OrderByDb;
@ -58,40 +58,100 @@ impl Command for OrderByDb {
) -> Result<PipelineData, ShellError> {
let asc = call.has_flag("ascending");
let nulls_first = call.has_flag("nulls_first");
let vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
let value = Value::List {
vals,
let expressions: Vec<Value> = call.rest(engine_state, stack, 0)?;
let expressions = Value::List {
vals: expressions,
span: call.head,
};
let expressions = ExprDb::extract_exprs(value)?;
let expressions = ExprDb::extract_exprs(expressions)?;
let expressions: Vec<OrderByExpr> = expressions
.into_iter()
.map(|expr| OrderByExpr {
expr,
asc: if asc { Some(asc) } else { None },
nulls_first: if nulls_first { Some(nulls_first) } else { None },
})
.collect();
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
db.query = match db.query {
Some(mut query) => {
let mut order_expr: Vec<OrderByExpr> = expressions
.into_iter()
.map(|expr| OrderByExpr {
expr,
asc: if asc { Some(asc) } else { None },
nulls_first: if nulls_first { Some(nulls_first) } else { None },
})
.collect();
let value = input.into_value(call.head);
query.order_by.append(&mut order_expr);
Some(query)
}
if let Ok(expr) = ExprDb::try_from_value(&value) {
update_expressions(expr, expressions, call)
} else if let Ok(db) = SQLiteDatabase::try_from_value(value.clone()) {
update_connection(db, expressions, call)
} else {
Err(ShellError::CantConvert(
"expression or query".into(),
value.get_type().to_string(),
value.span()?,
None,
))
}
}
}
fn update_expressions(
mut expr: ExprDb,
mut expressions: Vec<OrderByExpr>,
call: &Call,
) -> Result<PipelineData, ShellError> {
match expr.as_mut() {
Expr::Function(function) => match &mut function.over {
Some(over) => over.order_by.append(&mut expressions),
None => {
return Err(ShellError::GenericError(
"Connection without query".into(),
"The connection needs a query defined".into(),
"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(db.into_value(call.head).into_pipeline_data())
}
Ok(expr.into_value(call.head).into_pipeline_data())
}
fn update_connection(
mut db: SQLiteDatabase,
mut expressions: Vec<OrderByExpr>,
call: &Call,
) -> Result<PipelineData, ShellError> {
match db.statement.as_mut() {
Some(statement) => match statement {
Statement::Query(query) => {
query.order_by.append(&mut expressions);
}
s => {
return Err(ShellError::GenericError(
"Connection doesnt define a query".into(),
format!("Expected a connection with query. Got {}", s),
Some(call.head),
None,
Vec::new(),
))
}
},
None => {
return Err(ShellError::GenericError(
"Connection without statement".into(),
"The connection needs a statement defined".into(),
Some(call.head),
None,
Vec::new(),
))
}
};
Ok(db.into_value(call.head).into_pipeline_data())
}

View File

@ -0,0 +1,80 @@
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,9 +3,10 @@ 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,
Value,
};
use sqlparser::ast::{Query, Select, SelectItem, SetExpr};
use sqlparser::ast::{Query, Select, SelectItem, SetExpr, Statement};
#[derive(Clone)]
pub struct ProjectionDb;
@ -63,17 +64,17 @@ impl Command for ProjectionDb {
let projection = SelectDb::extract_selects(value)?;
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
db.query = match db.query {
None => Some(create_query(projection)),
Some(query) => Some(modify_query(query, projection)),
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_query(expressions: Vec<SelectItem>) -> Query {
Query {
fn create_statement(expressions: Vec<SelectItem>) -> Statement {
let query = Query {
with: None,
body: SetExpr::Select(Box::new(create_select(expressions))),
order_by: Vec::new(),
@ -81,21 +82,35 @@ fn create_query(expressions: Vec<SelectItem>) -> Query {
offset: None,
fetch: None,
lock: None,
}
}
fn modify_query(mut query: Query, expressions: Vec<SelectItem>) -> Query {
query.body = match query.body {
SetExpr::Select(select) => SetExpr::Select(modify_select(select, expressions)),
_ => SetExpr::Select(Box::new(create_select(expressions))),
};
query
Statement::Query(Box::new(query))
}
fn modify_select(mut select: Box<Select>, projection: Vec<SelectItem>) -> Box<Select> {
select.as_mut().projection = projection;
select
fn modify_statement(
mut statement: Statement,
expressions: Vec<SelectItem>,
span: Span,
) -> Result<Statement, ShellError> {
match statement {
Statement::Query(ref mut query) => {
match query.body {
SetExpr::Select(ref mut select) => select.as_mut().projection = expressions,
_ => {
query.as_mut().body = SetExpr::Select(Box::new(create_select(expressions)));
}
};
Ok(statement)
}
s => Err(ShellError::GenericError(
"Connection doesnt define a statement".into(),
format!("Expected a connection with query. Got {}", s),
Some(span),
None,
Vec::new(),
)),
}
}
fn create_select(projection: Vec<SelectItem>) -> Select {

View File

@ -7,7 +7,7 @@ use nu_protocol::{
engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
};
use sqlparser::ast::{Expr, Query, Select, SetExpr};
use sqlparser::ast::{Expr, Query, Select, SetExpr, Statement};
#[derive(Clone)]
pub struct WhereDb;
@ -54,12 +54,23 @@ impl Command for WhereDb {
let expr = ExprDb::try_from_value(&value)?.into_native();
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
db.query = match db.query {
Some(query) => Some(modify_query(query, expr)),
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 query".into(),
"The connection needs a query defined".into(),
"Connection without statement".into(),
"The connection needs a statement defined".into(),
Some(call.head),
None,
Vec::new(),
@ -71,18 +82,17 @@ impl Command for WhereDb {
}
}
fn modify_query(mut query: Query, expression: Expr) -> Query {
query.body = match query.body {
SetExpr::Select(select) => SetExpr::Select(modify_select(select, expression)),
_ => SetExpr::Select(Box::new(create_select(expression))),
fn modify_query(query: &mut Box<Query>, expression: Expr) {
match query.body {
SetExpr::Select(ref mut select) => modify_select(select, expression),
_ => {
query.as_mut().body = SetExpr::Select(Box::new(create_select(expression)));
}
};
query
}
fn modify_select(mut select: Box<Select>, expression: Expr) -> Box<Select> {
fn modify_select(select: &mut Box<Select>, expression: Expr) {
select.as_mut().selection = Some(expression);
select
}
fn create_select(expression: Expr) -> Select {

View File

@ -1,21 +0,0 @@
mod alias;
mod col;
use nu_protocol::engine::StateWorkingSet;
use alias::AliasExpr;
use col::ColExpr;
pub fn add_expression_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!(AliasExpr, ColExpr);
}

View File

@ -1,16 +1,8 @@
mod commands;
mod values;
mod expressions;
pub use commands::add_commands_decls;
pub use expressions::add_expression_decls;
use nu_protocol::engine::StateWorkingSet;
pub use commands::add_database_decls;
pub use values::{
convert_sqlite_row_to_nu_value, convert_sqlite_value_to_nu_value, open_connection_in_memory,
SQLiteDatabase,
};
pub fn add_database_decls(working_set: &mut StateWorkingSet) {
add_commands_decls(working_set);
add_expression_decls(working_set);
}

View File

@ -1,6 +1,6 @@
use nu_protocol::{
ast::{Operator, PathMember},
CustomValue, ShellError, Span, Type, Value,
CustomValue, PipelineData, ShellError, Span, Type, Value,
};
use serde::{Deserialize, Serialize};
use sqlparser::ast::{BinaryOperator, Expr, Ident};
@ -160,6 +160,11 @@ impl ExprDb {
}
}
pub fn try_from_pipeline(input: PipelineData, span: Span) -> Result<Self, ShellError> {
let value = input.into_value(span);
Self::try_from_value(&value)
}
pub fn into_value(self, span: Span) -> Value {
Value::CustomValue {
val: Box::new(self),
@ -273,6 +278,41 @@ impl ExprDb {
Value::Record { cols, vals, span }
}
Expr::Function(function) => {
let cols = vec![
"name".into(),
"args".into(),
"over".into(),
"distinct".into(),
];
let name = Value::String {
val: function.name.to_string(),
span,
};
let args: Vec<Value> = function
.args
.iter()
.map(|arg| Value::String {
val: arg.to_string(),
span,
})
.collect();
let args = Value::List { vals: args, span };
let over = Value::String {
val: format!("{:?}", function.over),
span,
};
let distinct = Value::Bool {
val: function.distinct,
span,
};
let vals = vec![name, args, over, distinct];
Value::Record { cols, vals, span }
}
Expr::Nested(expr) => ExprDb::expr_to_value(expr, span),
Expr::CompoundIdentifier(_) => todo!(),
Expr::IsNull(_) => todo!(),
@ -292,7 +332,6 @@ impl ExprDb {
Expr::Collate { .. } => todo!(),
Expr::TypedString { .. } => todo!(),
Expr::MapAccess { .. } => todo!(),
Expr::Function(_) => todo!(),
Expr::Case { .. } => todo!(),
Expr::Exists(_) => todo!(),
Expr::Subquery(_) => todo!(),

View File

@ -5,7 +5,7 @@ use crate::database::values::definitions::{
use nu_protocol::{CustomValue, PipelineData, ShellError, Span, Spanned, Value};
use rusqlite::{types::ValueRef, Connection, Row};
use serde::{Deserialize, Serialize};
use sqlparser::ast::Query;
use sqlparser::ast::Statement;
use std::{
fs::File,
io::Read,
@ -20,14 +20,14 @@ pub struct SQLiteDatabase {
// 1) YAGNI, 2) it's not obvious how cloning a connection could work, 3) state
// management gets tricky quick. Revisit this approach if we find a compelling use case.
pub path: PathBuf,
pub query: Option<Query>,
pub statement: Option<Statement>,
}
impl SQLiteDatabase {
pub fn new(path: &Path) -> Self {
Self {
path: PathBuf::from(path),
query: None,
statement: None,
}
}
@ -52,7 +52,7 @@ impl SQLiteDatabase {
Value::CustomValue { val, span } => match val.as_any().downcast_ref::<Self>() {
Some(db) => Ok(Self {
path: db.path.clone(),
query: db.query.clone(),
statement: db.statement.clone(),
}),
None => Err(ShellError::CantConvert(
"database".into(),
@ -96,8 +96,8 @@ impl SQLiteDatabase {
}
pub fn collect(&self, call_span: Span) -> Result<Value, ShellError> {
let sql = match &self.query {
Some(query) => Ok(format!("{}", query)),
let sql = match &self.statement {
Some(statement) => Ok(format!("{}", statement)),
None => Err(ShellError::GenericError(
"Error collecting from db".into(),
"No query found in connection".into(),
@ -131,8 +131,8 @@ impl SQLiteDatabase {
span,
};
let query = match &self.query {
Some(query) => format!("{query}"),
let query = match &self.statement {
Some(statement) => format!("{statement}"),
None => "".into(),
};
@ -351,7 +351,7 @@ impl CustomValue for SQLiteDatabase {
fn clone_value(&self, span: Span) -> Value {
let cloned = SQLiteDatabase {
path: self.path.clone(),
query: self.query.clone(),
statement: self.statement.clone(),
};
Value::CustomValue {