Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DESCRIBE statement #1169

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion cli/src/print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,20 @@ impl<'a, W: Write> Print<W> {
let table = self.build_table(table);
self.writeln(table)?;
}
Payload::ExplainTable(columns) => {
let mut table = self.get_table(vec!["Field", "Type", "Null", "Key", "Default"]);
for row in columns {
table.add_record([
row.name.to_owned(),
row.data_type.to_string(),
row.nullable.to_string(),
row.key.to_string(),
row.default.to_string(),
]);
}
let table = self.build_table(table);
self.write(table)?;
}
Payload::Select { labels, rows } => match &self.option.tabular {
true => {
let labels = labels.iter().map(AsRef::as_ref);
Expand Down Expand Up @@ -359,7 +373,7 @@ mod tests {
fn print_payload() {
use gluesql_core::{
ast::DataType,
prelude::{Payload, PayloadVariable, Value},
prelude::{ExplainTableRow, Payload, PayloadVariable, Value},
};

let mut print = Print::new(Vec::new(), None, Default::default());
Expand Down Expand Up @@ -577,6 +591,47 @@ mod tests {
| mylist | LIST |"
);

test!(
Payload::ExplainTable(vec![
ExplainTableRow {
name: "id".to_owned(),
data_type: DataType::Int,
nullable: false,
key: "PRIMARY KEY".to_owned(),
default: "".to_owned(),
},
ExplainTableRow {
name: "name".to_owned(),
data_type: DataType::Text,
nullable: true,
key: "".to_owned(),
default: "".to_owned(),
},
ExplainTableRow {
name: "age".to_owned(),
data_type: DataType::Int,
nullable: false,
key: "".to_owned(),
default: "".to_owned(),
},
ExplainTableRow {
name: "alive".to_owned(),
data_type: DataType::Boolean,
nullable: true,
key: "".to_owned(),
default: "TRUE".to_owned(),
}
]),
"
| Field | Type | Null | Key | Default |
|-------|---------|-------|-------------|---------|
| id | INT | false | PRIMARY KEY | |
| name | TEXT | true | | |
| age | INT | false | | |
| alive | BOOLEAN | true | | TRUE |
"
);

// ".set tabular OFF" should print SELECTED payload without tabular option
print.set_option(SetOption::Tabular(false));
test!(
Expand Down
17 changes: 17 additions & 0 deletions core/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ pub enum Statement {
ShowColumns {
table_name: String,
},
ExplainTable {
table_name: String,
},
/// SELECT, VALUES
Query(Query),
/// INSERT
Expand Down Expand Up @@ -160,6 +163,9 @@ impl ToSql for Statement {
Statement::ShowColumns { table_name } => {
format!("SHOW COLUMNS FROM {table_name};")
}
Statement::ExplainTable { table_name } => {
format!("EXPLAIN {table_name};")
}
Statement::Insert {
table_name,
columns,
Expand Down Expand Up @@ -376,6 +382,17 @@ mod tests {
)
}

#[test]
fn to_sql_explain_table() {
assert_eq!(
"EXPLAIN Bar;",
Statement::ExplainTable {
table_name: "Bar".into()
}
.to_sql()
)
}

#[test]
fn to_sql_insert() {
assert_eq!(
Expand Down
34 changes: 34 additions & 0 deletions core/src/ast_builder/explain_table.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use {
super::Build,
crate::{ast::Statement, result::Result},
};

#[derive(Clone, Debug)]
pub struct ExplainTableNode {
table_name: String,
}

impl ExplainTableNode {
pub fn new(table_name: String) -> Self {
Self { table_name }
}
}

impl Build for ExplainTableNode {
fn build(self) -> Result<Statement> {
let table_name = self.table_name;
Ok(Statement::ExplainTable { table_name })
}
}

#[cfg(test)]
mod tests {
use crate::ast_builder::{table, test, Build};

#[test]
fn explain_table() {
let actual = table("Foo").explain().build();
let expected = "EXPLAIN Foo";
test(actual, expected);
}
}
2 changes: 2 additions & 0 deletions core/src/ast_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod delete;
mod drop_table;
mod error;
mod execute;
mod explain_table;
mod expr;
mod expr_list;
mod expr_with_alias;
Expand Down Expand Up @@ -53,6 +54,7 @@ pub use {
drop_table::DropTableNode,
error::AstBuilderError,
execute::Execute,
explain_table::ExplainTableNode,
expr_list::ExprList,
expr_with_alias::ExprWithAliasNode,
index::{CreateIndexNode, DropIndexNode},
Expand Down
8 changes: 6 additions & 2 deletions core/src/ast_builder/table_name.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::{
table_factor::TableType, AlterTableNode, CreateIndexNode, CreateTableNode, DeleteNode,
DropIndexNode, DropTableNode, IndexItemNode, InsertNode, OrderByExprNode, SelectNode,
ShowColumnsNode, TableFactorNode, UpdateNode,
DropIndexNode, DropTableNode, ExplainTableNode, IndexItemNode, InsertNode, OrderByExprNode,
SelectNode, ShowColumnsNode, TableFactorNode, UpdateNode,
};
#[derive(Clone, Debug)]
pub struct TableNameNode {
Expand Down Expand Up @@ -36,6 +36,10 @@ impl<'a> TableNameNode {
ShowColumnsNode::new(self.table_name)
}

pub fn explain(self) -> ExplainTableNode {
ExplainTableNode::new(self.table_name)
}

pub fn alias_as(self, table_alias: &str) -> TableFactorNode<'a> {
TableFactorNode {
table_name: self.table_name,
Expand Down
32 changes: 31 additions & 1 deletion core/src/executor/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use {
crate::{
ast::{
AstLiteral, BinaryOperator, DataType, Dictionary, Expr, Query, SelectItem, SetExpr,
Statement, TableAlias, TableFactor, TableWithJoins, Variable,
Statement, TableAlias, TableFactor, TableWithJoins, ToSql, Variable,
},
data::{Key, Row, Schema, Value},
result::Result,
Expand All @@ -35,6 +35,7 @@ pub enum ExecuteError {
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub enum Payload {
ShowColumns(Vec<(String, DataType)>),
ExplainTable(Vec<ExplainTableRow>),
Create,
Insert(usize),
Select {
Expand Down Expand Up @@ -95,6 +96,15 @@ pub enum PayloadVariable {
Version(String),
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct ExplainTableRow {
pub name: String,
pub data_type: DataType,
pub nullable: bool,
pub key: String,
pub default: String,
}

pub async fn execute<T: GStore + GStoreMut>(
storage: &mut T,
statement: &Statement,
Expand Down Expand Up @@ -293,6 +303,26 @@ async fn execute_inner<T: GStore + GStoreMut>(

Ok(Payload::ShowColumns(output))
}
Statement::ExplainTable { table_name } => {
let Schema { column_defs, .. } = storage
.fetch_schema(table_name)
.await?
.ok_or_else(|| ExecuteError::TableNotFound(table_name.to_owned()))?;

let output: Vec<ExplainTableRow> = column_defs
.unwrap_or_default()
.into_iter()
.map(|key| ExplainTableRow {
name: key.name,
data_type: key.data_type,
nullable: key.nullable,
key: key.unique.map(|e| e.to_sql()).unwrap_or_default(),
default: key.default.map(|e| e.to_sql()).unwrap_or_default(),
})
.collect();

Ok(Payload::ExplainTable(output))
}
Statement::ShowIndexes(table_name) => {
let query = Query {
body: SetExpr::Select(Box::new(crate::ast::Select {
Expand Down
2 changes: 1 addition & 1 deletion core/src/executor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub use {
context::RowContext,
delete::DeleteError,
evaluate::{evaluate_stateless, EvaluateError},
execute::{execute, ExecuteError, Payload, PayloadVariable},
execute::{execute, ExecuteError, ExplainTableRow, Payload, PayloadVariable},
fetch::FetchError,
insert::InsertError,
select::SelectError,
Expand Down
2 changes: 1 addition & 1 deletion core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub mod prelude {
pub use crate::{
ast::DataType,
data::{Key, Value},
executor::{execute, Payload, PayloadVariable},
executor::{execute, ExplainTableRow, Payload, PayloadVariable},
glue::Glue,
parse_sql::parse,
plan::plan,
Expand Down
3 changes: 3 additions & 0 deletions core/src/translate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,9 @@ pub fn translate(sql_statement: &SqlStatement) -> Result<Statement> {
SqlStatement::ShowColumns { table_name, .. } => Ok(Statement::ShowColumns {
table_name: translate_object_name(table_name)?,
}),
SqlStatement::ExplainTable { table_name, .. } => Ok(Statement::ExplainTable {
table_name: translate_object_name(table_name)?,
}),
SqlStatement::CreateFunction {
or_replace,
name,
Expand Down
8 changes: 8 additions & 0 deletions pkg/javascript/src/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ fn convert_payload(payload: Payload) -> Json {
"columns": Json::Array(columns),
})
}
Payload::ExplainTable(columns) => {
let columns = columns.into_iter().map(|row| json!(row)).collect();

json!({
"type": "EXPLAIN",
"columns": Json::Array(columns),
})
}
Payload::Insert(num) => json!({
"type": "INSERT",
"affected": num
Expand Down
8 changes: 8 additions & 0 deletions pkg/python/src/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@ fn convert_payload(payload: Payload) -> Json {
Payload::AlterTable => json!({ "type": "ALTER TABLE" }),
Payload::CreateIndex => json!({ "type": "CREATE INDEX" }),
Payload::DropIndex => json!({ "type": "DROP INDEX" }),
Payload::ExplainTable(columns) => {
let columns = columns.into_iter().map(|row| json!(row)).collect();

json!({
"type": "EXPLAIN",
"columns": Json::Array(columns),
})
}
Payload::StartTransaction => json!({ "type": "BEGIN" }),
Payload::Commit => json!({ "type": "COMMIT" }),
Payload::Rollback => json!({ "type": "ROLLBACK" }),
Expand Down
64 changes: 64 additions & 0 deletions test-suite/src/explain_table.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use {
crate::*,
gluesql_core::{
ast::DataType,
executor::{ExecuteError, ExplainTableRow, Payload},
},
};

test_case!(explain_table, {
let g = get_tester!();

g.run(
"
CREATE TABLE person(
id INT PRIMARY KEY,
name TEXT,
age INT NOT NULL,
alive BOOLEAN DEFAULT true
)
",
)
.await;

g.test(
r#"EXPLAIN person"#,
Ok(Payload::ExplainTable(vec![
ExplainTableRow {
name: "id".to_owned(),
data_type: DataType::Int,
nullable: false,
key: "PRIMARY KEY".to_owned(),
default: "".to_owned(),
},
ExplainTableRow {
name: "name".to_owned(),
data_type: DataType::Text,
nullable: true,
key: "".to_owned(),
default: "".to_owned(),
},
ExplainTableRow {
name: "age".to_owned(),
data_type: DataType::Int,
nullable: false,
key: "".to_owned(),
default: "".to_owned(),
},
ExplainTableRow {
name: "alive".to_owned(),
data_type: DataType::Boolean,
nullable: true,
key: "".to_owned(),
default: "TRUE".to_owned(),
},
])),
)
.await;

g.test(
r#"EXPLAIN mytable1"#,
Err(ExecuteError::TableNotFound("mytable1".to_owned()).into()),
)
.await;
});
2 changes: 2 additions & 0 deletions test-suite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub mod default;
pub mod delete;
pub mod dictionary;
pub mod dictionary_index;
pub mod explain_table;
pub mod filter;
pub mod foreign_key;
pub mod function;
Expand Down Expand Up @@ -177,6 +178,7 @@ macro_rules! generate_store_tests {
glue!(order_by, order_by::order_by);
glue!(sql_types, data_type::sql_types::sql_types);
glue!(show_columns, show_columns::show_columns);
glue!(explain_table, explain_table::explain_table);
glue!(int8, data_type::int8::int8);
glue!(int16, data_type::int16::int16);
glue!(int32, data_type::int32::int32);
Expand Down
Loading