Skip to content

Commit

Permalink
Support Schema::from_ddl(ddl: &str) -> String (gluesql#1089)
Browse files Browse the repository at this point in the history
Currently we can convert Schema to DDL String by Schema::to_ddl(self) -> String
In this PR, reverse conversion DDL String(CREATE TABLE & CREATE INDEX) to Schema will be supported by

Schema::from_ddl(ddl: &str) -> Result<String>
  • Loading branch information
devgony committed Feb 10, 2023
1 parent 1277a7e commit ffcef50
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 28 deletions.
2 changes: 1 addition & 1 deletion core/src/data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub use {
key::{Key, KeyError},
literal::{Literal, LiteralError},
row::{Row, RowError},
schema::{Schema, SchemaIndex, SchemaIndexOrd},
schema::{Schema, SchemaIndex, SchemaIndexOrd, SchemaParseError},
string_ext::{StringExt, StringExtError},
table::{get_alias, get_index, TableError},
value::{NumericBinaryOperator, Value, ValueError},
Expand Down
186 changes: 160 additions & 26 deletions core/src/data/schema.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
use {
crate::ast::{ColumnDef, Expr, Statement, ToSql},
chrono::NaiveDateTime,
crate::{
ast::{ColumnDef, Expr, Statement, ToSql},
prelude::{parse, translate},
result::Result,
},
chrono::{NaiveDateTime, Utc},
serde::{Deserialize, Serialize},
std::{fmt::Debug, iter},
strum_macros::Display,
thiserror::Error as ThisError,
};

#[cfg(feature = "index")]
use crate::ast::OrderByExpr;

#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Display)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
pub enum SchemaIndexOrd {
Expand All @@ -32,7 +40,7 @@ pub struct Schema {
}

impl Schema {
pub fn to_ddl(self) -> String {
pub fn to_ddl(&self) -> String {
let Schema {
table_name,
column_defs,
Expand All @@ -43,9 +51,9 @@ impl Schema {

let create_table = Statement::CreateTable {
if_not_exists: false,
name: table_name.clone(),
columns: column_defs,
engine,
name: table_name.to_owned(),
columns: column_defs.to_owned(),
engine: engine.to_owned(),
source: None,
}
.to_sql();
Expand All @@ -62,17 +70,122 @@ impl Schema {
.collect::<Vec<_>>()
.join("\n")
}

pub fn from_ddl(ddl: &str) -> Result<Schema> {
let created = Utc::now().naive_utc();
let statements = parse(ddl)?;

let indexes = statements
.iter()
.skip(1)
.map(|create_index| {
let create_index = translate(create_index)?;
match create_index {
#[cfg(feature = "index")]
Statement::CreateIndex {
name,
column: OrderByExpr { expr, asc },
..
} => {
let order = asc
.and_then(|bool| bool.then_some(SchemaIndexOrd::Asc))
.unwrap_or(SchemaIndexOrd::Both);

let index = SchemaIndex {
name,
expr,
order,
created,
};

Ok(index)
}
_ => Err(SchemaParseError::CannotParseDDL.into()),
}
})
.collect::<Result<Vec<_>>>()?;

let create_table = statements.get(0).ok_or(SchemaParseError::CannotParseDDL)?;
let create_table = translate(create_table)?;

match create_table {
Statement::CreateTable {
name,
columns,
engine,
..
} => Ok(Schema {
table_name: name,
column_defs: columns,
indexes,
engine,
created,
}),
_ => Err(SchemaParseError::CannotParseDDL.into()),
}
}
}

#[derive(ThisError, Debug, PartialEq, Serialize)]
pub enum SchemaParseError {
#[error("cannot parse ddl")]
CannotParseDDL,
}

#[cfg(test)]
mod tests {
use crate::{
ast::{AstLiteral, ColumnDef, ColumnUniqueOption, Expr},
chrono::Utc,
data::{Schema, SchemaIndex, SchemaIndexOrd},
prelude::DataType,
use {
super::SchemaParseError,
crate::{
ast::{AstLiteral, ColumnDef, ColumnUniqueOption, Expr},
chrono::Utc,
data::{Schema, SchemaIndex},
prelude::DataType,
},
};

fn assert_schema(actual: Schema, expected: Schema) {
let Schema {
table_name,
column_defs,
indexes,
engine,
..
} = actual;

let Schema {
table_name: table_name_e,
column_defs: column_defs_e,
indexes: indexes_e,
engine: engine_e,
..
} = expected;

assert_eq!(table_name, table_name_e);
assert_eq!(column_defs, column_defs_e);
assert_eq!(engine, engine_e);
indexes
.into_iter()
.zip(indexes_e)
.for_each(|(actual, expected)| assert_index(actual, expected));
}

fn assert_index(actual: SchemaIndex, expected: SchemaIndex) {
let SchemaIndex {
name, expr, order, ..
} = actual;
let SchemaIndex {
name: name_e,
expr: expr_e,
order: order_e,
..
} = expected;

assert_eq!(name, name_e);
assert_eq!(expr, expr_e);
assert_eq!(order, order_e);
}

#[test]
fn table_basic() {
let schema = Schema {
Expand All @@ -98,10 +211,11 @@ mod tests {
created: Utc::now().naive_utc(),
};

assert_eq!(
schema.to_ddl(),
"CREATE TABLE User (id INT NOT NULL, name TEXT NULL DEFAULT 'glue');"
);
let ddl = "CREATE TABLE User (id INT NOT NULL, name TEXT NULL DEFAULT 'glue');";
assert_eq!(schema.to_ddl(), ddl);

let actual = Schema::from_ddl(ddl).unwrap();
assert_schema(actual, schema);

let schema = Schema {
table_name: "Test".to_owned(),
Expand All @@ -110,7 +224,11 @@ mod tests {
engine: None,
created: Utc::now().naive_utc(),
};
assert_eq!(schema.to_ddl(), "CREATE TABLE Test;");
let ddl = "CREATE TABLE Test;";
assert_eq!(schema.to_ddl(), ddl);

let actual = Schema::from_ddl(ddl).unwrap();
assert_schema(actual, schema);
}

#[test]
Expand All @@ -129,14 +247,25 @@ mod tests {
created: Utc::now().naive_utc(),
};

assert_eq!(
schema.to_ddl(),
"CREATE TABLE User (id INT NOT NULL PRIMARY KEY);"
);
let ddl = "CREATE TABLE User (id INT NOT NULL PRIMARY KEY);";
assert_eq!(schema.to_ddl(), ddl);

let actual = Schema::from_ddl(ddl).unwrap();
assert_schema(actual, schema);
}

#[test]
fn invalid_ddl() {
let invalid_ddl = "DROP TABLE Users";
let actual = Schema::from_ddl(invalid_ddl);
assert_eq!(actual, Err(SchemaParseError::CannotParseDDL.into()));
}

#[test]
#[cfg(feature = "index")]
fn table_with_index() {
use crate::data::SchemaIndexOrd;

let schema = Schema {
table_name: "User".to_owned(),
column_defs: Some(vec![
Expand Down Expand Up @@ -172,12 +301,17 @@ mod tests {
engine: None,
created: Utc::now().naive_utc(),
};

assert_eq!(
schema.to_ddl(),
"CREATE TABLE User (id INT NOT NULL, name TEXT NOT NULL);
let ddl = "CREATE TABLE User (id INT NOT NULL, name TEXT NOT NULL);
CREATE INDEX User_id ON User (id);
CREATE INDEX User_name ON User (name);"
);
CREATE INDEX User_name ON User (name);";
assert_eq!(schema.to_ddl(), ddl);

let actual = Schema::from_ddl(ddl).unwrap();
assert_schema(actual, schema);

let index_should_not_be_first = "CREATE INDEX User_id ON User (id);
CREATE TABLE User (id INT NOT NULL, name TEXT NOT NULL);";
let actual = Schema::from_ddl(index_should_not_be_first);
assert_eq!(actual, Err(SchemaParseError::CannotParseDDL.into()));
}
}
6 changes: 5 additions & 1 deletion core/src/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use {
crate::{
ast_builder::AstBuilderError,
data::{
IntervalError, KeyError, LiteralError, RowError, StringExtError, TableError, ValueError,
IntervalError, KeyError, LiteralError, RowError, SchemaParseError, StringExtError,
TableError, ValueError,
},
executor::{
AggregateError, AlterError, EvaluateError, ExecuteError, FetchError, InsertError,
Expand Down Expand Up @@ -84,6 +85,8 @@ pub enum Error {
StringExt(#[from] StringExtError),
#[error(transparent)]
Plan(#[from] PlanError),
#[error(transparent)]
Schema(#[from] SchemaParseError),
}

pub type Result<T, E = Error> = std::result::Result<T, E>;
Expand Down Expand Up @@ -119,6 +122,7 @@ impl PartialEq for Error {
(Interval(e), Interval(e2)) => e == e2,
(StringExt(e), StringExt(e2)) => e == e2,
(Plan(e), Plan(e2)) => e == e2,
(Schema(e), Schema(e2)) => e == e2,
_ => false,
}
}
Expand Down

0 comments on commit ffcef50

Please sign in to comment.