use super::blueprint::{ColumnDef, ColumnType, ConstraintDef, ForeignKeyAction, ForeignKeyDef};
use crate::schema::SchemaDialect;
impl SchemaDialect {
pub(crate) fn quote_ident(self, ident: &str) -> String {
let quote = match self {
SchemaDialect::Sqlite | SchemaDialect::Postgres => '"',
SchemaDialect::MariaDb => '`',
};
let escaped = ident.replace(quote, if quote == '"' { "\"\"" } else { "``" });
format!("{quote}{escaped}{quote}")
}
}
pub(crate) fn default_index_name(table: &str, column: &str) -> String {
format!("{table}_{column}_idx")
}
pub(crate) fn infer_referenced_table(column: &str) -> String {
if let Some(prefix) = column.strip_suffix("_id") {
if prefix.ends_with('s') {
prefix.to_owned()
} else {
format!("{prefix}s")
}
} else if column.ends_with('s') {
column.to_owned()
} else {
format!("{column}s")
}
}
pub(crate) fn render_column(dialect: SchemaDialect, column: &ColumnDef) -> String {
let name = dialect.quote_ident(&column.name);
if column.primary_key && column.auto_increment {
return match dialect {
SchemaDialect::Sqlite => format!("{name} integer primary key autoincrement"),
SchemaDialect::Postgres => match &column.ty {
ColumnType::Integer => format!("{name} serial primary key"),
ColumnType::BigInt => format!("{name} bigserial primary key"),
_ => format!("{name} bigserial primary key"),
},
SchemaDialect::MariaDb => match &column.ty {
ColumnType::Integer => {
format!("{name} integer unsigned not null auto_increment primary key")
}
ColumnType::BigInt => {
format!("{name} bigint unsigned not null auto_increment primary key")
}
_ => format!("{name} bigint unsigned not null auto_increment primary key"),
},
};
}
let ty = match (dialect, &column.ty) {
(_, ColumnType::Integer) => "integer".to_owned(),
(_, ColumnType::BigInt) => "bigint".to_owned(),
(_, ColumnType::Bool) => "boolean".to_owned(),
(_, ColumnType::Char(len)) => format!("char({len})"),
(_, ColumnType::Varchar(len)) => format!("varchar({len})"),
(_, ColumnType::Text) => "text".to_owned(),
(_, ColumnType::Date) => "date".to_owned(),
(_, ColumnType::Time) => "time".to_owned(),
(SchemaDialect::Sqlite, ColumnType::DateTime | ColumnType::Timestamp) => {
"datetime".to_owned()
}
(SchemaDialect::Postgres, ColumnType::DateTime) => "timestamp".to_owned(),
(SchemaDialect::MariaDb, ColumnType::DateTime) => "datetime".to_owned(),
(SchemaDialect::Postgres, ColumnType::Timestamp) => "timestamp".to_owned(),
(SchemaDialect::MariaDb, ColumnType::Timestamp) => "timestamp".to_owned(),
(SchemaDialect::Sqlite, ColumnType::Decimal(precision, scale)) => {
format!("numeric({precision}, {scale})")
}
(_, ColumnType::Decimal(precision, scale)) => format!("decimal({precision}, {scale})"),
(SchemaDialect::MariaDb, ColumnType::Float) => "float".to_owned(),
(_, ColumnType::Float) => "real".to_owned(),
(SchemaDialect::MariaDb, ColumnType::Double) => "double".to_owned(),
(_, ColumnType::Double) => "double precision".to_owned(),
(SchemaDialect::Sqlite, ColumnType::Json) => "text".to_owned(),
(SchemaDialect::Postgres, ColumnType::Json) => "jsonb".to_owned(),
(SchemaDialect::MariaDb, ColumnType::Json) => "json".to_owned(),
(SchemaDialect::Sqlite, ColumnType::Uuid) => "text".to_owned(),
(SchemaDialect::Postgres, ColumnType::Uuid) => "uuid".to_owned(),
(SchemaDialect::MariaDb, ColumnType::Uuid) => "char(36)".to_owned(),
(_, ColumnType::Custom(name)) => name.clone(),
};
let mut out = format!("{name} {ty}");
if !column.nullable {
out.push_str(" not null");
}
if column.unique {
out.push_str(" unique");
}
if let Some(default) = &column.default_raw {
out.push_str(" default ");
out.push_str(default);
}
out
}
pub(crate) fn render_foreign_key(dialect: SchemaDialect, foreign: &ForeignKeyDef) -> String {
let mut out = format!(
"foreign key ({}) references {}({})",
dialect.quote_ident(&foreign.column),
dialect.quote_ident(&foreign.references_table),
dialect.quote_ident(&foreign.references_column),
);
if let Some(action) = foreign.on_delete {
out.push_str(" on delete ");
out.push_str(match action {
ForeignKeyAction::Cascade => "cascade",
ForeignKeyAction::Restrict => "restrict",
ForeignKeyAction::SetNull => "set null",
ForeignKeyAction::NoAction => "no action",
});
}
if let Some(action) = foreign.on_update {
out.push_str(" on update ");
out.push_str(match action {
ForeignKeyAction::Cascade => "cascade",
ForeignKeyAction::Restrict => "restrict",
ForeignKeyAction::SetNull => "set null",
ForeignKeyAction::NoAction => "no action",
});
}
out
}
pub(crate) fn render_constraint(dialect: SchemaDialect, constraint: &ConstraintDef) -> String {
match constraint {
ConstraintDef::Primary { columns } => {
let columns = columns
.iter()
.map(|column| dialect.quote_ident(column))
.collect::<Vec<_>>()
.join(", ");
format!("primary key ({columns})")
}
ConstraintDef::Unique { name, columns } => {
let columns = columns
.iter()
.map(|column| dialect.quote_ident(column))
.collect::<Vec<_>>()
.join(", ");
match name {
Some(name) => {
format!(
"constraint {} unique ({columns})",
dialect.quote_ident(name)
)
}
None => format!("unique ({columns})"),
}
}
ConstraintDef::Check { name, expression } => match name {
Some(name) => format!(
"constraint {} check ({expression})",
dialect.quote_ident(name)
),
None => format!("check ({expression})"),
},
}
}