use anyhow::Result;
use sqlx::PgPool;
use std::collections::BTreeSet;
use crate::catalog::id::DbObjectId;
use crate::catalog::raw::snapshot;
#[derive(Debug, Clone)]
pub struct CatalogIdentity {
pub objects: BTreeSet<DbObjectId>,
}
impl CatalogIdentity {
pub async fn load(pool: &PgPool) -> Result<Self> {
Ok(Self {
objects: snapshot::fetch(pool)
.await?
.iter()
.filter_map(to_id)
.collect(),
})
}
pub async fn load_with_oids(pool: &PgPool) -> Result<Vec<(i64, DbObjectId)>> {
Ok(snapshot::fetch(pool)
.await?
.iter()
.filter_map(|row| to_id(row).map(|id| (row.oid, id)))
.collect())
}
}
pub async fn current_oid_mark(pool: &PgPool) -> Result<i64> {
snapshot::current_mark(pool).await
}
fn to_id(row: &snapshot::Row) -> Option<DbObjectId> {
Some(match row.kind.as_str() {
"schema" => DbObjectId::Schema {
name: row.name.clone(),
},
"table" => DbObjectId::Table {
schema: row.schema.clone().unwrap_or_default(),
name: row.name.clone(),
},
"view" => DbObjectId::View {
schema: row.schema.clone().unwrap_or_default(),
name: row.name.clone(),
},
"sequence" => DbObjectId::Sequence {
schema: row.schema.clone().unwrap_or_default(),
name: row.name.clone(),
},
"index" => DbObjectId::Index {
schema: row.schema.clone().unwrap_or_default(),
name: row.name.clone(),
},
"function" => DbObjectId::Function {
schema: row.schema.clone().unwrap_or_default(),
name: row.name.clone(),
arguments: row.args.clone().unwrap_or_default(),
},
"procedure" => DbObjectId::Procedure {
schema: row.schema.clone().unwrap_or_default(),
name: row.name.clone(),
arguments: row.args.clone().unwrap_or_default(),
},
"aggregate" => DbObjectId::Aggregate {
schema: row.schema.clone().unwrap_or_default(),
name: row.name.clone(),
arguments: row.args.clone().unwrap_or_default(),
},
"type" => DbObjectId::Type {
schema: row.schema.clone().unwrap_or_default(),
name: row.name.clone(),
},
"domain" => DbObjectId::Domain {
schema: row.schema.clone().unwrap_or_default(),
name: row.name.clone(),
},
"collation" => DbObjectId::Collation {
schema: row.schema.clone().unwrap_or_default(),
name: row.name.clone(),
},
"constraint" => DbObjectId::Constraint {
schema: row.schema.clone().unwrap_or_default(),
table: row.table.clone().unwrap_or_default(),
name: row.name.clone(),
},
"trigger" => DbObjectId::Trigger {
schema: row.schema.clone().unwrap_or_default(),
table: row.table.clone().unwrap_or_default(),
name: row.name.clone(),
},
"policy" => DbObjectId::Policy {
schema: row.schema.clone().unwrap_or_default(),
table: row.table.clone().unwrap_or_default(),
name: row.name.clone(),
},
"operator" => DbObjectId::Operator {
schema: row.schema.clone().unwrap_or_default(),
name: row.name.clone(),
arguments: row.args.clone().unwrap_or_default(),
},
"cast" => DbObjectId::Cast {
source: row.name.clone(),
target: row.table.clone().unwrap_or_default(),
},
"extension" => DbObjectId::Extension {
name: row.name.clone(),
},
other => {
tracing::warn!("Unknown object type in identity query: {}", other);
return None;
}
})
}
pub fn find_new_objects(old: &CatalogIdentity, new: &CatalogIdentity) -> Vec<DbObjectId> {
new.objects.difference(&old.objects).cloned().collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_find_new_objects_empty() {
let old = CatalogIdentity {
objects: BTreeSet::new(),
};
let new = CatalogIdentity {
objects: BTreeSet::new(),
};
let result = find_new_objects(&old, &new);
assert!(result.is_empty());
}
#[test]
fn test_find_new_objects_detects_additions() {
let old = CatalogIdentity {
objects: BTreeSet::from([DbObjectId::Schema {
name: "existing".to_string(),
}]),
};
let new = CatalogIdentity {
objects: BTreeSet::from([
DbObjectId::Schema {
name: "existing".to_string(),
},
DbObjectId::Table {
schema: "existing".to_string(),
name: "new_table".to_string(),
},
]),
};
let result = find_new_objects(&old, &new);
assert_eq!(result.len(), 1);
assert_eq!(
result[0],
DbObjectId::Table {
schema: "existing".to_string(),
name: "new_table".to_string(),
}
);
}
}