use super::ddl::{
CheckConstraint, Column, Enum, ForeignKey, Index, Policy, PostgresEntity, PrimaryKey, Role,
Schema, Sequence, Table, UniqueConstraint, View,
};
use crate::collection::EntityCollection;
use crate::traits::EntityKind;
use std::collections::HashMap;
impl EntityCollection<Schema> {
#[must_use]
pub fn one(&self, name: &str) -> Option<&Schema> {
self.entities.iter().find(|s| s.name == name)
}
}
impl EntityCollection<Enum> {
#[must_use]
pub fn one(&self, schema: &str, name: &str) -> Option<&Enum> {
self.entities
.iter()
.find(|e| e.schema == schema && e.name == name)
}
}
impl EntityCollection<Sequence> {
#[must_use]
pub fn one(&self, schema: &str, name: &str) -> Option<&Sequence> {
self.entities
.iter()
.find(|s| s.schema == schema && s.name == name)
}
}
impl EntityCollection<Role> {
#[must_use]
pub fn one(&self, name: &str) -> Option<&Role> {
self.entities.iter().find(|r| r.name == name)
}
}
impl EntityCollection<Policy> {
#[must_use]
pub fn one(&self, schema: &str, table: &str, name: &str) -> Option<&Policy> {
self.entities
.iter()
.find(|p| p.schema == schema && p.table == table && p.name == name)
}
#[must_use]
pub fn for_table(&self, schema: &str, table: &str) -> Vec<&Policy> {
self.entities
.iter()
.filter(|p| p.schema == schema && p.table == table)
.collect()
}
}
impl EntityCollection<Table> {
#[must_use]
pub fn one(&self, schema: &str, name: &str) -> Option<&Table> {
self.entities
.iter()
.find(|t| t.schema == schema && t.name == name)
}
}
impl EntityCollection<Column> {
#[must_use]
pub fn one(&self, schema: &str, table: &str, name: &str) -> Option<&Column> {
self.entities
.iter()
.find(|c| c.schema == schema && c.table == table && c.name == name)
}
#[must_use]
pub fn for_table(&self, schema: &str, table: &str) -> Vec<&Column> {
self.entities
.iter()
.filter(|c| c.schema == schema && c.table == table)
.collect()
}
}
impl EntityCollection<Index> {
#[must_use]
pub fn one(&self, schema: &str, name: &str) -> Option<&Index> {
self.entities
.iter()
.find(|i| i.schema == schema && i.name == name)
}
#[must_use]
pub fn for_table(&self, schema: &str, table: &str) -> Vec<&Index> {
self.entities
.iter()
.filter(|i| i.schema == schema && i.table == table)
.collect()
}
}
impl EntityCollection<ForeignKey> {
#[must_use]
pub fn one(&self, schema: &str, name: &str) -> Option<&ForeignKey> {
self.entities
.iter()
.find(|f| f.schema == schema && f.name == name)
}
#[must_use]
pub fn for_table(&self, schema: &str, table: &str) -> Vec<&ForeignKey> {
self.entities
.iter()
.filter(|f| f.schema == schema && f.table == table)
.collect()
}
}
impl EntityCollection<PrimaryKey> {
#[must_use]
pub fn one(&self, schema: &str, name: &str) -> Option<&PrimaryKey> {
self.entities
.iter()
.find(|p| p.schema == schema && p.name == name)
}
#[must_use]
pub fn for_table(&self, schema: &str, table: &str) -> Option<&PrimaryKey> {
self.entities
.iter()
.find(|p| p.schema == schema && p.table == table)
}
}
impl EntityCollection<UniqueConstraint> {
#[must_use]
pub fn one(&self, schema: &str, name: &str) -> Option<&UniqueConstraint> {
self.entities
.iter()
.find(|u| u.schema == schema && u.name == name)
}
#[must_use]
pub fn for_table(&self, schema: &str, table: &str) -> Vec<&UniqueConstraint> {
self.entities
.iter()
.filter(|u| u.schema == schema && u.table == table)
.collect()
}
}
impl EntityCollection<CheckConstraint> {
#[must_use]
pub fn one(&self, schema: &str, name: &str) -> Option<&CheckConstraint> {
self.entities
.iter()
.find(|c| c.schema == schema && c.name == name)
}
#[must_use]
pub fn for_table(&self, schema: &str, table: &str) -> Vec<&CheckConstraint> {
self.entities
.iter()
.filter(|c| c.schema == schema && c.table == table)
.collect()
}
}
impl EntityCollection<View> {
#[must_use]
pub fn one(&self, schema: &str, name: &str) -> Option<&View> {
self.entities
.iter()
.find(|v| v.schema == schema && v.name == name)
}
}
#[derive(Debug, Clone, Default)]
pub struct PostgresDDL {
pub schemas: EntityCollection<Schema>,
pub enums: EntityCollection<Enum>,
pub sequences: EntityCollection<Sequence>,
pub roles: EntityCollection<Role>,
pub policies: EntityCollection<Policy>,
pub tables: EntityCollection<Table>,
pub columns: EntityCollection<Column>,
pub indexes: EntityCollection<Index>,
pub fks: EntityCollection<ForeignKey>,
pub pks: EntityCollection<PrimaryKey>,
pub uniques: EntityCollection<UniqueConstraint>,
pub checks: EntityCollection<CheckConstraint>,
pub views: EntityCollection<View>,
}
impl PostgresDDL {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn from_entities(entities: Vec<PostgresEntity>) -> Self {
let mut ddl = Self::new();
for entity in entities {
ddl.push_entity(entity);
}
ddl
}
pub fn push_entity(&mut self, entity: PostgresEntity) {
match entity {
PostgresEntity::Schema(s) => self.schemas.push(s),
PostgresEntity::Enum(e) => self.enums.push(e),
PostgresEntity::Sequence(s) => self.sequences.push(s),
PostgresEntity::Role(r) => self.roles.push(r),
PostgresEntity::Policy(p) => self.policies.push(p),
PostgresEntity::Table(t) => self.tables.push(t),
PostgresEntity::Column(c) => self.columns.push(c),
PostgresEntity::Index(i) => self.indexes.push(i),
PostgresEntity::ForeignKey(f) => self.fks.push(f),
PostgresEntity::PrimaryKey(p) => self.pks.push(p),
PostgresEntity::UniqueConstraint(u) => self.uniques.push(u),
PostgresEntity::CheckConstraint(c) => self.checks.push(c),
PostgresEntity::View(v) => self.views.push(v),
PostgresEntity::Privilege(_) => {}
}
}
#[must_use]
pub fn to_entities(&self) -> Vec<PostgresEntity> {
let mut entities = Vec::new();
for e in self.schemas.list() {
entities.push(PostgresEntity::Schema(e.clone()));
}
for e in self.enums.list() {
entities.push(PostgresEntity::Enum(e.clone()));
}
for e in self.sequences.list() {
entities.push(PostgresEntity::Sequence(e.clone()));
}
for e in self.roles.list() {
entities.push(PostgresEntity::Role(e.clone()));
}
for e in self.tables.list() {
entities.push(PostgresEntity::Table(e.clone()));
}
for e in self.columns.list() {
entities.push(PostgresEntity::Column(e.clone()));
}
for e in self.indexes.list() {
entities.push(PostgresEntity::Index(e.clone()));
}
for e in self.fks.list() {
entities.push(PostgresEntity::ForeignKey(e.clone()));
}
for e in self.pks.list() {
entities.push(PostgresEntity::PrimaryKey(e.clone()));
}
for e in self.uniques.list() {
entities.push(PostgresEntity::UniqueConstraint(e.clone()));
}
for e in self.checks.list() {
entities.push(PostgresEntity::CheckConstraint(e.clone()));
}
for e in self.policies.list() {
entities.push(PostgresEntity::Policy(e.clone()));
}
for e in self.views.list() {
entities.push(PostgresEntity::View(e.clone()));
}
entities
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.tables.is_empty() && self.enums.is_empty() && self.views.is_empty()
}
}
pub use crate::traits::DiffType;
#[derive(Debug, Clone)]
pub struct EntityDiff {
pub diff_type: DiffType,
pub kind: EntityKind,
pub name: String,
pub changes: HashMap<String, (String, String)>,
pub left: Option<PostgresEntity>,
pub right: Option<PostgresEntity>,
}
fn diff_top_level_entities(left: &PostgresDDL, right: &PostgresDDL, diffs: &mut Vec<EntityDiff>) {
diff_entity_type(
left.schemas.list(),
right.schemas.list(),
|e| e.name.to_string(),
|e| PostgresEntity::Schema(e.clone()),
EntityKind::Schema,
diffs,
);
diff_entity_type(
left.enums.list(),
right.enums.list(),
|e| format!("{}.{}", e.schema, e.name),
|e| PostgresEntity::Enum(e.clone()),
EntityKind::Enum,
diffs,
);
diff_entity_type(
left.sequences.list(),
right.sequences.list(),
|e| format!("{}.{}", e.schema, e.name),
|e| PostgresEntity::Sequence(e.clone()),
EntityKind::Sequence,
diffs,
);
diff_entity_type(
left.roles.list(),
right.roles.list(),
|e| e.name.to_string(),
|e| PostgresEntity::Role(e.clone()),
EntityKind::Role,
diffs,
);
diff_entity_type(
left.tables.list(),
right.tables.list(),
|e| format!("{}.{}", e.schema, e.name),
|e| PostgresEntity::Table(e.clone()),
EntityKind::Table,
diffs,
);
diff_entity_type(
left.views.list(),
right.views.list(),
|e| format!("{}.{}", e.schema, e.name),
|e| PostgresEntity::View(e.clone()),
EntityKind::View,
diffs,
);
}
fn diff_table_entities(left: &PostgresDDL, right: &PostgresDDL, diffs: &mut Vec<EntityDiff>) {
diff_entity_type(
left.columns.list(),
right.columns.list(),
|e| format!("{}.{}.{}", e.schema, e.table, e.name),
|e| PostgresEntity::Column(e.clone()),
EntityKind::Column,
diffs,
);
diff_entity_type(
left.indexes.list(),
right.indexes.list(),
|e| format!("{}.{}", e.schema, e.name),
|e| PostgresEntity::Index(e.clone()),
EntityKind::Index,
diffs,
);
diff_entity_type(
left.fks.list(),
right.fks.list(),
|e| format!("{}.{}", e.schema, e.name),
|e| PostgresEntity::ForeignKey(e.clone()),
EntityKind::ForeignKey,
diffs,
);
diff_entity_type(
left.pks.list(),
right.pks.list(),
|e| format!("{}.{}", e.schema, e.name),
|e| PostgresEntity::PrimaryKey(e.clone()),
EntityKind::PrimaryKey,
diffs,
);
diff_entity_type(
left.uniques.list(),
right.uniques.list(),
|e| format!("{}.{}", e.schema, e.name),
|e| PostgresEntity::UniqueConstraint(e.clone()),
EntityKind::UniqueConstraint,
diffs,
);
diff_entity_type(
left.checks.list(),
right.checks.list(),
|e| format!("{}.{}", e.schema, e.name),
|e| PostgresEntity::CheckConstraint(e.clone()),
EntityKind::CheckConstraint,
diffs,
);
diff_entity_type(
left.policies.list(),
right.policies.list(),
|e| format!("{}.{}.{}", e.schema, e.table, e.name),
|e| PostgresEntity::Policy(e.clone()),
EntityKind::Policy,
diffs,
);
}
#[must_use]
pub fn diff_ddl(left: &PostgresDDL, right: &PostgresDDL) -> Vec<EntityDiff> {
let mut diffs = Vec::new();
diff_top_level_entities(left, right, &mut diffs);
diff_table_entities(left, right, &mut diffs);
diffs
}
fn diff_entity_type<T: Clone + PartialEq>(
left: &[T],
right: &[T],
key_fn: impl Fn(&T) -> String,
to_entity: impl Fn(&T) -> PostgresEntity,
kind: EntityKind,
diffs: &mut Vec<EntityDiff>,
) {
let left_map: HashMap<String, &T> = left.iter().map(|e| (key_fn(e), e)).collect();
let right_map: HashMap<String, &T> = right.iter().map(|e| (key_fn(e), e)).collect();
for (key, left_entity) in &left_map {
if !right_map.contains_key(key) {
diffs.push(EntityDiff {
diff_type: DiffType::Drop,
kind,
name: key.clone(),
changes: HashMap::new(),
left: Some(to_entity(left_entity)),
right: None,
});
}
}
for (key, right_entity) in &right_map {
if !left_map.contains_key(key) {
diffs.push(EntityDiff {
diff_type: DiffType::Create,
kind,
name: key.clone(),
changes: HashMap::new(),
left: None,
right: Some(to_entity(right_entity)),
});
}
}
for (key, left_entity) in &left_map {
if let Some(right_entity) = right_map.get(key)
&& *left_entity != *right_entity
{
diffs.push(EntityDiff {
diff_type: DiffType::Alter,
kind,
name: key.clone(),
changes: HashMap::new(), left: Some(to_entity(left_entity)),
right: Some(to_entity(right_entity)),
});
}
}
}