use crate::catalog::id::DbObjectId;
pub fn is_system_schema(schema: &str) -> bool {
matches!(schema, "pg_catalog" | "information_schema" | "pg_toast")
|| schema.starts_with("pg_temp_")
}
pub fn resolve_type_dependency(
type_schema: Option<&str>,
type_name: Option<&str>,
typtype: Option<&str>,
relkind: Option<&str>,
is_extension: bool,
extension_name: Option<&str>,
) -> Option<DbObjectId> {
if is_extension {
return extension_name.map(|n| DbObjectId::Extension {
name: n.to_string(),
});
}
let (schema, name) = match (type_schema, type_name) {
(Some(s), Some(n)) if !is_system_schema(s) => (s.to_string(), n.to_string()),
_ => return None,
};
Some(match typtype {
Some("d") => DbObjectId::Domain { schema, name },
Some("c") => {
match relkind {
Some("r") | Some("p") => DbObjectId::Table { schema, name },
Some("v") | Some("m") => DbObjectId::View { schema, name },
_ => DbObjectId::Type { schema, name },
}
}
_ => DbObjectId::Type { schema, name },
})
}
pub struct DependencyBuilder {
deps: Vec<DbObjectId>,
}
impl DependencyBuilder {
pub fn new(schema: String) -> Self {
Self {
deps: vec![DbObjectId::Schema { name: schema }],
}
}
#[allow(dead_code)] pub fn add_custom_type(&mut self, type_schema: Option<String>, type_name: Option<String>) {
if let (Some(schema), Some(name)) = (type_schema, type_name)
&& !is_system_schema(&schema)
{
self.deps.push(DbObjectId::Type { schema, name });
}
}
#[allow(dead_code)] pub fn add_type_or_extension(
&mut self,
type_schema: Option<String>,
type_name: Option<String>,
is_extension: bool,
extension_name: Option<String>,
) {
if is_extension {
if let Some(ext_name) = extension_name {
self.deps.push(DbObjectId::Extension { name: ext_name });
}
} else if let (Some(schema), Some(name)) = (type_schema, type_name)
&& !is_system_schema(&schema)
{
self.deps.push(DbObjectId::Type { schema, name });
}
}
pub fn add_type_dependency(
&mut self,
type_schema: Option<String>,
type_name: Option<String>,
typtype: Option<String>,
relkind: Option<String>,
is_extension: bool,
extension_name: Option<String>,
) {
if let Some(dep) = resolve_type_dependency(
type_schema.as_deref(),
type_name.as_deref(),
typtype.as_deref(),
relkind.as_deref(),
is_extension,
extension_name.as_deref(),
) {
self.deps.push(dep);
}
}
pub fn build(self) -> Vec<DbObjectId> {
self.deps
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_system_schema() {
assert!(is_system_schema("pg_catalog"));
assert!(is_system_schema("information_schema"));
assert!(is_system_schema("pg_toast"));
assert!(is_system_schema("pg_temp_1234"));
assert!(!is_system_schema("public"));
assert!(!is_system_schema("my_schema"));
assert!(!is_system_schema("pg_something"));
}
#[test]
fn test_dependency_builder() {
let mut builder = DependencyBuilder::new("test_schema".to_string());
builder.add_custom_type(
Some("custom_schema".to_string()),
Some("my_type".to_string()),
);
builder.add_custom_type(Some("pg_catalog".to_string()), Some("text".to_string()));
let deps = builder.build();
assert_eq!(deps.len(), 2);
assert_eq!(
deps[0],
DbObjectId::Schema {
name: "test_schema".to_string()
}
);
assert_eq!(
deps[1],
DbObjectId::Type {
schema: "custom_schema".to_string(),
name: "my_type".to_string()
}
);
}
#[test]
fn test_add_type_or_extension_with_extension() {
let mut builder = DependencyBuilder::new("test_schema".to_string());
builder.add_type_or_extension(
Some("public".to_string()),
Some("citext".to_string()),
true, Some("citext".to_string()),
);
let deps = builder.build();
assert_eq!(deps.len(), 2);
assert_eq!(
deps[1],
DbObjectId::Extension {
name: "citext".to_string()
}
);
}
#[test]
fn test_add_type_or_extension_with_custom_type() {
let mut builder = DependencyBuilder::new("test_schema".to_string());
builder.add_type_or_extension(
Some("app".to_string()),
Some("priority".to_string()),
false, None,
);
let deps = builder.build();
assert_eq!(deps.len(), 2);
assert_eq!(
deps[1],
DbObjectId::Type {
schema: "app".to_string(),
name: "priority".to_string()
}
);
}
#[test]
fn test_add_type_or_extension_preserves_type_name() {
let mut builder = DependencyBuilder::new("test_schema".to_string());
builder.add_type_or_extension(
Some("app".to_string()),
Some("priority".to_string()), false,
None,
);
let deps = builder.build();
assert_eq!(deps.len(), 2);
assert_eq!(
deps[1],
DbObjectId::Type {
schema: "app".to_string(),
name: "priority".to_string()
}
);
}
#[test]
fn test_add_type_or_extension_handles_underscore_prefixed_types() {
let mut builder = DependencyBuilder::new("test_schema".to_string());
builder.add_type_or_extension(
Some("app".to_string()),
Some("_internal_status".to_string()), false,
None,
);
let deps = builder.build();
assert_eq!(deps.len(), 2);
assert_eq!(
deps[1],
DbObjectId::Type {
schema: "app".to_string(),
name: "_internal_status".to_string() }
);
}
#[test]
fn test_add_type_dependency_with_domain() {
let mut builder = DependencyBuilder::new("test_schema".to_string());
builder.add_type_dependency(
Some("app".to_string()),
Some("positive_int".to_string()),
Some("d".to_string()), None, false,
None,
);
let deps = builder.build();
assert_eq!(deps.len(), 2);
assert_eq!(
deps[1],
DbObjectId::Domain {
schema: "app".to_string(),
name: "positive_int".to_string()
}
);
}
#[test]
fn test_add_type_dependency_with_enum() {
let mut builder = DependencyBuilder::new("test_schema".to_string());
builder.add_type_dependency(
Some("app".to_string()),
Some("status".to_string()),
Some("e".to_string()), None, false,
None,
);
let deps = builder.build();
assert_eq!(deps.len(), 2);
assert_eq!(
deps[1],
DbObjectId::Type {
schema: "app".to_string(),
name: "status".to_string()
}
);
}
#[test]
fn test_add_type_dependency_with_extension() {
let mut builder = DependencyBuilder::new("test_schema".to_string());
builder.add_type_dependency(
Some("public".to_string()),
Some("citext".to_string()),
Some("d".to_string()), None, true,
Some("citext".to_string()),
);
let deps = builder.build();
assert_eq!(deps.len(), 2);
assert_eq!(
deps[1],
DbObjectId::Extension {
name: "citext".to_string()
}
);
}
#[test]
fn test_add_type_dependency_with_table_composite() {
let mut builder = DependencyBuilder::new("test_schema".to_string());
builder.add_type_dependency(
Some("app".to_string()),
Some("policies".to_string()),
Some("c".to_string()), Some("r".to_string()), false,
None,
);
let deps = builder.build();
assert_eq!(deps.len(), 2);
assert_eq!(
deps[1],
DbObjectId::Table {
schema: "app".to_string(),
name: "policies".to_string()
}
);
}
#[test]
fn test_add_type_dependency_with_view_composite() {
let mut builder = DependencyBuilder::new("test_schema".to_string());
builder.add_type_dependency(
Some("app".to_string()),
Some("policy_view".to_string()),
Some("c".to_string()), Some("v".to_string()), false,
None,
);
let deps = builder.build();
assert_eq!(deps.len(), 2);
assert_eq!(
deps[1],
DbObjectId::View {
schema: "app".to_string(),
name: "policy_view".to_string()
}
);
}
#[test]
fn test_add_type_dependency_with_explicit_composite() {
let mut builder = DependencyBuilder::new("test_schema".to_string());
builder.add_type_dependency(
Some("app".to_string()),
Some("address".to_string()),
Some("c".to_string()), None, false,
None,
);
let deps = builder.build();
assert_eq!(deps.len(), 2);
assert_eq!(
deps[1],
DbObjectId::Type {
schema: "app".to_string(),
name: "address".to_string()
}
);
}
#[test]
fn test_resolve_type_dependency_extension() {
let result = resolve_type_dependency(
Some("public"),
Some("citext"),
Some("d"), None,
true,
Some("citext"),
);
assert_eq!(
result,
Some(DbObjectId::Extension {
name: "citext".to_string()
})
);
}
#[test]
fn test_resolve_type_dependency_domain() {
let result = resolve_type_dependency(
Some("app"),
Some("positive_int"),
Some("d"),
None,
false,
None,
);
assert_eq!(
result,
Some(DbObjectId::Domain {
schema: "app".to_string(),
name: "positive_int".to_string()
})
);
}
#[test]
fn test_resolve_type_dependency_table_composite() {
let result = resolve_type_dependency(
Some("app"),
Some("orders"),
Some("c"),
Some("r"), false,
None,
);
assert_eq!(
result,
Some(DbObjectId::Table {
schema: "app".to_string(),
name: "orders".to_string()
})
);
}
#[test]
fn test_resolve_type_dependency_partitioned_table_composite() {
let result = resolve_type_dependency(
Some("app"),
Some("events"),
Some("c"),
Some("p"), false,
None,
);
assert_eq!(
result,
Some(DbObjectId::Table {
schema: "app".to_string(),
name: "events".to_string()
})
);
}
#[test]
fn test_resolve_type_dependency_view_composite() {
let result = resolve_type_dependency(
Some("app"),
Some("order_summary"),
Some("c"),
Some("v"), false,
None,
);
assert_eq!(
result,
Some(DbObjectId::View {
schema: "app".to_string(),
name: "order_summary".to_string()
})
);
}
#[test]
fn test_resolve_type_dependency_materialized_view_composite() {
let result = resolve_type_dependency(
Some("app"),
Some("cached_stats"),
Some("c"),
Some("m"), false,
None,
);
assert_eq!(
result,
Some(DbObjectId::View {
schema: "app".to_string(),
name: "cached_stats".to_string()
})
);
}
#[test]
fn test_resolve_type_dependency_explicit_composite() {
let result = resolve_type_dependency(
Some("app"),
Some("address"),
Some("c"),
None, false,
None,
);
assert_eq!(
result,
Some(DbObjectId::Type {
schema: "app".to_string(),
name: "address".to_string()
})
);
}
#[test]
fn test_resolve_type_dependency_enum() {
let result =
resolve_type_dependency(Some("app"), Some("status"), Some("e"), None, false, None);
assert_eq!(
result,
Some(DbObjectId::Type {
schema: "app".to_string(),
name: "status".to_string()
})
);
}
#[test]
fn test_resolve_type_dependency_system_schema() {
let result = resolve_type_dependency(
Some("pg_catalog"),
Some("int4"),
Some("b"),
None,
false,
None,
);
assert_eq!(result, None);
}
#[test]
fn test_resolve_type_dependency_missing_info() {
assert_eq!(
resolve_type_dependency(None, Some("mytype"), Some("e"), None, false, None),
None
);
assert_eq!(
resolve_type_dependency(Some("app"), None, Some("e"), None, false, None),
None
);
}
}