use crate::dialect::{Dialect, DialectKind, SqlType, predicate_sql};
use crate::index::IndexDef;
use crate::error::OrmError;
use super::ddl::{AlterAction, AlterTable, ColumnSpec, DefaultValue, ForeignKeySpec, TableDef};
pub fn create_table(dialect: &dyn Dialect, table: &TableDef) -> crate::Result<Vec<String>> {
let mut statements = Vec::new();
let mut sql = String::from("CREATE TABLE ");
if table.if_not_exists {
sql.push_str("IF NOT EXISTS ");
}
dialect.quote_identifier(&table.name, &mut sql);
sql.push_str(" (");
let auto_pk = auto_increment_pk(table);
for (index, column) in table.columns.iter().enumerate() {
if index != 0 {
sql.push_str(", ");
}
let is_auto_pk = auto_pk == Some(column.name.as_str());
render_column(dialect, column, is_auto_pk, &mut sql);
}
if auto_pk.is_none() {
let pk = primary_key_columns(table);
if !pk.is_empty() {
sql.push_str(", PRIMARY KEY (");
render_identifier_list(dialect, &pk, &mut sql);
sql.push(')');
}
}
for foreign_key in &table.foreign_keys {
sql.push_str(", ");
render_foreign_key(dialect, foreign_key, &mut sql);
}
for check in &table.checks {
sql.push_str(", CHECK (");
sql.push_str(check);
sql.push(')');
}
sql.push(')');
statements.push(sql);
for index in &table.indexes {
statements.push(create_index(dialect, &table.name, index, table.if_not_exists)?);
}
Ok(statements)
}
pub fn drop_table(dialect: &dyn Dialect, name: &str, if_exists: bool) -> String {
let mut sql = String::from("DROP TABLE ");
if if_exists {
sql.push_str("IF EXISTS ");
}
dialect.quote_identifier(name, &mut sql);
sql
}
pub fn create_index(
dialect: &dyn Dialect,
table: &str,
index: &IndexDef,
if_not_exists: bool,
) -> crate::Result<String> {
validate_index(dialect, index)?;
let mut sql = String::from("CREATE ");
if index.unique {
sql.push_str("UNIQUE ");
}
sql.push_str("INDEX ");
if if_not_exists {
sql.push_str("IF NOT EXISTS ");
}
dialect.quote_identifier(&index.name, &mut sql);
sql.push_str(" ON ");
dialect.quote_identifier(table, &mut sql);
if let Some(method) = &index.method {
sql.push_str(" USING ");
sql.push_str(method);
}
sql.push_str(" (");
for (position, column) in index.columns.iter().enumerate() {
if position != 0 {
sql.push_str(", ");
}
match &column.expression {
Some(expression) => {
sql.push('(');
sql.push_str(&predicate_sql(dialect, expression));
sql.push(')');
}
None => dialect.quote_identifier(&column.name, &mut sql),
}
if let Some(collation) = &column.collation {
sql.push_str(" COLLATE ");
sql.push_str(collation);
}
if let Some(opclass) = &column.opclass {
sql.push(' ');
sql.push_str(opclass);
}
if column.descending {
sql.push_str(" DESC");
}
match column.nulls {
Some(crate::index::NullsOrder::First) => sql.push_str(" NULLS FIRST"),
Some(crate::index::NullsOrder::Last) => sql.push_str(" NULLS LAST"),
None => {}
}
}
sql.push(')');
if !index.include.is_empty() {
sql.push_str(" INCLUDE (");
render_identifier_list(dialect, &index.include, &mut sql);
sql.push(')');
}
if let Some(predicate) = &index.predicate {
sql.push_str(" WHERE ");
sql.push_str(&predicate_sql(dialect, predicate));
}
Ok(sql)
}
fn validate_index(dialect: &dyn Dialect, index: &IndexDef) -> crate::Result<()> {
if let Some(method) = &index.method {
if !dialect.supports_index_method() {
return Err(OrmError::configuration(format!(
"{} does not support index method `{method}`",
dialect.name()
)));
}
}
if !index.include.is_empty() && !dialect.supports_index_include() {
return Err(OrmError::configuration(format!(
"{} does not support covering index columns (INCLUDE)",
dialect.name()
)));
}
if index.columns.iter().any(|c| c.opclass.is_some()) && !dialect.supports_index_opclass() {
return Err(OrmError::configuration(format!(
"{} does not support index operator classes",
dialect.name()
)));
}
Ok(())
}
pub fn drop_index(dialect: &dyn Dialect, name: &str, if_exists: bool) -> String {
let mut sql = String::from("DROP INDEX ");
if if_exists {
sql.push_str("IF EXISTS ");
}
dialect.quote_identifier(name, &mut sql);
sql
}
pub fn rename_table(dialect: &dyn Dialect, from: &str, to: &str) -> String {
let mut sql = String::from("ALTER TABLE ");
dialect.quote_identifier(from, &mut sql);
sql.push_str(" RENAME TO ");
dialect.quote_identifier(to, &mut sql);
sql
}
pub fn alter_table(dialect: &dyn Dialect, alter: &AlterTable) -> Vec<String> {
alter
.actions
.iter()
.map(|action| {
let mut sql = String::from("ALTER TABLE ");
dialect.quote_identifier(&alter.table, &mut sql);
match action {
AlterAction::AddColumn(column) => {
sql.push_str(" ADD COLUMN ");
render_column(dialect, column, false, &mut sql);
}
AlterAction::DropColumn(name) => {
sql.push_str(" DROP COLUMN ");
dialect.quote_identifier(name, &mut sql);
}
AlterAction::RenameColumn { from, to } => {
sql.push_str(" RENAME COLUMN ");
dialect.quote_identifier(from, &mut sql);
sql.push_str(" TO ");
dialect.quote_identifier(to, &mut sql);
}
}
sql
})
.collect()
}
fn auto_increment_pk(table: &TableDef) -> Option<&str> {
if !table.primary_key.is_empty() {
return None;
}
let mut found = None;
for column in &table.columns {
if column.primary_key && column.auto_increment {
if found.is_some() {
return None; }
found = Some(column.name.as_str());
}
}
found
}
fn primary_key_columns(table: &TableDef) -> Vec<String> {
if !table.primary_key.is_empty() {
return table.primary_key.clone();
}
table
.columns
.iter()
.filter(|column| column.primary_key)
.map(|column| column.name.clone())
.collect()
}
fn render_column(dialect: &dyn Dialect, column: &ColumnSpec, is_auto_pk: bool, out: &mut String) {
dialect.quote_identifier(&column.name, out);
out.push(' ');
if is_auto_pk {
match dialect.kind() {
DialectKind::Sqlite => out.push_str("INTEGER PRIMARY KEY AUTOINCREMENT"),
DialectKind::Postgres => out.push_str("BIGSERIAL PRIMARY KEY"),
DialectKind::Mysql => out.push_str("BIGINT AUTO_INCREMENT PRIMARY KEY"),
}
return;
}
dialect.map_sql_type(column.ty, out);
if !column.nullable {
out.push_str(" NOT NULL");
}
if column.unique {
out.push_str(" UNIQUE");
}
if let Some(default) = &column.default {
out.push_str(" DEFAULT ");
render_default(dialect, default, out);
}
if let SqlType::Enum { variants, .. } = column.ty {
if dialect.kind() != DialectKind::Mysql {
out.push_str(" CHECK (");
dialect.quote_identifier(&column.name, out);
out.push_str(" IN (");
for (index, variant) in variants.iter().enumerate() {
if index > 0 {
out.push_str(", ");
}
dialect.escape_string_literal(variant, out);
}
out.push_str("))");
}
}
}
pub(crate) fn column_type_str(dialect: &dyn Dialect, ty: SqlType) -> String {
let mut out = String::new();
dialect.map_sql_type(ty, &mut out);
out
}
fn render_default(dialect: &dyn Dialect, default: &DefaultValue, out: &mut String) {
match default {
DefaultValue::Bool(value) => out.push_str(dialect.bool_literal(*value)),
DefaultValue::Int(value) => out.push_str(&value.to_string()),
DefaultValue::Real(value) => out.push_str(&value.to_string()),
DefaultValue::Text(value) => dialect.escape_string_literal(value, out),
DefaultValue::CurrentTimestamp => out.push_str("CURRENT_TIMESTAMP"),
DefaultValue::Null => out.push_str("NULL"),
DefaultValue::Uuid => match dialect.kind() {
DialectKind::Postgres => out.push_str("gen_random_uuid()"),
DialectKind::Sqlite => out.push_str("(lower(hex(randomblob(16))))"),
DialectKind::Mysql => out.push_str("UUID()"),
},
DefaultValue::Raw(sql) => out.push_str(sql),
}
}
fn render_foreign_key(dialect: &dyn Dialect, fk: &ForeignKeySpec, out: &mut String) {
out.push_str("FOREIGN KEY (");
render_identifier_list(dialect, &fk.columns, out);
out.push_str(") REFERENCES ");
dialect.quote_identifier(&fk.ref_table, out);
out.push_str(" (");
render_identifier_list(dialect, &fk.ref_columns, out);
out.push(')');
if let Some(action) = fk.on_delete.as_sql() {
out.push_str(" ON DELETE ");
out.push_str(action);
}
if let Some(action) = fk.on_update.as_sql() {
out.push_str(" ON UPDATE ");
out.push_str(action);
}
}
fn render_identifier_list(dialect: &dyn Dialect, names: &[String], out: &mut String) {
for (index, name) in names.iter().enumerate() {
if index != 0 {
out.push_str(", ");
}
dialect.quote_identifier(name, out);
}
}