mirror of
https://github.com/nushell/nushell
synced 2024-12-26 21:13:19 +00:00
Remove sqlparser SQLite commands (#7040)
This commit is contained in:
parent
c259ef41bd
commit
5ee096232c
37 changed files with 30 additions and 4088 deletions
|
@ -146,8 +146,8 @@ features = [
|
||||||
trash-support = ["trash"]
|
trash-support = ["trash"]
|
||||||
which-support = ["which"]
|
which-support = ["which"]
|
||||||
plugin = ["nu-parser/plugin"]
|
plugin = ["nu-parser/plugin"]
|
||||||
dataframe = ["polars", "num"]
|
dataframe = ["polars", "num", "sqlparser"]
|
||||||
database = ["sqlparser", "rusqlite"]
|
database = ["rusqlite"] # TODO: given that rusqlite is included in reedline, should we just always include it?
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
shadow-rs = { version = "0.16.1", default-features = false }
|
shadow-rs = { version = "0.16.1", default-features = false }
|
||||||
|
|
|
@ -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<Example> {
|
|
||||||
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<PipelineData, ShellError> {
|
|
||||||
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<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(())
|
|
||||||
}
|
|
||||||
|
|
||||||
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()),
|
|
||||||
op: BinaryOperator::And,
|
|
||||||
right: Box::new(expression),
|
|
||||||
}),
|
|
||||||
None => Err(ShellError::GenericError(
|
|
||||||
"Query without a select".into(),
|
|
||||||
"Missing a WHERE clause before an AND clause".into(),
|
|
||||||
Some(span),
|
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
)),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
select.as_mut().selection = Some(new_expression);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::super::super::expressions::{AndExpr, FieldExpr};
|
|
||||||
use super::super::{FromDb, ProjectionDb, WhereDb};
|
|
||||||
use super::*;
|
|
||||||
use crate::database::test_database::test_database;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_examples() {
|
|
||||||
test_database(vec![
|
|
||||||
Box::new(AndDb {}),
|
|
||||||
Box::new(ProjectionDb {}),
|
|
||||||
Box::new(FromDb {}),
|
|
||||||
Box::new(WhereDb {}),
|
|
||||||
Box::new(FieldExpr {}),
|
|
||||||
Box::new(AndExpr {}),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,173 +0,0 @@
|
||||||
use crate::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::{Ident, SetExpr, Statement, TableAlias, TableFactor};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct AliasDb;
|
|
||||||
|
|
||||||
impl Command for AliasDb {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"as"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build(self.name())
|
|
||||||
.required("alias", SyntaxShape::String, "alias name")
|
|
||||||
.input_type(Type::Custom("database".into()))
|
|
||||||
.output_type(Type::Custom("database".into()))
|
|
||||||
.category(Category::Custom("database".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 selected table",
|
|
||||||
example: r#"open db.sqlite
|
|
||||||
| from table table_1
|
|
||||||
| select a
|
|
||||||
| as t1
|
|
||||||
| 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 AS t1".into(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "Creates an alias for a derived table",
|
|
||||||
example: r#"open db.sqlite
|
|
||||||
| from table (
|
|
||||||
open db.sqlite
|
|
||||||
| from table table_a
|
|
||||||
| select a b
|
|
||||||
)
|
|
||||||
| select a
|
|
||||||
| as t1
|
|
||||||
| 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 (SELECT a, b FROM table_a) AS t1".into(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
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 db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
|
||||||
alias_db(db, alias, call)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alias_db(
|
|
||||||
mut db: SQLiteDatabase,
|
|
||||||
new_alias: String,
|
|
||||||
call: &Call,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
match db.statement.as_mut() {
|
|
||||||
None => Err(ShellError::GenericError(
|
|
||||||
"Error creating alias".into(),
|
|
||||||
"there is no statement defined yet".into(),
|
|
||||||
Some(call.head),
|
|
||||||
None,
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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 => Err(ShellError::GenericError(
|
|
||||||
"Connection doesn't define a query".into(),
|
|
||||||
format!("Expected a connection with query. Got {}", s),
|
|
||||||
Some(call.head),
|
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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 {}),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
use nu_protocol::{
|
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::super::SQLiteDatabase;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct CollectDb;
|
|
||||||
|
|
||||||
impl Command for CollectDb {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"collect"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build(self.name())
|
|
||||||
.input_type(Type::Custom("database".into()))
|
|
||||||
.output_type(Type::Any)
|
|
||||||
.category(Category::Custom("database".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Collects a query from a database database connection"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![Example {
|
|
||||||
description: "Collect from a select query",
|
|
||||||
example: "open foo.db | from table table_1 db | select a | collect",
|
|
||||||
result: None,
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["database"]
|
|
||||||
}
|
|
||||||
|
|
||||||
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)?;
|
|
||||||
|
|
||||||
db.collect(call.head)
|
|
||||||
.map(IntoPipelineData::into_pipeline_data)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
use crate::{database::values::definitions::ConnectionDb, SQLiteDatabase};
|
|
||||||
use nu_protocol::{ShellError, Value};
|
|
||||||
use sqlparser::ast::{ObjectName, Statement, TableAlias, TableFactor};
|
|
||||||
|
|
||||||
pub fn value_into_table_factor(
|
|
||||||
table: Value,
|
|
||||||
connection: &ConnectionDb,
|
|
||||||
alias: Option<TableAlias>,
|
|
||||||
) -> Result<TableFactor, ShellError> {
|
|
||||||
match table {
|
|
||||||
Value::String { val, .. } => {
|
|
||||||
let ident = sqlparser::ast::Ident {
|
|
||||||
value: val,
|
|
||||||
quote_style: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(TableFactor::Table {
|
|
||||||
name: ObjectName(vec![ident]),
|
|
||||||
alias,
|
|
||||||
args: None,
|
|
||||||
with_hints: Vec::new(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Value::CustomValue { span, .. } => {
|
|
||||||
let db = SQLiteDatabase::try_from_value(table)?;
|
|
||||||
|
|
||||||
if &db.connection != connection {
|
|
||||||
return Err(ShellError::GenericError(
|
|
||||||
"Incompatible connections".into(),
|
|
||||||
"trying to join on table with different connection".into(),
|
|
||||||
Some(span),
|
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
match db.statement {
|
|
||||||
Some(statement) => match statement {
|
|
||||||
Statement::Query(query) => Ok(TableFactor::Derived {
|
|
||||||
lateral: false,
|
|
||||||
subquery: query,
|
|
||||||
alias,
|
|
||||||
}),
|
|
||||||
s => Err(ShellError::GenericError(
|
|
||||||
"Connection doesnt define a query".into(),
|
|
||||||
format!("Expected a connection with query. Got {}", s),
|
|
||||||
Some(span),
|
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
None => Err(ShellError::GenericError(
|
|
||||||
"Error creating derived table".into(),
|
|
||||||
"there is no statement defined yet".into(),
|
|
||||||
Some(span),
|
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => Err(ShellError::UnsupportedInput(
|
|
||||||
"String or connection".into(),
|
|
||||||
table.span()?,
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
use nu_protocol::{
|
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::super::SQLiteDatabase;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct DescribeDb;
|
|
||||||
|
|
||||||
impl Command for DescribeDb {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"describe"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build(self.name())
|
|
||||||
.input_type(Type::Custom("database".into()))
|
|
||||||
.output_type(Type::Any)
|
|
||||||
.category(Category::Custom("database".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Describes connection and query of the DB object"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![Example {
|
|
||||||
description: "Describe SQLite database constructed query",
|
|
||||||
example: "open foo.db | from table table_1 | select col_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"]
|
|
||||||
}
|
|
||||||
|
|
||||||
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.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 {}),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,210 +0,0 @@
|
||||||
use crate::database::values::definitions::ConnectionDb;
|
|
||||||
|
|
||||||
use super::{super::SQLiteDatabase, conversions::value_into_table_factor};
|
|
||||||
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, Query, Select, SetExpr, Statement, TableAlias, TableWithJoins};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct FromDb;
|
|
||||||
|
|
||||||
impl Command for FromDb {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"from table"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Select section from query statement for a DB"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build(self.name())
|
|
||||||
.required(
|
|
||||||
"select",
|
|
||||||
SyntaxShape::Any,
|
|
||||||
"table of derived table to select from",
|
|
||||||
)
|
|
||||||
.named(
|
|
||||||
"as",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"Alias for the selected table",
|
|
||||||
Some('a'),
|
|
||||||
)
|
|
||||||
.input_type(Type::Any)
|
|
||||||
.output_type(Type::Custom("database".into()))
|
|
||||||
.category(Category::Custom("database".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["database"]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![Example {
|
|
||||||
description: "Selects a table from database",
|
|
||||||
example: "open db.sqlite | from table table_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 FROM table_a".into(),
|
|
||||||
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 mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
|
||||||
db.statement = match db.statement {
|
|
||||||
None => Some(create_statement(&db.connection, engine_state, stack, call)?),
|
|
||||||
Some(statement) => Some(modify_statement(
|
|
||||||
&db.connection,
|
|
||||||
statement,
|
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
call,
|
|
||||||
)?),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(db.into_value(call.head).into_pipeline_data())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_statement(
|
|
||||||
connection: &ConnectionDb,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
) -> Result<Statement, ShellError> {
|
|
||||||
let query = Query {
|
|
||||||
with: None,
|
|
||||||
body: Box::new(SetExpr::Select(Box::new(create_select(
|
|
||||||
connection,
|
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
call,
|
|
||||||
)?))),
|
|
||||||
order_by: Vec::new(),
|
|
||||||
limit: None,
|
|
||||||
offset: None,
|
|
||||||
fetch: None,
|
|
||||||
lock: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Statement::Query(Box::new(query)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn modify_statement(
|
|
||||||
connection: &ConnectionDb,
|
|
||||||
mut statement: Statement,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
) -> Result<Statement, ShellError> {
|
|
||||||
match statement {
|
|
||||||
Statement::Query(ref mut query) => {
|
|
||||||
match *query.body {
|
|
||||||
SetExpr::Select(ref mut select) => {
|
|
||||||
let table = create_table(connection, engine_state, stack, call)?;
|
|
||||||
select.from.push(table);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
query.as_mut().body = Box::new(SetExpr::Select(Box::new(create_select(
|
|
||||||
connection,
|
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
call,
|
|
||||||
)?)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(statement)
|
|
||||||
}
|
|
||||||
s => Err(ShellError::GenericError(
|
|
||||||
"Connection doesnt define a query".into(),
|
|
||||||
format!("Expected a connection with query. Got {}", s),
|
|
||||||
Some(call.head),
|
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_select(
|
|
||||||
connection: &ConnectionDb,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
) -> Result<Select, ShellError> {
|
|
||||||
Ok(Select {
|
|
||||||
distinct: false,
|
|
||||||
top: None,
|
|
||||||
projection: Vec::new(),
|
|
||||||
into: None,
|
|
||||||
from: vec![create_table(connection, engine_state, stack, call)?],
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_table(
|
|
||||||
connection: &ConnectionDb,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
) -> Result<TableWithJoins, ShellError> {
|
|
||||||
let alias = call
|
|
||||||
.get_flag::<String>(engine_state, stack, "as")?
|
|
||||||
.map(|alias| TableAlias {
|
|
||||||
name: Ident {
|
|
||||||
value: alias,
|
|
||||||
quote_style: None,
|
|
||||||
},
|
|
||||||
columns: Vec::new(),
|
|
||||||
});
|
|
||||||
|
|
||||||
let select_table: Value = call.req(engine_state, stack, 0)?;
|
|
||||||
let table_factor = value_into_table_factor(select_table, connection, alias)?;
|
|
||||||
|
|
||||||
let table = TableWithJoins {
|
|
||||||
relation: table_factor,
|
|
||||||
joins: Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(table)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
use crate::database::test_database::test_database;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_examples() {
|
|
||||||
test_database(vec![Box::new(FromDb {})])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,163 +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::{SetExpr, Statement};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct GroupByDb;
|
|
||||||
|
|
||||||
impl Command for GroupByDb {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"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",
|
|
||||||
)
|
|
||||||
.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", "select"]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![
|
|
||||||
Example {
|
|
||||||
description: "groups by column a and calculates the max",
|
|
||||||
example: r#"open db.sqlite
|
|
||||||
| from table 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.sqlite".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.sqlite
|
|
||||||
| from table 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.sqlite".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(
|
|
||||||
&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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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 {}),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
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 the input into an open db connection"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
|
||||||
"This function is used as a hint to Nushell to optimize the pipeline for database queries."
|
|
||||||
}
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build(self.name())
|
|
||||||
.input_type(Type::Any)
|
|
||||||
.output_type(Type::Custom("database".into()))
|
|
||||||
.category(Category::Custom("database".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["database", "convert"]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![Example {
|
|
||||||
description: "Converts an open file into a db object.",
|
|
||||||
example: "open db.sqlite | into db",
|
|
||||||
result: None,
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -45,7 +45,7 @@ impl Command for IntoSqliteDb {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Convert table into a sqlite database"
|
"Convert table into a SQLite database"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
@ -54,22 +54,22 @@ impl Command for IntoSqliteDb {
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Convert ls entries into a sqlite database with 'main' as the table name",
|
description: "Convert ls entries into a SQLite database with 'main' as the table name",
|
||||||
example: "ls | into sqlite my_ls.db",
|
example: "ls | into sqlite my_ls.db",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert ls entries into a sqlite database with 'my_table' as the table name",
|
description: "Convert ls entries into a SQLite database with 'my_table' as the table name",
|
||||||
example: "ls | into sqlite my_ls.db -t my_table",
|
example: "ls | into sqlite my_ls.db -t my_table",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert table literal into a sqlite database with 'main' as the table name",
|
description: "Convert table literal into a SQLite database with 'main' as the table name",
|
||||||
example: "[[name]; [-----] [someone] [=====] [somename] ['(((((']] | into sqlite filename.db",
|
example: "[[name]; [-----] [someone] [=====] [somename] ['(((((']] | into sqlite filename.db",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert a variety of values in table literal form into a sqlite database",
|
description: "Convert a variety of values in table literal form into a SQLite database",
|
||||||
example: "[one 2 5.2 six true 100mib 25sec] | into sqlite variety.db",
|
example: "[one 2 5.2 six true 100mib 25sec] | into sqlite variety.db",
|
||||||
result: None,
|
result: None,
|
||||||
}]
|
}]
|
||||||
|
|
|
@ -1,248 +0,0 @@
|
||||||
use super::{super::SQLiteDatabase, conversions::value_into_table_factor};
|
|
||||||
use crate::database::values::{definitions::ConnectionDb, 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::{
|
|
||||||
Ident, Join, JoinConstraint, JoinOperator, Select, SetExpr, Statement, TableAlias,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct JoinDb;
|
|
||||||
|
|
||||||
impl Command for JoinDb {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"join"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Joins with another table or derived table. Default join type is inner"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build(self.name())
|
|
||||||
.required(
|
|
||||||
"table",
|
|
||||||
SyntaxShape::Any,
|
|
||||||
"table or derived table to join on",
|
|
||||||
)
|
|
||||||
.required("on", SyntaxShape::Any, "expression to join tables")
|
|
||||||
.named(
|
|
||||||
"as",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"Alias for the selected join",
|
|
||||||
Some('a'),
|
|
||||||
)
|
|
||||||
.switch("left", "left outer join", Some('l'))
|
|
||||||
.switch("right", "right outer join", Some('r'))
|
|
||||||
.switch("outer", "full outer join", Some('o'))
|
|
||||||
.switch("cross", "cross join", Some('c'))
|
|
||||||
.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<Example> {
|
|
||||||
vec![
|
|
||||||
Example {
|
|
||||||
description: "joins two tables on col_b",
|
|
||||||
example: r#"open db.sqlite
|
|
||||||
| from table table_1 --as t1
|
|
||||||
| join table_2 col_b --as t2
|
|
||||||
| select col_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 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.sqlite
|
|
||||||
| from table table_1 --as t1
|
|
||||||
| join (
|
|
||||||
open db.sqlite
|
|
||||||
| from table table_2
|
|
||||||
| select col_c
|
|
||||||
) ((field t1.col_a) == (field t2.col_c)) --as t2 --right
|
|
||||||
| select col_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 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(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
|
||||||
|
|
||||||
db.statement = match db.statement {
|
|
||||||
Some(statement) => Some(modify_statement(
|
|
||||||
&db.connection,
|
|
||||||
statement,
|
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
call,
|
|
||||||
)?),
|
|
||||||
None => {
|
|
||||||
return Err(ShellError::GenericError(
|
|
||||||
"Error creating join".into(),
|
|
||||||
"there is no statement defined yet".into(),
|
|
||||||
Some(call.head),
|
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(db.into_value(call.head).into_pipeline_data())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn modify_statement(
|
|
||||||
connection: &ConnectionDb,
|
|
||||||
mut statement: Statement,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
) -> Result<Statement, ShellError> {
|
|
||||||
match statement {
|
|
||||||
Statement::Query(ref mut query) => {
|
|
||||||
match &mut *query.body {
|
|
||||||
SetExpr::Select(ref mut select) => {
|
|
||||||
modify_from(connection, select, engine_state, stack, call)?
|
|
||||||
}
|
|
||||||
s => {
|
|
||||||
return Err(ShellError::GenericError(
|
|
||||||
"Connection doesnt define a select".into(),
|
|
||||||
format!("Expected a connection with select. Got {}", s),
|
|
||||||
Some(call.head),
|
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(statement)
|
|
||||||
}
|
|
||||||
s => Err(ShellError::GenericError(
|
|
||||||
"Connection doesnt define a query".into(),
|
|
||||||
format!("Expected a connection with query. Got {}", s),
|
|
||||||
Some(call.head),
|
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn modify_from(
|
|
||||||
connection: &ConnectionDb,
|
|
||||||
select: &mut Select,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
) -> Result<(), ShellError> {
|
|
||||||
match select.from.last_mut() {
|
|
||||||
Some(table) => {
|
|
||||||
let alias = call
|
|
||||||
.get_flag::<String>(engine_state, stack, "as")?
|
|
||||||
.map(|alias| TableAlias {
|
|
||||||
name: Ident {
|
|
||||||
value: alias,
|
|
||||||
quote_style: None,
|
|
||||||
},
|
|
||||||
columns: Vec::new(),
|
|
||||||
});
|
|
||||||
|
|
||||||
let join_table: Value = call.req(engine_state, stack, 0)?;
|
|
||||||
let table_factor = value_into_table_factor(join_table, connection, alias)?;
|
|
||||||
|
|
||||||
let on_expr: Value = call.req(engine_state, stack, 1)?;
|
|
||||||
let on_expr = ExprDb::try_from_value(&on_expr)?;
|
|
||||||
|
|
||||||
let join_on = if call.has_flag("left") {
|
|
||||||
JoinOperator::LeftOuter(JoinConstraint::On(on_expr.into_native()))
|
|
||||||
} else if call.has_flag("right") {
|
|
||||||
JoinOperator::RightOuter(JoinConstraint::On(on_expr.into_native()))
|
|
||||||
} else if call.has_flag("outer") {
|
|
||||||
JoinOperator::FullOuter(JoinConstraint::On(on_expr.into_native()))
|
|
||||||
} else {
|
|
||||||
JoinOperator::Inner(JoinConstraint::On(on_expr.into_native()))
|
|
||||||
};
|
|
||||||
|
|
||||||
let join = Join {
|
|
||||||
relation: table_factor,
|
|
||||||
join_operator: join_on,
|
|
||||||
};
|
|
||||||
|
|
||||||
table.joins.push(join);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
None => Err(ShellError::GenericError(
|
|
||||||
"Connection without table defined".into(),
|
|
||||||
"Expected a table defined".into(),
|
|
||||||
Some(call.head),
|
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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 {}),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,122 +0,0 @@
|
||||||
use super::super::SQLiteDatabase;
|
|
||||||
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::Statement;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct LimitDb;
|
|
||||||
|
|
||||||
impl Command for LimitDb {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"limit"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Limit result from query"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build(self.name())
|
|
||||||
.required(
|
|
||||||
"limit",
|
|
||||||
SyntaxShape::Int,
|
|
||||||
"Number of rows to extract for query",
|
|
||||||
)
|
|
||||||
.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", "head", "tail"]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![Example {
|
|
||||||
description: "Limits selection from table",
|
|
||||||
example: r#"open db.sqlite
|
|
||||||
| from table table_a
|
|
||||||
| select a
|
|
||||||
| limit 10
|
|
||||||
| 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 LIMIT 10".into(),
|
|
||||||
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 limit: Value = call.req(engine_state, stack, 0)?;
|
|
||||||
let expr = ExprDb::try_from_value(&limit)?.into_native();
|
|
||||||
|
|
||||||
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
|
||||||
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(),
|
|
||||||
"The connection needs a query 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(LimitDb {}),
|
|
||||||
Box::new(ProjectionDb {}),
|
|
||||||
Box::new(FromDb {}),
|
|
||||||
Box::new(WhereDb {}),
|
|
||||||
Box::new(FieldExpr {}),
|
|
||||||
Box::new(OrExpr {}),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,46 +1,11 @@
|
||||||
// Conversions between value and sqlparser objects
|
|
||||||
pub mod conversions;
|
|
||||||
|
|
||||||
mod and;
|
|
||||||
mod as_;
|
|
||||||
mod collect;
|
|
||||||
mod describe;
|
|
||||||
mod from_table;
|
|
||||||
mod group_by;
|
|
||||||
mod into_db;
|
|
||||||
mod into_sqlite;
|
mod into_sqlite;
|
||||||
mod join;
|
|
||||||
mod limit;
|
|
||||||
mod open_db;
|
|
||||||
mod or;
|
|
||||||
mod order_by;
|
|
||||||
mod query_db;
|
mod query_db;
|
||||||
mod schema;
|
mod schema;
|
||||||
mod select;
|
|
||||||
mod where_;
|
|
||||||
|
|
||||||
// Temporal module to create Query objects
|
|
||||||
mod testing_db;
|
|
||||||
use testing_db::TestingDb;
|
|
||||||
|
|
||||||
use and::AndDb;
|
|
||||||
use as_::AliasDb;
|
|
||||||
use collect::CollectDb;
|
|
||||||
pub(crate) use describe::DescribeDb;
|
|
||||||
pub(crate) use from_table::FromDb;
|
|
||||||
use group_by::GroupByDb;
|
|
||||||
pub(crate) use into_db::ToDataBase;
|
|
||||||
use into_sqlite::IntoSqliteDb;
|
use into_sqlite::IntoSqliteDb;
|
||||||
use join::JoinDb;
|
|
||||||
use limit::LimitDb;
|
|
||||||
use nu_protocol::engine::StateWorkingSet;
|
use nu_protocol::engine::StateWorkingSet;
|
||||||
use open_db::OpenDb;
|
|
||||||
use or::OrDb;
|
|
||||||
use order_by::OrderByDb;
|
|
||||||
use query_db::QueryDb;
|
use query_db::QueryDb;
|
||||||
use schema::SchemaDb;
|
use schema::SchemaDb;
|
||||||
pub(crate) use select::ProjectionDb;
|
|
||||||
use where_::WhereDb;
|
|
||||||
|
|
||||||
pub fn add_commands_decls(working_set: &mut StateWorkingSet) {
|
pub fn add_commands_decls(working_set: &mut StateWorkingSet) {
|
||||||
macro_rules! bind_command {
|
macro_rules! bind_command {
|
||||||
|
@ -53,24 +18,5 @@ pub fn add_commands_decls(working_set: &mut StateWorkingSet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Series commands
|
// Series commands
|
||||||
bind_command!(
|
bind_command!(IntoSqliteDb, QueryDb, SchemaDb);
|
||||||
ToDataBase,
|
|
||||||
AliasDb,
|
|
||||||
AndDb,
|
|
||||||
CollectDb,
|
|
||||||
DescribeDb,
|
|
||||||
FromDb,
|
|
||||||
GroupByDb,
|
|
||||||
IntoSqliteDb,
|
|
||||||
JoinDb,
|
|
||||||
LimitDb,
|
|
||||||
OpenDb,
|
|
||||||
OrderByDb,
|
|
||||||
OrDb,
|
|
||||||
QueryDb,
|
|
||||||
ProjectionDb,
|
|
||||||
SchemaDb,
|
|
||||||
TestingDb,
|
|
||||||
WhereDb
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
use super::super::SQLiteDatabase;
|
|
||||||
use nu_engine::CallExt;
|
|
||||||
use nu_protocol::{
|
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
|
||||||
Type,
|
|
||||||
};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct OpenDb;
|
|
||||||
|
|
||||||
impl Command for OpenDb {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"open-db"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build(self.name())
|
|
||||||
.required("query", SyntaxShape::Filepath, "SQLite file to be opened")
|
|
||||||
.input_type(Type::Any)
|
|
||||||
.output_type(Type::Custom("database".into()))
|
|
||||||
.category(Category::Custom("database".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Open a database"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["database"]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![Example {
|
|
||||||
description: "Creates a connection to a sqlite database based on the file name",
|
|
||||||
example: r#"open-db file.sqlite"#,
|
|
||||||
result: None,
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
_input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let path: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
|
||||||
|
|
||||||
SQLiteDatabase::try_from_path(path.item.as_path(), path.span)
|
|
||||||
.map(|db| db.into_value(call.head).into_pipeline_data())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 OrDb;
|
|
||||||
|
|
||||||
impl Command for OrDb {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"or"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Includes an OR 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<Example> {
|
|
||||||
vec![
|
|
||||||
Example {
|
|
||||||
description: "selects a column from a database with an OR clause",
|
|
||||||
example: r#"open db.sqlite
|
|
||||||
| from table table_1
|
|
||||||
| select a
|
|
||||||
| 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.sqlite".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 an OR clause in the column names and a column",
|
|
||||||
example: r#"open db.sqlite
|
|
||||||
| from table table_1
|
|
||||||
| select a
|
|
||||||
| 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.sqlite".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(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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 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<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(())
|
|
||||||
}
|
|
||||||
|
|
||||||
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()),
|
|
||||||
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 {}),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<Example> {
|
|
||||||
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<PipelineData, ShellError> {
|
|
||||||
let asc = call.has_flag("ascending");
|
|
||||||
let nulls_first = call.has_flag("nulls-first");
|
|
||||||
let expressions: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
|
||||||
let expressions = Value::List {
|
|
||||||
vals: expressions,
|
|
||||||
span: call.head,
|
|
||||||
};
|
|
||||||
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 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<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(
|
|
||||||
"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<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())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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 {}),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -22,7 +22,7 @@ impl Command for SchemaDb {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Show sqlite database information, including its schema."
|
"Show SQLite database information, including its schema."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
@ -54,7 +54,7 @@ impl Command for SchemaDb {
|
||||||
|
|
||||||
cols.push("db_filename".into());
|
cols.push("db_filename".into());
|
||||||
vals.push(Value::String {
|
vals.push(Value::String {
|
||||||
val: sqlite_db.connection.to_string(),
|
val: sqlite_db.path.to_string_lossy().into(),
|
||||||
span,
|
span,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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<Example> {
|
|
||||||
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<PipelineData, ShellError> {
|
|
||||||
let vals: Vec<Value> = 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<SelectItem>) -> 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<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 =
|
|
||||||
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<SelectItem>) -> 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 {}),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<Example> {
|
|
||||||
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<PipelineData, ShellError> {
|
|
||||||
let sql: Spanned<String> = 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())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<Example> {
|
|
||||||
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<PipelineData, ShellError> {
|
|
||||||
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<Query>, 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<Select>, expression: Expr) {
|
|
||||||
select.as_mut().selection = Some(expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_select(expression: Expr) -> Select {
|
|
||||||
Select {
|
|
||||||
distinct: false,
|
|
||||||
top: None,
|
|
||||||
into: None,
|
|
||||||
projection: Vec::new(),
|
|
||||||
from: Vec::new(),
|
|
||||||
lateral_views: Vec::new(),
|
|
||||||
selection: Some(expression),
|
|
||||||
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::{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 {}),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,132 +0,0 @@
|
||||||
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")
|
|
||||||
.input_type(Type::Custom("db-expression".into()))
|
|
||||||
.output_type(Type::Custom("db-expression".into()))
|
|
||||||
.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 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 {})])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,141 +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, 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")
|
|
||||||
.input_type(Type::Custom("db-expression".into()))
|
|
||||||
.output_type(Type::Custom("db-expression".into()))
|
|
||||||
.category(Category::Custom("db-expression".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["database", "expression"]
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {})])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
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())
|
|
||||||
.input_type(Type::Custom("db-expression".into()))
|
|
||||||
.output_type(Type::Any)
|
|
||||||
.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 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 {})])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,78 +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, 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")
|
|
||||||
.input_type(Type::Any)
|
|
||||||
.output_type(Type::Custom("db-expression".into()))
|
|
||||||
.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 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 {})])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,157 +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, 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")
|
|
||||||
.input_type(Type::Any)
|
|
||||||
.output_type(Type::Custom("db-expression".into()))
|
|
||||||
.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.sqlite
|
|
||||||
| from table table_a
|
|
||||||
| select (fn lead col_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 lead(col_a) FROM table_a".into(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
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"),
|
|
||||||
special: false,
|
|
||||||
})
|
|
||||||
.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 {}),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
// 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
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,141 +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, 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")
|
|
||||||
.input_type(Type::Custom("db-expression".into()))
|
|
||||||
.output_type(Type::Custom("db-expression".into()))
|
|
||||||
.category(Category::Custom("db-expression".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["database", "expression"]
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {})])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,152 +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, 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",
|
|
||||||
)
|
|
||||||
.input_type(Type::Custom("db-expression".into()))
|
|
||||||
.output_type(Type::Custom("db-expression".into()))
|
|
||||||
.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.sqlite
|
|
||||||
| from table table_a
|
|
||||||
| select (fn lead col_a | over col_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 lead(col_a) OVER (PARTITION BY col_b) FROM table_a".into(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
span: Span::test_data(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["database", "expression"]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
|
||||||
let value = Value::List {
|
|
||||||
vals,
|
|
||||||
span: call.head,
|
|
||||||
};
|
|
||||||
let partitions = ExprDb::extract_exprs(value)?;
|
|
||||||
|
|
||||||
let mut expression = ExprDb::try_from_pipeline(input, call.head)?;
|
|
||||||
match expression.as_mut() {
|
|
||||||
Expr::Function(function) => {
|
|
||||||
function.over = Some(WindowSpec {
|
|
||||||
partition_by: partitions,
|
|
||||||
order_by: Vec::new(),
|
|
||||||
window_frame: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
s => {
|
|
||||||
return Err(ShellError::GenericError(
|
|
||||||
"Expression doesnt define a function".into(),
|
|
||||||
format!("Expected an expression with a function. Got {}", s),
|
|
||||||
Some(call.head),
|
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(expression.into_value(call.head).into_pipeline_data())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::super::super::commands::{FromDb, ProjectionDb};
|
|
||||||
use super::super::FunctionExpr;
|
|
||||||
use super::*;
|
|
||||||
use crate::database::test_database::test_database;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_examples() {
|
|
||||||
test_database(vec![
|
|
||||||
Box::new(OverExpr {}),
|
|
||||||
Box::new(FunctionExpr {}),
|
|
||||||
Box::new(ProjectionDb {}),
|
|
||||||
Box::new(FromDb {}),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +1,7 @@
|
||||||
mod commands;
|
mod commands;
|
||||||
mod expressions;
|
|
||||||
mod values;
|
mod values;
|
||||||
|
|
||||||
use commands::add_commands_decls;
|
use commands::add_commands_decls;
|
||||||
use expressions::add_expressions_decls;
|
|
||||||
|
|
||||||
pub use values::{
|
pub use values::{
|
||||||
convert_sqlite_row_to_nu_value, convert_sqlite_value_to_nu_value, open_connection_in_memory,
|
convert_sqlite_row_to_nu_value, convert_sqlite_value_to_nu_value, open_connection_in_memory,
|
||||||
|
@ -14,8 +12,4 @@ use nu_protocol::engine::StateWorkingSet;
|
||||||
|
|
||||||
pub fn add_database_decls(working_set: &mut StateWorkingSet) {
|
pub fn add_database_decls(working_set: &mut StateWorkingSet) {
|
||||||
add_commands_decls(working_set);
|
add_commands_decls(working_set);
|
||||||
add_expressions_decls(working_set);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test_database;
|
|
||||||
|
|
|
@ -1,141 +0,0 @@
|
||||||
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",
|
|
||||||
)
|
|
||||||
.input_type(Type::Any)
|
|
||||||
.output_type(Type::Custom("database".into()))
|
|
||||||
.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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
};
|
|
||||||
|
|
||||||
engine_state
|
|
||||||
.merge_delta(delta)
|
|
||||||
.expect("Error merging delta");
|
|
||||||
|
|
||||||
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())
|
|
||||||
};
|
|
||||||
|
|
||||||
engine_state
|
|
||||||
.merge_delta(delta)
|
|
||||||
.expect("Error merging delta");
|
|
||||||
|
|
||||||
let mut stack = Stack::new();
|
|
||||||
|
|
||||||
match eval_block(
|
|
||||||
&engine_state,
|
|
||||||
&mut stack,
|
|
||||||
&block,
|
|
||||||
PipelineData::new(Span::test_data()),
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
) {
|
|
||||||
Err(err) => panic!("test eval error in `{}`: {:?}", example.example, err),
|
|
||||||
Ok(result) => {
|
|
||||||
let result = result.into_value(Span::test_data());
|
|
||||||
println!("input: {}", example.example);
|
|
||||||
println!("result: {:?}", result);
|
|
||||||
println!("done: {:?}", start.elapsed());
|
|
||||||
|
|
||||||
// Note. Value implements PartialEq for Bool, Int, Float, String and Block
|
|
||||||
// If the command you are testing requires to compare another case, then
|
|
||||||
// you need to define its equality in the Value struct
|
|
||||||
if let Some(expected) = example.result {
|
|
||||||
if result != expected {
|
|
||||||
panic!(
|
|
||||||
"the example result is different to expected value: {:?} != {:?}",
|
|
||||||
result, expected
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,3 @@
|
||||||
use nu_protocol::{ShellError, Span};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::{fmt::Display, path::PathBuf};
|
|
||||||
|
|
||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod db_column;
|
pub mod db_column;
|
||||||
pub mod db_constraint;
|
pub mod db_constraint;
|
||||||
|
@ -10,24 +6,3 @@ pub mod db_index;
|
||||||
pub mod db_row;
|
pub mod db_row;
|
||||||
pub mod db_schema;
|
pub mod db_schema;
|
||||||
pub mod db_table;
|
pub mod db_table;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
|
||||||
pub enum ConnectionDb {
|
|
||||||
Path(PathBuf),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for ConnectionDb {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Path(path) => write!(f, "{}", path.to_str().unwrap_or("")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConnectionDb {
|
|
||||||
pub fn as_path(&self, _span: Span) -> Result<&PathBuf, ShellError> {
|
|
||||||
match self {
|
|
||||||
Self::Path(path) => Ok(path),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,373 +0,0 @@
|
||||||
use nu_protocol::{
|
|
||||||
ast::{Operator, PathMember},
|
|
||||||
CustomValue, PipelineData, ShellError, Span, Type, Value,
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use sqlparser::ast::{BinaryOperator, Expr, Ident};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct ExprDb(Expr);
|
|
||||||
|
|
||||||
// Referenced access to the native expression
|
|
||||||
impl AsRef<Expr> for ExprDb {
|
|
||||||
fn as_ref(&self) -> &Expr {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsMut<Expr> for ExprDb {
|
|
||||||
fn as_mut(&mut self) -> &mut Expr {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Expr> for ExprDb {
|
|
||||||
fn from(expr: Expr) -> Self {
|
|
||||||
Self(expr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CustomValue for ExprDb {
|
|
||||||
fn clone_value(&self, span: Span) -> Value {
|
|
||||||
let cloned = Self(self.0.clone());
|
|
||||||
|
|
||||||
Value::CustomValue {
|
|
||||||
val: Box::new(cloned),
|
|
||||||
span,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn value_string(&self) -> String {
|
|
||||||
self.typetag_name().to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
|
|
||||||
Ok(self.to_value(span))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn std::any::Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn follow_path_int(&self, count: usize, span: Span) -> Result<Value, ShellError> {
|
|
||||||
let path = PathMember::Int { val: count, span };
|
|
||||||
|
|
||||||
ExprDb::expr_to_value(self.as_ref(), span).follow_cell_path(&[path], false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn follow_path_string(&self, column_name: String, span: Span) -> Result<Value, ShellError> {
|
|
||||||
let path = PathMember::String {
|
|
||||||
val: column_name,
|
|
||||||
span,
|
|
||||||
};
|
|
||||||
|
|
||||||
ExprDb::expr_to_value(self.as_ref(), span).follow_cell_path(&[path], false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn typetag_name(&self) -> &'static str {
|
|
||||||
"DB expresssion"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn typetag_deserialize(&self) {
|
|
||||||
unimplemented!("typetag_deserialize")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn operation(
|
|
||||||
&self,
|
|
||||||
lhs_span: Span,
|
|
||||||
operator: Operator,
|
|
||||||
op: Span,
|
|
||||||
right: &Value,
|
|
||||||
) -> Result<Value, ShellError> {
|
|
||||||
let right_expr = match right {
|
|
||||||
Value::CustomValue { .. } => ExprDb::try_from_value(right).map(ExprDb::into_native),
|
|
||||||
Value::String { val, .. } => Ok(Expr::Value(
|
|
||||||
sqlparser::ast::Value::SingleQuotedString(val.clone()),
|
|
||||||
)),
|
|
||||||
Value::Int { val, .. } => Ok(Expr::Value(sqlparser::ast::Value::Number(
|
|
||||||
format!("{}", val),
|
|
||||||
false,
|
|
||||||
))),
|
|
||||||
Value::Bool { val, .. } => Ok(Expr::Value(sqlparser::ast::Value::Boolean(*val))),
|
|
||||||
_ => Err(ShellError::OperatorMismatch {
|
|
||||||
op_span: op,
|
|
||||||
lhs_ty: Type::Custom(self.typetag_name().into()),
|
|
||||||
lhs_span,
|
|
||||||
rhs_ty: right.get_type(),
|
|
||||||
rhs_span: right.span()?,
|
|
||||||
}),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
let sql_operator = match operator {
|
|
||||||
Operator::Equal => Ok(BinaryOperator::Eq),
|
|
||||||
Operator::NotEqual => Ok(BinaryOperator::NotEq),
|
|
||||||
Operator::LessThan => Ok(BinaryOperator::Lt),
|
|
||||||
Operator::GreaterThan => Ok(BinaryOperator::Gt),
|
|
||||||
Operator::LessThanOrEqual => Ok(BinaryOperator::LtEq),
|
|
||||||
Operator::GreaterThanOrEqual => Ok(BinaryOperator::GtEq),
|
|
||||||
Operator::RegexMatch => Ok(BinaryOperator::PGRegexMatch),
|
|
||||||
Operator::NotRegexMatch => Ok(BinaryOperator::PGRegexNotMatch),
|
|
||||||
Operator::Plus => Ok(BinaryOperator::Plus),
|
|
||||||
Operator::Minus => Ok(BinaryOperator::Minus),
|
|
||||||
Operator::Multiply => Ok(BinaryOperator::Multiply),
|
|
||||||
Operator::Divide => Ok(BinaryOperator::Divide),
|
|
||||||
Operator::Modulo => Ok(BinaryOperator::Modulo),
|
|
||||||
Operator::FloorDivision => Ok(BinaryOperator::Divide),
|
|
||||||
Operator::And => Ok(BinaryOperator::And),
|
|
||||||
Operator::Or => Ok(BinaryOperator::Or),
|
|
||||||
Operator::In
|
|
||||||
| Operator::NotIn
|
|
||||||
| Operator::Pow
|
|
||||||
| Operator::BitOr
|
|
||||||
| Operator::BitXor
|
|
||||||
| Operator::BitAnd
|
|
||||||
| Operator::ShiftLeft
|
|
||||||
| Operator::ShiftRight
|
|
||||||
| Operator::StartsWith
|
|
||||||
| Operator::EndsWith
|
|
||||||
| Operator::Append => Err(ShellError::UnsupportedOperator(operator, op)),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
let expr = Expr::BinaryOp {
|
|
||||||
left: Box::new(self.as_ref().clone()),
|
|
||||||
op: sql_operator,
|
|
||||||
right: Box::new(right_expr),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(ExprDb(expr).into_value(lhs_span))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExprDb {
|
|
||||||
pub fn try_from_value(value: &Value) -> Result<Self, ShellError> {
|
|
||||||
match value {
|
|
||||||
Value::CustomValue { val, span } => match val.as_any().downcast_ref::<Self>() {
|
|
||||||
Some(expr) => Ok(Self(expr.0.clone())),
|
|
||||||
None => Err(ShellError::CantConvert(
|
|
||||||
"db expression".into(),
|
|
||||||
"non-expression".into(),
|
|
||||||
*span,
|
|
||||||
None,
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
Value::String { val, .. } => Ok(Expr::Identifier(Ident {
|
|
||||||
value: val.clone(),
|
|
||||||
quote_style: None,
|
|
||||||
})
|
|
||||||
.into()),
|
|
||||||
Value::Int { val, .. } => {
|
|
||||||
Ok(Expr::Value(sqlparser::ast::Value::Number(format!("{}", val), false)).into())
|
|
||||||
}
|
|
||||||
x => Err(ShellError::CantConvert(
|
|
||||||
"database".into(),
|
|
||||||
x.get_type().to_string(),
|
|
||||||
x.span()?,
|
|
||||||
None,
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
span,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_native(self) -> Expr {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_value(&self, span: Span) -> Value {
|
|
||||||
ExprDb::expr_to_value(self.as_ref(), span)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convenient function to extrac multiple Expr that could be inside a nushell Value
|
|
||||||
pub fn extract_exprs(value: Value) -> Result<Vec<Expr>, ShellError> {
|
|
||||||
ExtractedExpr::extract_exprs(value).map(ExtractedExpr::into_exprs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ExtractedExpr {
|
|
||||||
Single(Expr),
|
|
||||||
List(Vec<ExtractedExpr>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExtractedExpr {
|
|
||||||
fn into_exprs(self) -> Vec<Expr> {
|
|
||||||
match self {
|
|
||||||
Self::Single(expr) => vec![expr],
|
|
||||||
Self::List(exprs) => exprs
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(ExtractedExpr::into_exprs)
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_exprs(value: Value) -> Result<ExtractedExpr, ShellError> {
|
|
||||||
match value {
|
|
||||||
Value::String { val, .. } => {
|
|
||||||
let expr = Expr::Identifier(Ident {
|
|
||||||
value: val,
|
|
||||||
quote_style: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(ExtractedExpr::Single(expr))
|
|
||||||
}
|
|
||||||
Value::Int { val, .. } => {
|
|
||||||
let expr = Expr::Value(sqlparser::ast::Value::Number(format!("{}", val), false));
|
|
||||||
|
|
||||||
Ok(ExtractedExpr::Single(expr))
|
|
||||||
}
|
|
||||||
Value::Bool { val, .. } => {
|
|
||||||
let expr = Expr::Value(sqlparser::ast::Value::Boolean(val));
|
|
||||||
|
|
||||||
Ok(ExtractedExpr::Single(expr))
|
|
||||||
}
|
|
||||||
Value::CustomValue { .. } => {
|
|
||||||
let expr = ExprDb::try_from_value(&value)?.into_native();
|
|
||||||
Ok(ExtractedExpr::Single(expr))
|
|
||||||
}
|
|
||||||
Value::List { vals, .. } => vals
|
|
||||||
.into_iter()
|
|
||||||
.map(Self::extract_exprs)
|
|
||||||
.collect::<Result<Vec<ExtractedExpr>, ShellError>>()
|
|
||||||
.map(ExtractedExpr::List),
|
|
||||||
x => Err(ShellError::CantConvert(
|
|
||||||
"selection".into(),
|
|
||||||
x.get_type().to_string(),
|
|
||||||
x.span()?,
|
|
||||||
None,
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExprDb {
|
|
||||||
pub fn expr_to_value(expr: &Expr, span: Span) -> Value {
|
|
||||||
match expr {
|
|
||||||
Expr::Identifier(ident) => {
|
|
||||||
let cols = vec!["value".into(), "quoted_style".into()];
|
|
||||||
let val = Value::String {
|
|
||||||
val: ident.value.to_string(),
|
|
||||||
span,
|
|
||||||
};
|
|
||||||
let style = Value::String {
|
|
||||||
val: format!("{:?}", ident.quote_style),
|
|
||||||
span,
|
|
||||||
};
|
|
||||||
|
|
||||||
Value::Record {
|
|
||||||
cols,
|
|
||||||
vals: vec![val, style],
|
|
||||||
span,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Expr::Value(value) => Value::String {
|
|
||||||
val: format!("{}", value),
|
|
||||||
span,
|
|
||||||
},
|
|
||||||
Expr::BinaryOp { left, op, right } => {
|
|
||||||
let cols = vec!["left".into(), "op".into(), "right".into()];
|
|
||||||
let left = ExprDb::expr_to_value(left.as_ref(), span);
|
|
||||||
let right = ExprDb::expr_to_value(right.as_ref(), span);
|
|
||||||
let op = Value::String {
|
|
||||||
val: format!("{}", op),
|
|
||||||
span,
|
|
||||||
};
|
|
||||||
|
|
||||||
let vals = vec![left, op, right];
|
|
||||||
|
|
||||||
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!(),
|
|
||||||
Expr::IsNotNull(_) => todo!(),
|
|
||||||
Expr::IsDistinctFrom(_, _) => todo!(),
|
|
||||||
Expr::IsNotDistinctFrom(_, _) => todo!(),
|
|
||||||
Expr::InList { .. } => todo!(),
|
|
||||||
Expr::InSubquery { .. } => todo!(),
|
|
||||||
Expr::InUnnest { .. } => todo!(),
|
|
||||||
Expr::Between { .. } => todo!(),
|
|
||||||
Expr::UnaryOp { .. } => todo!(),
|
|
||||||
Expr::Cast { .. } => todo!(),
|
|
||||||
Expr::TryCast { .. } => todo!(),
|
|
||||||
Expr::Extract { .. } => todo!(),
|
|
||||||
Expr::Substring { .. } => todo!(),
|
|
||||||
Expr::Trim { .. } => todo!(),
|
|
||||||
Expr::Collate { .. } => todo!(),
|
|
||||||
Expr::TypedString { .. } => todo!(),
|
|
||||||
Expr::MapAccess { .. } => todo!(),
|
|
||||||
Expr::Case { .. } => todo!(),
|
|
||||||
Expr::Exists { .. } => todo!(),
|
|
||||||
Expr::Subquery(_) => todo!(),
|
|
||||||
Expr::ListAgg(_) => todo!(),
|
|
||||||
Expr::GroupingSets(_) => todo!(),
|
|
||||||
Expr::Cube(_) => todo!(),
|
|
||||||
Expr::Rollup(_) => todo!(),
|
|
||||||
Expr::Tuple(_) => todo!(),
|
|
||||||
Expr::ArrayIndex { .. } => todo!(),
|
|
||||||
Expr::Array(_) => todo!(),
|
|
||||||
Expr::JsonAccess { .. } => todo!(),
|
|
||||||
Expr::CompositeAccess { .. } => todo!(),
|
|
||||||
Expr::IsFalse(_) => todo!(),
|
|
||||||
Expr::IsNotFalse(_) => todo!(),
|
|
||||||
Expr::IsTrue(_) => todo!(),
|
|
||||||
Expr::IsNotTrue(_) => todo!(),
|
|
||||||
Expr::IsUnknown(_) => todo!(),
|
|
||||||
Expr::IsNotUnknown(_) => todo!(),
|
|
||||||
Expr::Like { .. } => todo!(),
|
|
||||||
Expr::ILike { .. } => todo!(),
|
|
||||||
Expr::SimilarTo { .. } => todo!(),
|
|
||||||
Expr::AnyOp(_) => todo!(),
|
|
||||||
Expr::AllOp(_) => todo!(),
|
|
||||||
Expr::SafeCast { .. } => todo!(),
|
|
||||||
Expr::AtTimeZone { .. } => todo!(),
|
|
||||||
Expr::Position { .. } => todo!(),
|
|
||||||
Expr::Overlay { .. } => todo!(),
|
|
||||||
Expr::AggregateExpressionWithFilter { .. } => todo!(),
|
|
||||||
Expr::ArraySubquery(_) => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
mod expression;
|
|
||||||
mod select_item;
|
|
||||||
|
|
||||||
pub(crate) use expression::ExprDb;
|
|
||||||
pub(crate) use select_item::SelectDb;
|
|
|
@ -1,263 +0,0 @@
|
||||||
use super::ExprDb;
|
|
||||||
use nu_protocol::{ast::PathMember, CustomValue, ShellError, Span, Value};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use sqlparser::ast::{Expr, Ident, ObjectName, SelectItem};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct SelectDb(SelectItem);
|
|
||||||
|
|
||||||
// Referenced access to the native expression
|
|
||||||
impl AsRef<SelectItem> for SelectDb {
|
|
||||||
fn as_ref(&self) -> &SelectItem {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsMut<SelectItem> for SelectDb {
|
|
||||||
fn as_mut(&mut self) -> &mut SelectItem {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<SelectItem> for SelectDb {
|
|
||||||
fn from(selection: SelectItem) -> Self {
|
|
||||||
Self(selection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Expr> for SelectDb {
|
|
||||||
fn from(expr: Expr) -> Self {
|
|
||||||
SelectItem::UnnamedExpr(expr).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CustomValue for SelectDb {
|
|
||||||
fn clone_value(&self, span: Span) -> Value {
|
|
||||||
let cloned = Self(self.0.clone());
|
|
||||||
|
|
||||||
Value::CustomValue {
|
|
||||||
val: Box::new(cloned),
|
|
||||||
span,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn value_string(&self) -> String {
|
|
||||||
self.typetag_name().to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
|
|
||||||
Ok(self.to_value(span))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn std::any::Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn follow_path_int(&self, count: usize, span: Span) -> Result<Value, ShellError> {
|
|
||||||
let path = PathMember::Int { val: count, span };
|
|
||||||
|
|
||||||
SelectDb::select_to_value(self.as_ref(), span).follow_cell_path(&[path], false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn follow_path_string(&self, column_name: String, span: Span) -> Result<Value, ShellError> {
|
|
||||||
let path = PathMember::String {
|
|
||||||
val: column_name,
|
|
||||||
span,
|
|
||||||
};
|
|
||||||
SelectDb::select_to_value(self.as_ref(), span).follow_cell_path(&[path], false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn typetag_name(&self) -> &'static str {
|
|
||||||
"DB selection"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn typetag_deserialize(&self) {
|
|
||||||
unimplemented!("typetag_deserialize")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SelectDb {
|
|
||||||
pub fn try_from_value(value: &Value) -> Result<Self, ShellError> {
|
|
||||||
match value {
|
|
||||||
Value::CustomValue { val, span } => match val.as_any().downcast_ref::<Self>() {
|
|
||||||
Some(expr) => Ok(Self(expr.0.clone())),
|
|
||||||
None => Err(ShellError::CantConvert(
|
|
||||||
"db selection".into(),
|
|
||||||
"non-expression".into(),
|
|
||||||
*span,
|
|
||||||
None,
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
Value::String { val, .. } => match val.as_str() {
|
|
||||||
"*" => Ok(SelectItem::Wildcard.into()),
|
|
||||||
name if (name.contains('.') && name.contains('*')) => {
|
|
||||||
let parts: Vec<Ident> = name
|
|
||||||
.split('.')
|
|
||||||
.filter(|part| part != &"*")
|
|
||||||
.map(|part| Ident {
|
|
||||||
value: part.to_string(),
|
|
||||||
quote_style: None,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(SelectItem::QualifiedWildcard(ObjectName(parts)).into())
|
|
||||||
}
|
|
||||||
name if name.contains('.') => {
|
|
||||||
let parts: Vec<Ident> = name
|
|
||||||
.split('.')
|
|
||||||
.map(|part| Ident {
|
|
||||||
value: part.to_string(),
|
|
||||||
quote_style: None,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let expr = Expr::CompoundIdentifier(parts);
|
|
||||||
Ok(SelectItem::UnnamedExpr(expr).into())
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let expr = Expr::Identifier(Ident {
|
|
||||||
value: val.clone(),
|
|
||||||
quote_style: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(SelectItem::UnnamedExpr(expr).into())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
x => Err(ShellError::CantConvert(
|
|
||||||
"selection".into(),
|
|
||||||
x.get_type().to_string(),
|
|
||||||
x.span()?,
|
|
||||||
None,
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_value(self, span: Span) -> Value {
|
|
||||||
Value::CustomValue {
|
|
||||||
val: Box::new(self),
|
|
||||||
span,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_native(self) -> SelectItem {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_value(&self, span: Span) -> Value {
|
|
||||||
SelectDb::select_to_value(self.as_ref(), span)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SelectDb {
|
|
||||||
fn select_to_value(select: &SelectItem, span: Span) -> Value {
|
|
||||||
match select {
|
|
||||||
SelectItem::UnnamedExpr(expr) => ExprDb::expr_to_value(expr, span),
|
|
||||||
SelectItem::ExprWithAlias { expr, alias } => {
|
|
||||||
let expr = ExprDb::expr_to_value(expr, span);
|
|
||||||
|
|
||||||
let val = Value::String {
|
|
||||||
val: alias.value.to_string(),
|
|
||||||
span,
|
|
||||||
};
|
|
||||||
let style = Value::String {
|
|
||||||
val: format!("{:?}", alias.quote_style),
|
|
||||||
span,
|
|
||||||
};
|
|
||||||
|
|
||||||
let cols = vec!["value".into(), "quoted_style".into()];
|
|
||||||
let alias = Value::Record {
|
|
||||||
cols,
|
|
||||||
vals: vec![val, style],
|
|
||||||
span,
|
|
||||||
};
|
|
||||||
|
|
||||||
let cols = vec!["expression".into(), "alias".into()];
|
|
||||||
Value::Record {
|
|
||||||
cols,
|
|
||||||
vals: vec![expr, alias],
|
|
||||||
span,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SelectItem::QualifiedWildcard(object) => {
|
|
||||||
let vals: Vec<Value> = object
|
|
||||||
.0
|
|
||||||
.iter()
|
|
||||||
.map(|ident| Value::String {
|
|
||||||
val: ident.value.clone(),
|
|
||||||
span,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Value::List { vals, span }
|
|
||||||
}
|
|
||||||
SelectItem::Wildcard => Value::String {
|
|
||||||
val: "*".into(),
|
|
||||||
span,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convenient function to extrac multiple SelectItem that could be inside a
|
|
||||||
// nushell Value
|
|
||||||
pub fn extract_selects(value: Value) -> Result<Vec<SelectItem>, ShellError> {
|
|
||||||
ExtractedSelect::extract_selects(value).map(ExtractedSelect::into_selects)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enum to represent the parsing of the selects from Value
|
|
||||||
enum ExtractedSelect {
|
|
||||||
Single(SelectItem),
|
|
||||||
List(Vec<ExtractedSelect>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExtractedSelect {
|
|
||||||
fn into_selects(self) -> Vec<SelectItem> {
|
|
||||||
match self {
|
|
||||||
Self::Single(select) => vec![select],
|
|
||||||
Self::List(selects) => selects
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(ExtractedSelect::into_selects)
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_selects(value: Value) -> Result<ExtractedSelect, ShellError> {
|
|
||||||
match value {
|
|
||||||
Value::String { val, .. } => {
|
|
||||||
let expr = Expr::Identifier(Ident {
|
|
||||||
value: val,
|
|
||||||
quote_style: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(ExtractedSelect::Single(SelectItem::UnnamedExpr(expr)))
|
|
||||||
}
|
|
||||||
Value::CustomValue { .. } => {
|
|
||||||
if let Ok(expr) = ExprDb::try_from_value(&value) {
|
|
||||||
Ok(ExtractedSelect::Single(SelectItem::UnnamedExpr(
|
|
||||||
expr.into_native(),
|
|
||||||
)))
|
|
||||||
} else if let Ok(select) = SelectDb::try_from_value(&value) {
|
|
||||||
Ok(ExtractedSelect::Single(select.into_native()))
|
|
||||||
} else {
|
|
||||||
Err(ShellError::CantConvert(
|
|
||||||
"selection".into(),
|
|
||||||
value.get_type().to_string(),
|
|
||||||
value.span()?,
|
|
||||||
None,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::List { vals, .. } => vals
|
|
||||||
.into_iter()
|
|
||||||
.map(Self::extract_selects)
|
|
||||||
.collect::<Result<Vec<ExtractedSelect>, ShellError>>()
|
|
||||||
.map(ExtractedSelect::List),
|
|
||||||
x => Err(ShellError::CantConvert(
|
|
||||||
"selection".into(),
|
|
||||||
x.get_type().to_string(),
|
|
||||||
x.span()?,
|
|
||||||
None,
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,4 @@
|
||||||
pub mod definitions;
|
pub mod definitions;
|
||||||
pub mod dsl;
|
|
||||||
pub mod sqlite;
|
pub mod sqlite;
|
||||||
|
|
||||||
pub use sqlite::{
|
pub use sqlite::{
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
use super::definitions::ConnectionDb;
|
use super::definitions::{
|
||||||
use crate::database::values::definitions::{
|
|
||||||
db::Db, db_column::DbColumn, db_constraint::DbConstraint, db_foreignkey::DbForeignKey,
|
db::Db, db_column::DbColumn, db_constraint::DbConstraint, db_foreignkey::DbForeignKey,
|
||||||
db_index::DbIndex, db_table::DbTable,
|
db_index::DbIndex, db_table::DbTable,
|
||||||
};
|
};
|
||||||
|
|
||||||
use nu_protocol::{CustomValue, PipelineData, ShellError, Span, Spanned, Value};
|
use nu_protocol::{CustomValue, PipelineData, ShellError, Span, Spanned, Value};
|
||||||
use rusqlite::{types::ValueRef, Connection, Row};
|
use rusqlite::{types::ValueRef, Connection, Row};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlparser::ast::Statement;
|
|
||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::Read,
|
io::Read,
|
||||||
|
@ -20,15 +19,13 @@ pub struct SQLiteDatabase {
|
||||||
// I considered storing a SQLite connection here, but decided against it because
|
// I considered storing a SQLite connection here, but decided against it because
|
||||||
// 1) YAGNI, 2) it's not obvious how cloning a connection could work, 3) state
|
// 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.
|
// management gets tricky quick. Revisit this approach if we find a compelling use case.
|
||||||
pub connection: ConnectionDb,
|
pub path: PathBuf,
|
||||||
pub statement: Option<Statement>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SQLiteDatabase {
|
impl SQLiteDatabase {
|
||||||
pub fn new(path: &Path) -> Self {
|
pub fn new(path: &Path) -> Self {
|
||||||
Self {
|
Self {
|
||||||
connection: ConnectionDb::Path(PathBuf::from(path)),
|
path: PathBuf::from(path),
|
||||||
statement: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,8 +49,7 @@ impl SQLiteDatabase {
|
||||||
match value {
|
match value {
|
||||||
Value::CustomValue { val, span } => match val.as_any().downcast_ref::<Self>() {
|
Value::CustomValue { val, span } => match val.as_any().downcast_ref::<Self>() {
|
||||||
Some(db) => Ok(Self {
|
Some(db) => Ok(Self {
|
||||||
connection: db.connection.clone(),
|
path: db.path.clone(),
|
||||||
statement: db.statement.clone(),
|
|
||||||
}),
|
}),
|
||||||
None => Err(ShellError::CantConvert(
|
None => Err(ShellError::CantConvert(
|
||||||
"database".into(),
|
"database".into(),
|
||||||
|
@ -84,7 +80,7 @@ impl SQLiteDatabase {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query(&self, sql: &Spanned<String>, call_span: Span) -> Result<Value, ShellError> {
|
pub fn query(&self, sql: &Spanned<String>, call_span: Span) -> Result<Value, ShellError> {
|
||||||
let db = open_sqlite_db(self.connection.as_path(call_span)?, call_span)?;
|
let db = open_sqlite_db(&self.path, call_span)?;
|
||||||
run_sql_query(db, sql).map_err(|e| {
|
run_sql_query(db, sql).map_err(|e| {
|
||||||
ShellError::GenericError(
|
ShellError::GenericError(
|
||||||
"Failed to query SQLite database".into(),
|
"Failed to query SQLite database".into(),
|
||||||
|
@ -96,58 +92,8 @@ impl SQLiteDatabase {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collect(&self, call_span: Span) -> Result<Value, ShellError> {
|
|
||||||
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(),
|
|
||||||
Some(call_span),
|
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
)),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
let sql = Spanned {
|
|
||||||
item: sql,
|
|
||||||
span: call_span,
|
|
||||||
};
|
|
||||||
|
|
||||||
let db = open_sqlite_db(self.connection.as_path(call_span)?, call_span)?;
|
|
||||||
run_sql_query(db, &sql).map_err(|e| {
|
|
||||||
ShellError::GenericError(
|
|
||||||
"Failed to query SQLite database".into(),
|
|
||||||
e.to_string(),
|
|
||||||
Some(sql.span),
|
|
||||||
None,
|
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn describe(&self, span: Span) -> Value {
|
|
||||||
let cols = vec!["connection".to_string(), "query".to_string()];
|
|
||||||
let connection = Value::String {
|
|
||||||
val: self.connection.to_string(),
|
|
||||||
span,
|
|
||||||
};
|
|
||||||
|
|
||||||
let query = match &self.statement {
|
|
||||||
Some(statement) => format!("{statement}"),
|
|
||||||
None => "".into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let query = Value::String { val: query, span };
|
|
||||||
|
|
||||||
Value::Record {
|
|
||||||
cols,
|
|
||||||
vals: vec![connection, query],
|
|
||||||
span,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn open_connection(&self) -> Result<Connection, rusqlite::Error> {
|
pub fn open_connection(&self) -> Result<Connection, rusqlite::Error> {
|
||||||
let conn = match Connection::open(self.connection.to_string()) {
|
let conn = match Connection::open(&self.path) {
|
||||||
Ok(conn) => conn,
|
Ok(conn) => conn,
|
||||||
Err(err) => return Err(err),
|
Err(err) => return Err(err),
|
||||||
};
|
};
|
||||||
|
@ -156,7 +102,6 @@ impl SQLiteDatabase {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_databases_and_tables(&self, conn: &Connection) -> Result<Vec<Db>, rusqlite::Error> {
|
pub fn get_databases_and_tables(&self, conn: &Connection) -> Result<Vec<Db>, rusqlite::Error> {
|
||||||
// let conn = open_connection(path)?;
|
|
||||||
let mut db_query = conn.prepare("SELECT name FROM pragma_database_list")?;
|
let mut db_query = conn.prepare("SELECT name FROM pragma_database_list")?;
|
||||||
|
|
||||||
let databases = db_query.query_map([], |row| {
|
let databases = db_query.query_map([], |row| {
|
||||||
|
@ -351,8 +296,7 @@ impl SQLiteDatabase {
|
||||||
impl CustomValue for SQLiteDatabase {
|
impl CustomValue for SQLiteDatabase {
|
||||||
fn clone_value(&self, span: Span) -> Value {
|
fn clone_value(&self, span: Span) -> Value {
|
||||||
let cloned = SQLiteDatabase {
|
let cloned = SQLiteDatabase {
|
||||||
connection: self.connection.clone(),
|
path: self.path.clone(),
|
||||||
statement: self.statement.clone(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Value::CustomValue {
|
Value::CustomValue {
|
||||||
|
@ -366,21 +310,16 @@ impl CustomValue for SQLiteDatabase {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
|
fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
|
||||||
match self.statement {
|
let db = open_sqlite_db(&self.path, span)?;
|
||||||
None => {
|
read_entire_sqlite_db(db, span).map_err(|e| {
|
||||||
let db = open_sqlite_db(self.connection.as_path(span)?, span)?;
|
ShellError::GenericError(
|
||||||
read_entire_sqlite_db(db, span).map_err(|e| {
|
"Failed to read from SQLite database".into(),
|
||||||
ShellError::GenericError(
|
e.to_string(),
|
||||||
"Failed to read from SQLite database".into(),
|
Some(span),
|
||||||
e.to_string(),
|
None,
|
||||||
Some(span),
|
Vec::new(),
|
||||||
None,
|
)
|
||||||
Vec::new(),
|
})
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Some(_) => self.collect(span),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn std::any::Any {
|
fn as_any(&self) -> &dyn std::any::Any {
|
||||||
|
@ -393,7 +332,7 @@ impl CustomValue for SQLiteDatabase {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn follow_path_string(&self, _column_name: String, span: Span) -> Result<Value, ShellError> {
|
fn follow_path_string(&self, _column_name: String, span: Span) -> Result<Value, ShellError> {
|
||||||
let db = open_sqlite_db(self.connection.as_path(span)?, span)?;
|
let db = open_sqlite_db(&self.path, span)?;
|
||||||
|
|
||||||
read_single_table(db, _column_name, span).map_err(|e| {
|
read_single_table(db, _column_name, span).map_err(|e| {
|
||||||
ShellError::GenericError(
|
ShellError::GenericError(
|
||||||
|
|
|
@ -59,7 +59,7 @@ fn fails_if_passing_engine_custom_values_to_plugins() {
|
||||||
let actual = nu_with_plugins!(
|
let actual = nu_with_plugins!(
|
||||||
cwd: "tests/fixtures/formats",
|
cwd: "tests/fixtures/formats",
|
||||||
plugin: ("nu_plugin_custom_values"),
|
plugin: ("nu_plugin_custom_values"),
|
||||||
"open-db sample.db | custom-value update"
|
"open sample.db | custom-value update"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(actual
|
assert!(actual
|
||||||
|
|
Loading…
Reference in a new issue