fix(schema): include postgres column comments in ddl

This commit is contained in:
t8y2 committed 2026-05-17 15:20:52 +08:00
1 parent 153daf6c09
commit c00074dfdf
1 file changed
+73 -18
+73 -18
View File
@@ -829,6 +829,37 @@ mod object_source_tests {
} }
} }
#[cfg(test)]
mod ddl_tests {
use super::*;
fn column(name: &str, data_type: &str) -> db::ColumnInfo {
db::ColumnInfo {
name: name.to_string(),
data_type: data_type.to_string(),
is_nullable: true,
column_default: None,
is_primary_key: false,
extra: None,
comment: None,
numeric_precision: None,
numeric_scale: None,
character_maximum_length: None,
}
}
#[test]
fn postgres_table_ddl_includes_column_comments() {
let mut display_name = column("display_name", "text");
display_name.comment = Some("User's display name".to_string());
let columns = vec![display_name];
let ddl = render_postgres_table_ddl("public", "users", &columns, &[], &[]);
assert!(ddl.contains("COMMENT ON COLUMN \"public\".\"users\".\"display_name\" IS 'User''s display name';"));
}
}
pub async fn mysql_ddl(pool: &sqlx::mysql::MySqlPool, table: &str) -> Result<String, String> { pub async fn mysql_ddl(pool: &sqlx::mysql::MySqlPool, table: &str) -> Result<String, String> {
use sqlx::Row; use sqlx::Row;
let sql = format!("SHOW CREATE TABLE `{}`", table.replace('`', "``")); let sql = format!("SHOW CREATE TABLE `{}`", table.replace('`', "``"));
@@ -855,11 +886,22 @@ pub async fn pg_ddl(pool: &sqlx::postgres::PgPool, schema: &str, table: &str) ->
db::postgres::list_foreign_keys(pool, schema, table), db::postgres::list_foreign_keys(pool, schema, table),
)?; )?;
let mut ddl = format!("CREATE TABLE \"{schema}\".\"{table}\" (\n"); Ok(render_postgres_table_ddl(schema, table, &columns, &indexes, &fkeys))
}
fn render_postgres_table_ddl(
schema: &str,
table: &str,
columns: &[db::ColumnInfo],
indexes: &[db::IndexInfo],
fkeys: &[db::ForeignKeyInfo],
) -> String {
let table_name = format!("{}.{}", pg_ident(schema), pg_ident(table));
let mut ddl = format!("CREATE TABLE {table_name} (\n");
let col_lines: Vec<String> = columns let col_lines: Vec<String> = columns
.iter() .iter()
.map(|c| { .map(|c| {
let mut line = format!(" \"{}\" {}", c.name, c.data_type); let mut line = format!(" {} {}", pg_ident(&c.name), c.data_type);
if !c.is_nullable { if !c.is_nullable {
line.push_str(" NOT NULL"); line.push_str(" NOT NULL");
} }
@@ -873,44 +915,57 @@ pub async fn pg_ddl(pool: &sqlx::postgres::PgPool, schema: &str, table: &str) ->
let pks: Vec<&str> = columns.iter().filter(|c| c.is_primary_key).map(|c| c.name.as_str()).collect(); let pks: Vec<&str> = columns.iter().filter(|c| c.is_primary_key).map(|c| c.name.as_str()).collect();
if !pks.is_empty() { if !pks.is_empty() {
ddl.push_str(&format!( ddl.push_str(&format!(",\n PRIMARY KEY ({})", pks.iter().map(|k| pg_ident(k)).collect::<Vec<_>>().join(", ")));
",\n PRIMARY KEY ({})",
pks.iter().map(|k| format!("\"{k}\"")).collect::<Vec<_>>().join(", ")
));
} }
for fk in &fkeys { for fk in fkeys {
ddl.push_str(&format!( ddl.push_str(&format!(
",\n CONSTRAINT \"{}\" FOREIGN KEY (\"{}\") REFERENCES \"{}\"(\"{}\")", ",\n CONSTRAINT {} FOREIGN KEY ({}) REFERENCES {}({})",
fk.name, fk.column, fk.ref_table, fk.ref_column pg_ident(&fk.name),
pg_ident(&fk.column),
pg_ident(&fk.ref_table),
pg_ident(&fk.ref_column)
)); ));
} }
ddl.push_str("\n);\n"); ddl.push_str("\n);\n");
for idx in &indexes { for col in columns {
if let Some(comment) = col.comment.as_deref().filter(|comment| !comment.is_empty()) {
ddl.push_str(&format!(
"\nCOMMENT ON COLUMN {table_name}.{} IS {};",
pg_ident(&col.name),
sql_string(comment)
));
}
}
for idx in indexes {
if idx.is_primary { if idx.is_primary {
continue; continue;
} }
let unique = if idx.is_unique { "UNIQUE " } else { "" }; let unique = if idx.is_unique { "UNIQUE " } else { "" };
let cols = idx.columns.iter().map(|c| format!("\"{c}\"")).collect::<Vec<_>>().join(", "); let cols = idx.columns.iter().map(|c| pg_ident(c)).collect::<Vec<_>>().join(", ");
let using = idx.index_type.as_deref().map(|t| format!(" USING {t}")).unwrap_or_default(); let using = idx.index_type.as_deref().map(|t| format!(" USING {t}")).unwrap_or_default();
let include = idx let include = idx
.included_columns .included_columns
.as_deref() .as_deref()
.filter(|c| !c.is_empty()) .filter(|c| !c.is_empty())
.map(|cols| { .map(|cols| format!(" INCLUDE ({})", cols.iter().map(|c| pg_ident(c)).collect::<Vec<_>>().join(", ")))
format!(" INCLUDE ({})", cols.iter().map(|c| format!("\"{c}\"")).collect::<Vec<_>>().join(", "))
})
.unwrap_or_default(); .unwrap_or_default();
let filter = idx.filter.as_deref().map(|f| format!(" WHERE {f}")).unwrap_or_default(); let filter = idx.filter.as_deref().map(|f| format!(" WHERE {f}")).unwrap_or_default();
ddl.push_str(&format!( ddl.push_str(&format!(
"\nCREATE {unique}INDEX \"{}\" ON \"{schema}\".\"{table}\"{using} ({cols}){include}{filter};", "\nCREATE {unique}INDEX {} ON {table_name}{using} ({cols}){include}{filter};",
idx.name pg_ident(&idx.name)
)); ));
if let Some(ref c) = idx.comment { if let Some(ref c) = idx.comment {
ddl.push_str(&format!("\nCOMMENT ON INDEX \"{schema}\".\"{}\" IS '{}';", idx.name, c.replace('\'', "''"))); ddl.push_str(&format!(
"\nCOMMENT ON INDEX {}.{} IS {};",
pg_ident(schema),
pg_ident(&idx.name),
sql_string(c)
));
} }
} }
Ok(ddl) ddl
} }
pub async fn build_sqlserver_ddl( pub async fn build_sqlserver_ddl(