Skip to content
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
20 changes: 17 additions & 3 deletions src/postgres/def/column.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ pub struct ColumnInfo {
/// The type of the column with any additional definitions such as the precision or the character
/// set
pub col_type: ColumnType,
/// The default value experssion for this column, if any
pub default: Option<ColumnExpression>,
/// The generation expression for this column, if it is a generated colum
/// The default value for this column, if any
pub default: Option<ColumnDefault>,
/// The generation expression for this column, if it is a generated column
pub generated: Option<ColumnExpression>,
pub not_null: Option<NotNull>,
pub is_identity: bool,
Expand All @@ -34,6 +34,20 @@ pub struct ColumnInfo {

pub type ColumnType = Type;

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
pub enum ColumnDefault {
Int(i64),
Real(f64),
String(String),
Bool(bool),
CurrentTimestamp,
/// A sequence default, e.g. `nextval('table_id_seq'::regclass)`
AutoIncrement(String),
/// Any other expression not covered by the above variants
Expression(String),
}

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
pub struct ColumnExpression(pub String);
Expand Down
38 changes: 37 additions & 1 deletion src/postgres/parser/column.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,49 @@ pub fn parse_column_query_result(result: ColumnQueryResult, enums: &EnumVariantM
ColumnInfo {
name: result.column_name.clone(),
col_type: parse_column_type(&result, enums),
default: ColumnExpression::from_option_string(result.column_default),
default: parse_column_default(result.column_default),
generated: ColumnExpression::from_option_string(result.column_generated),
not_null: NotNull::from_bool(!yes_or_no_to_bool(&result.is_nullable)),
is_identity: yes_or_no_to_bool(&result.is_identity),
}
}

pub fn parse_column_default(default: Option<String>) -> Option<ColumnDefault> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand this is intended to keep changes minimal. But this approach quite hacky and hard to maintain. It may be better to refactor ColumnQueryResult retrieval to follow recommended practices. We should also take generated columns into consideration.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you.
I did it because MySQL parser used the same style:
https://github.com/sinder38/sea-schema/blob/41d89efde457aa5bc70c54609c96c5281c82affd/src/mysql/parser/column.rs#L265

Sqlite one just inlines it.

So if I am changing this it basically mandatory to change MySQL one.

I actually don't like how quite a lot of things are written is sea-*, but pushing for a refactor would require maintainer support and 100% would break the API.

let default = default?;
if default.is_empty() {
return None;
}
// Trim may be redundant
let def_trim = default.trim();

Some(if def_trim.starts_with("nextval") {
ColumnDefault::AutoIncrement(default)
} else if def_trim == "now()" || def_trim == "CURRENT_TIMESTAMP" {
ColumnDefault::CurrentTimestamp
} else if def_trim == "true" {
ColumnDefault::Bool(true)
} else if def_trim == "false" {
ColumnDefault::Bool(false)
} else if let Ok(int) = def_trim.parse::<i64>() {
ColumnDefault::Int(int)
} else if let Ok(real) = def_trim.parse::<f64>() {
ColumnDefault::Real(real)
} else {
// Check for quoted string literals like 'value'::type or plain 'value'
if let Some(inner) = def_trim.strip_prefix('\'') {
// Find the closing quote — handles 'value'::type_cast
if let Some(end) = inner.find('\'') {
let string_value = inner[..end].to_owned();
let suffix = &inner[end + 1..];
if suffix.is_empty() {
return Some(ColumnDefault::String(string_value));
}
}
}
ColumnDefault::Expression(default)
})
}

pub fn parse_column_type(result: &ColumnQueryResult, enums: &EnumVariantMap) -> ColumnType {
let is_enum = result
.udt_name
Expand Down
43 changes: 29 additions & 14 deletions src/postgres/writer/column.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
use crate::postgres::def::{ColumnInfo, Type};
use sea_query::{Alias, ColumnDef, ColumnType, DynIden, IntoIden, PgInterval, RcOrArc, StringLen};
use std::{convert::TryFrom, fmt::Write};
use crate::postgres::def::{ColumnDefault, ColumnInfo, Type};
use sea_query::{
Alias, ColumnDef, ColumnType, DynIden, Expr, IntoIden, Keyword, PgInterval, RcOrArc,
SimpleExpr, StringLen,
};
use std::convert::TryFrom;

impl ColumnInfo {
pub fn write(&self) -> ColumnDef {
let mut col_info = self.clone();
let mut extras: Vec<String> = Vec::new();
if let Some(default) = self.default.as_ref() {
if default.0.starts_with("nextval") {
col_info = Self::convert_to_serial(col_info);
} else {
let mut string = "".to_owned();
write!(&mut string, "DEFAULT {}", default.0).unwrap();
extras.push(string);
}
if let Some(ColumnDefault::AutoIncrement(_)) = &self.default {
col_info = Self::convert_to_serial(col_info);
}
let col_type = col_info.write_col_type();
let mut col_def = ColumnDef::new_with_type(Alias::new(self.name.as_str()), col_type);
Expand All @@ -29,8 +25,10 @@ impl ColumnInfo {
if self.not_null.is_some() {
col_def.not_null();
}
if !extras.is_empty() {
col_def.extra(extras.join(" "));
if let Some(default) = &self.default {
if let Some(default_expr) = default.write() {
col_def.default(default_expr);
}
}
col_def
}
Expand Down Expand Up @@ -144,3 +142,20 @@ impl ColumnInfo {
write_type(&self.col_type)
}
}

impl ColumnDefault {
/// Convert to a [SimpleExpr] for use with `col_def.default()`.
/// Returns `None` for [ColumnDefault::AutoIncrement] since those are handled
/// via SERIAL type conversion instead.
pub fn write(&self) -> Option<SimpleExpr> {
match self {
ColumnDefault::Int(int) => Some((*int).into()),
ColumnDefault::Real(real) => Some((*real).into()),
ColumnDefault::String(string) => Some(string.into()),
ColumnDefault::Bool(val) => Some(Expr::val(*val)),
ColumnDefault::CurrentTimestamp => Some(Keyword::CurrentTimestamp.into()),
ColumnDefault::AutoIncrement(_) => None,
ColumnDefault::Expression(expr) => Some(Expr::cust(expr.to_owned())),
}
}
}
Loading
Loading