Remove sqlparser SQLite commands (#7040)

This commit is contained in:
Reilly Wood 2022-11-09 14:14:48 -08:00 committed by GitHub
parent c259ef41bd
commit 5ee096232c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 30 additions and 4088 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +0,0 @@
mod expression;
mod select_item;
pub(crate) use expression::ExprDb;
pub(crate) use select_item::SelectDb;

View file

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

View file

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

View file

@ -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,9 +310,7 @@ 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 => {
let db = open_sqlite_db(self.connection.as_path(span)?, span)?;
read_entire_sqlite_db(db, span).map_err(|e| { read_entire_sqlite_db(db, span).map_err(|e| {
ShellError::GenericError( ShellError::GenericError(
"Failed to read from SQLite database".into(), "Failed to read from SQLite database".into(),
@ -379,9 +321,6 @@ impl CustomValue for SQLiteDatabase {
) )
}) })
} }
Some(_) => self.collect(span),
}
}
fn as_any(&self) -> &dyn std::any::Any { fn as_any(&self) -> &dyn std::any::Any {
self self
@ -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(

View file

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