Skip to content

Commit

Permalink
Parse MySQL column default value (#110)
Browse files Browse the repository at this point in the history
* Parse MySQL column default value

* Fixup

* clippy

* Fix

* clippy

* Add ColumnDefault::Null

* Parse extra to determine if it's default expression

* Customized parsing logic for MySQL and MariaDB column default

* Remove current date and time from `ColumnDefault`

* Add `ColumnDefault::CustomExpr`

* Simplify

* Parse custom expression

* fmt

* Fix

* use i64

* Tweaks

* MariaDB >= 10.2.7

---------

Co-authored-by: Chris Tsang <[email protected]>
  • Loading branch information
billy1624 and tyt2y3 authored Jun 10, 2023
1 parent 913ccd5 commit 1cd22b5
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 27 deletions.
10 changes: 7 additions & 3 deletions src/mysql/def/column.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,13 @@ pub enum ColumnKey {

#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
pub struct ColumnDefault {
/// default value expression
pub expr: String,
pub enum ColumnDefault {
Null,
Int(i64),
Real(f64),
String(String),
CustomExpr(String),
CurrentTimestamp,
}

#[derive(Clone, Debug, PartialEq)]
Expand Down
5 changes: 3 additions & 2 deletions src/mysql/discovery/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ impl SchemaDiscovery {
pub async fn discover_table(&self, info: TableInfo) -> Result<TableDef, SqlxError> {
let table = SeaRc::new(Alias::new(info.name.as_str()));
let columns = self
.discover_columns(self.schema.clone(), table.clone())
.discover_columns(self.schema.clone(), table.clone(), &self.query.system)
.await?;
let indexes = self
.discover_indexes(self.schema.clone(), table.clone())
Expand All @@ -114,6 +114,7 @@ impl SchemaDiscovery {
&self,
schema: SeaRc<dyn Iden>,
table: SeaRc<dyn Iden>,
system: &SystemInfo,
) -> Result<Vec<ColumnInfo>, SqlxError> {
let rows = self
.executor
Expand All @@ -125,7 +126,7 @@ impl SchemaDiscovery {
.map(|row| {
let result: ColumnQueryResult = row.into();
debug_print!("{:?}", result);
let column = result.parse();
let column = result.parse(system);
debug_print!("{:?}", column);
column
})
Expand Down
76 changes: 68 additions & 8 deletions src/mysql/parser/column.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@ use crate::{parser::Parser, Name};
use sea_query::{EscapeBuilder, MysqlQueryBuilder};

impl ColumnQueryResult {
pub fn parse(self) -> ColumnInfo {
parse_column_query_result(self)
pub fn parse(self, system: &SystemInfo) -> ColumnInfo {
parse_column_query_result(self, system)
}
}

pub fn parse_column_query_result(result: ColumnQueryResult) -> ColumnInfo {
pub fn parse_column_query_result(result: ColumnQueryResult, system: &SystemInfo) -> ColumnInfo {
let col_type = parse_column_type(&mut Parser::new(&result.column_type));
let default = parse_column_default(&col_type, result.column_default, &result.extra, system);
ColumnInfo {
name: result.column_name,
col_type: parse_column_type(&mut Parser::new(&result.column_type)),
col_type,
null: parse_column_null(&result.is_nullable),
key: parse_column_key(&result.column_key),
default: parse_column_default(result.column_default),
default,
extra: parse_column_extra(&mut Parser::new(&result.extra)),
expression: match result.generation_expression {
Some(generation_expression) => parse_generation_expression(generation_expression),
Expand Down Expand Up @@ -260,11 +262,23 @@ pub fn parse_column_key(string: &str) -> ColumnKey {
}
}

pub fn parse_column_default(column_default: Option<String>) -> Option<ColumnDefault> {
match column_default {
pub fn parse_column_default(
col_type: &Type,
default: Option<String>,
extra: &str,
system: &SystemInfo,
) -> Option<ColumnDefault> {
match default {
Some(default) => {
if !default.is_empty() {
Some(ColumnDefault { expr: default })
let default_value = if system.is_mysql() && system.version >= 80000 {
parse_mysql_8_default(default, extra)
} else if system.is_maria_db() && system.version >= 100207 {
parse_mariadb_10_default(default)
} else {
parse_mysql_5_default(default, col_type)
};
Some(default_value)
} else {
None
}
Expand All @@ -273,6 +287,52 @@ pub fn parse_column_default(column_default: Option<String>) -> Option<ColumnDefa
}
}

pub fn parse_mysql_5_default(default: String, col_type: &Type) -> ColumnDefault {
let is_date_time = matches!(col_type, Type::DateTime(_) | Type::Timestamp(_));
if is_date_time && default == "CURRENT_TIMESTAMP" {
ColumnDefault::CurrentTimestamp
} else if let Ok(int) = default.parse() {
ColumnDefault::Int(int)
} else if let Ok(real) = default.parse() {
ColumnDefault::Real(real)
} else {
ColumnDefault::String(default)
}
}

pub fn parse_mysql_8_default(default: String, extra: &str) -> ColumnDefault {
let is_expression = extra.contains("DEFAULT_GENERATED");
if is_expression && default == "CURRENT_TIMESTAMP" {
ColumnDefault::CurrentTimestamp
} else if is_expression && default == "NULL" {
ColumnDefault::Null
} else if let Ok(int) = default.parse() {
ColumnDefault::Int(int)
} else if let Ok(real) = default.parse() {
ColumnDefault::Real(real)
} else if is_expression {
ColumnDefault::CustomExpr(default)
} else {
ColumnDefault::String(default)
}
}

pub fn parse_mariadb_10_default(default: String) -> ColumnDefault {
if default.starts_with('\'') && default.ends_with('\'') {
ColumnDefault::String(default[1..(default.len() - 1)].into())
} else if let Ok(int) = default.parse() {
ColumnDefault::Int(int)
} else if let Ok(real) = default.parse() {
ColumnDefault::Real(real)
} else if default == "current_timestamp()" {
ColumnDefault::CurrentTimestamp
} else if default == "NULL" {
ColumnDefault::Null
} else {
ColumnDefault::CustomExpr(default)
}
}

pub fn parse_generation_expression(string: String) -> Option<ColumnExpression> {
if string.is_empty() {
None
Expand Down
19 changes: 13 additions & 6 deletions src/mysql/writer/column.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::mysql::def::{CharSet, ColumnInfo, NumericAttr, StringAttr, Type};
use crate::mysql::def::{CharSet, ColumnDefault, ColumnInfo, NumericAttr, StringAttr, Type};
use sea_query::{
Alias, BlobSize, ColumnDef, DynIden, EscapeBuilder, Iden, IntoIden, MysqlQueryBuilder,
Alias, BlobSize, ColumnDef, DynIden, EscapeBuilder, Expr, Iden, IntoIden, Keyword,
MysqlQueryBuilder, SimpleExpr,
};
use std::fmt::Write;

Expand All @@ -15,10 +16,16 @@ impl ColumnInfo {
col_def.auto_increment();
}
let mut extras = Vec::new();
if let Some(default) = self.default.as_ref() {
let mut string = "".to_owned();
write!(&mut string, "DEFAULT {}", default.expr).unwrap();
extras.push(string);
if let Some(default) = &self.default {
let default_expr: SimpleExpr = match default {
ColumnDefault::Null => Option::<bool>::None.into(),
ColumnDefault::Int(int) => (*int).into(),
ColumnDefault::Real(double) => (*double).into(),
ColumnDefault::String(string) => string.into(),
ColumnDefault::CustomExpr(string) => Expr::cust(string),
ColumnDefault::CurrentTimestamp => Keyword::CurrentTimestamp.into(),
};
col_def.default(default_expr);
}
if self.extra.on_update_current_timestamp {
extras.push("ON UPDATE CURRENT_TIMESTAMP".to_owned());
Expand Down
8 changes: 2 additions & 6 deletions src/mysql/writer/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,7 @@ mod tests {
null: false,
key: ColumnKey::NotKey,
default: Some(
ColumnDefault {
expr: "CURRENT_TIMESTAMP".to_owned(),
},
ColumnDefault::CurrentTimestamp,
),
extra: ColumnExtra {
auto_increment: false,
Expand Down Expand Up @@ -219,9 +217,7 @@ mod tests {
null: false,
key: ColumnKey::NotKey,
default: Some(
ColumnDefault {
expr: "CURRENT_TIMESTAMP".to_owned(),
},
ColumnDefault::CurrentTimestamp,
),
extra: ColumnExtra {
auto_increment: false,
Expand Down
23 changes: 21 additions & 2 deletions tests/live/mysql/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use pretty_assertions::assert_eq;
use regex::Regex;
use sea_schema::mysql::{def::TableDef, discovery::SchemaDiscovery};
use sea_schema::sea_query::{
Alias, ColumnDef, ForeignKey, ForeignKeyAction, Index, MysqlQueryBuilder, Table,
Alias, ColumnDef, Expr, ForeignKey, ForeignKeyAction, Index, MysqlQueryBuilder, Table,
TableCreateStatement, TableRef,
};
use sqlx::{MySql, MySqlPool, Pool};
Expand Down Expand Up @@ -206,7 +206,26 @@ fn create_order_table() -> TableCreateStatement {
.col(
ColumnDef::new(Alias::new("placed_at"))
.date_time()
.not_null(),
.not_null()
.default(Expr::current_timestamp()),
)
.col(
ColumnDef::new(Alias::new("updated"))
.date_time()
.not_null()
.default("2023-06-07 16:24:00"),
)
.col(
ColumnDef::new(Alias::new("net_weight"))
.double()
.not_null()
.default(10.05),
)
.col(
ColumnDef::new(Alias::new("priority"))
.integer()
.not_null()
.default(5),
)
.index(
Index::create()
Expand Down

0 comments on commit 1cd22b5

Please sign in to comment.