use super::exclusion::sql;
const COLUMNS: [&str; 5] = ["type", "schema", "name", "tbl", "args"];
pub struct Branch {
kind: &'static str,
from: String,
schema: String,
name: String,
table: String,
args: String,
predicates: Vec<String>,
}
impl Branch {
fn new(kind: &'static str, from: &str) -> Self {
Self {
kind,
from: from.to_string(),
schema: "NULL".to_string(),
name: "NULL".to_string(),
table: "NULL".to_string(),
args: "NULL".to_string(),
predicates: Vec::new(),
}
}
fn schema(mut self, expr: &str) -> Self {
self.schema = expr.to_string();
self
}
fn name(mut self, expr: &str) -> Self {
self.name = expr.to_string();
self
}
fn table(mut self, expr: &str) -> Self {
self.table = expr.to_string();
self
}
fn args(mut self, expr: &str) -> Self {
self.args = expr.to_string();
self
}
fn filter(mut self, predicate: impl Into<String>) -> Self {
self.predicates.push(predicate.into());
self
}
fn sql(&self) -> String {
let projections: Vec<String> = [
format!("'{}'", self.kind),
self.schema.clone(),
self.name.clone(),
self.table.clone(),
self.args.clone(),
]
.iter()
.zip(COLUMNS)
.map(|(expr, column)| format!(" ({expr})::text AS \"{column}\""))
.collect();
let mut sql = format!("SELECT\n{}\nFROM {}", projections.join(",\n"), self.from);
if !self.predicates.is_empty() {
let conditions: Vec<String> = self
.predicates
.iter()
.map(|predicate| format!("({predicate})"))
.collect();
sql.push_str(&format!("\nWHERE {}", conditions.join("\n AND ")));
}
sql
}
}
pub fn query() -> String {
branches()
.iter()
.map(Branch::sql)
.collect::<Vec<_>>()
.join("\n\nUNION ALL\n\n")
}
pub fn branches() -> Vec<Branch> {
vec![
Branch::new("schema", "pg_namespace n")
.name("n.nspname")
.filter(sql::not_a_system_namespace("n.nspname"))
.filter("n.nspname <> 'public'"),
Branch::new(
"table",
"pg_class c\n JOIN pg_namespace n ON c.relnamespace = n.oid",
)
.schema("n.nspname")
.name("c.relname")
.filter("c.relkind = 'r'")
.filter(sql::not_a_system_namespace("n.nspname"))
.filter(sql::not_extension_owned("pg_class", "c.oid")),
Branch::new(
"view",
"pg_class c\n JOIN pg_namespace n ON c.relnamespace = n.oid",
)
.schema("n.nspname")
.name("c.relname")
.filter("c.relkind = 'v'")
.filter(sql::not_a_system_namespace("n.nspname"))
.filter(sql::not_extension_owned("pg_class", "c.oid")),
Branch::new(
"sequence",
"pg_class c\n JOIN pg_namespace n ON c.relnamespace = n.oid",
)
.schema("n.nspname")
.name("c.relname")
.filter("c.relkind = 'S'")
.filter(sql::not_a_system_namespace("n.nspname"))
.filter(sql::not_extension_owned("pg_class", "c.oid"))
.filter(sql::not_an_identity_sequence("c.oid")),
Branch::new(
"index",
"pg_index idx\n \
JOIN pg_class i ON idx.indexrelid = i.oid\n \
JOIN pg_namespace n ON i.relnamespace = n.oid\n \
JOIN pg_class t ON idx.indrelid = t.oid\n \
JOIN pg_namespace tn ON t.relnamespace = tn.oid",
)
.schema("n.nspname")
.name("i.relname")
.filter(sql::not_a_system_namespace("n.nspname"))
.filter(sql::not_a_system_namespace("tn.nspname"))
.filter(sql::not_extension_owned("pg_class", "i.oid"))
.filter(sql::parent_relation_not_extension_owned("t.oid"))
.filter(sql::not_a_constraint_backing_index("i.oid")),
Branch::new(
"function",
"pg_proc p\n JOIN pg_namespace n ON p.pronamespace = n.oid",
)
.schema("n.nspname")
.name("p.proname")
.args("pg_catalog.pg_get_function_identity_arguments(p.oid)")
.filter("p.prokind NOT IN ('a', 'p')")
.filter(sql::not_a_system_namespace("n.nspname"))
.filter(sql::not_extension_owned("pg_proc", "p.oid")),
Branch::new(
"procedure",
"pg_proc p\n JOIN pg_namespace n ON p.pronamespace = n.oid",
)
.schema("n.nspname")
.name("p.proname")
.args("pg_catalog.pg_get_function_identity_arguments(p.oid)")
.filter("p.prokind = 'p'")
.filter(sql::not_a_system_namespace("n.nspname"))
.filter(sql::not_extension_owned("pg_proc", "p.oid")),
Branch::new(
"aggregate",
"pg_proc p\n JOIN pg_namespace n ON p.pronamespace = n.oid",
)
.schema("n.nspname")
.name("p.proname")
.args("pg_catalog.pg_get_function_identity_arguments(p.oid)")
.filter("p.prokind = 'a'")
.filter(sql::not_a_system_namespace("n.nspname"))
.filter(sql::not_extension_owned("pg_proc", "p.oid")),
Branch::new(
"type",
"pg_type t\n JOIN pg_namespace n ON t.typnamespace = n.oid",
)
.schema("n.nspname")
.name("t.typname")
.filter("t.typtype IN ('e', 'c', 'r')")
.filter(
"NOT EXISTS (\n SELECT 1 FROM pg_class c\n \
WHERE c.reltype = t.oid\n \
AND c.relkind != 'c'\n)",
)
.filter(sql::not_a_system_namespace("n.nspname"))
.filter(sql::not_extension_owned("pg_type", "t.oid")),
Branch::new(
"domain",
"pg_type t\n JOIN pg_namespace n ON t.typnamespace = n.oid",
)
.schema("n.nspname")
.name("t.typname")
.filter("t.typtype = 'd'")
.filter(sql::not_a_system_namespace("n.nspname"))
.filter(sql::not_extension_owned("pg_type", "t.oid")),
Branch::new(
"constraint",
"pg_constraint co\n \
JOIN pg_class cl ON co.conrelid = cl.oid\n \
JOIN pg_namespace n ON cl.relnamespace = n.oid",
)
.schema("n.nspname")
.name("co.conname")
.table("cl.relname")
.filter("cl.relkind = 'r'")
.filter("co.contype IN ('u', 'f', 'c', 'x')")
.filter(sql::not_a_system_namespace("n.nspname"))
.filter(sql::parent_relation_not_extension_owned("cl.oid")),
Branch::new(
"trigger",
"pg_trigger tg\n \
JOIN pg_class c ON tg.tgrelid = c.oid\n \
JOIN pg_namespace n ON c.relnamespace = n.oid",
)
.schema("n.nspname")
.name("tg.tgname")
.table("c.relname")
.filter("c.relkind IN ('r', 'v', 'm')")
.filter("NOT tg.tgisinternal")
.filter(sql::not_a_system_namespace("n.nspname"))
.filter(sql::parent_relation_not_extension_owned("c.oid")),
Branch::new(
"policy",
"pg_policy pol\n \
JOIN pg_class c ON pol.polrelid = c.oid\n \
JOIN pg_namespace n ON c.relnamespace = n.oid",
)
.schema("n.nspname")
.name("pol.polname")
.table("c.relname")
.filter(sql::not_a_system_namespace("n.nspname"))
.filter(sql::parent_relation_not_extension_owned("c.oid")),
Branch::new(
"operator",
"pg_operator o\n JOIN pg_namespace n ON o.oprnamespace = n.oid",
)
.schema("n.nspname")
.name("o.oprname")
.args(
"CASE WHEN o.oprleft = 0 THEN 'NONE' \
ELSE pg_catalog.format_type(o.oprleft, NULL) END\n \
|| ', '\n \
|| CASE WHEN o.oprright = 0 THEN 'NONE' \
ELSE pg_catalog.format_type(o.oprright, NULL) END",
)
.filter(sql::not_a_system_namespace("n.nspname"))
.filter(sql::not_extension_owned("pg_operator", "o.oid")),
Branch::new(
"cast",
"pg_cast ca\n \
JOIN pg_type st ON ca.castsource = st.oid\n \
JOIN pg_type ste ON ste.oid = COALESCE(NULLIF(st.typelem, 0), st.oid)\n \
JOIN pg_namespace stn ON ste.typnamespace = stn.oid\n \
JOIN pg_type tt ON ca.casttarget = tt.oid\n \
JOIN pg_type tte ON tte.oid = COALESCE(NULLIF(tt.typelem, 0), tt.oid)\n \
JOIN pg_namespace ttn ON tte.typnamespace = ttn.oid",
)
.name("pg_catalog.format_type(ca.castsource, NULL)")
.table("pg_catalog.format_type(ca.casttarget, NULL)")
.filter(format!(
"{} OR {}",
sql::not_a_system_namespace("stn.nspname"),
sql::not_a_system_namespace("ttn.nspname")
))
.filter(sql::not_extension_owned("pg_cast", "ca.oid")),
Branch::new("extension", "pg_extension e")
.name("e.extname")
.filter(sql::not_a_built_in_extension("e.extname")),
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_every_branch_projects_the_identity_columns() {
for branch in branches() {
let sql = branch.sql();
for column in COLUMNS {
assert!(
sql.contains(&format!("AS \"{column}\"")),
"branch {} does not project {column}:\n{sql}",
branch.kind
);
}
}
}
#[test]
fn test_query_unions_every_branch() {
let query = query();
assert_eq!(query.matches("UNION ALL").count(), branches().len() - 1);
for branch in branches() {
assert!(
query.contains(&format!("('{}')::text", branch.kind)),
"branch {} is missing from the query",
branch.kind
);
}
}
#[test]
fn test_exclusion_predicates_are_class_qualified() {
let query = query();
for fragment in query.split("dep.objid = ").skip(1) {
assert!(
fragment.contains("dep.classid = '"),
"an extension-ownership test is not class-qualified:\n{fragment}"
);
}
}
}