use super::helpers::acl::acl_identifier;
use super::helpers::oids::{split_schema_name, stable_object_oid};
use super::helpers::rows::{bool_value, catalog_array, catalog_usize, int_value, row, str_value};
use super::helpers::views::view_columns_for;
use super::partitioning::partition_bound_node;
use super::pg_catalog::{
catalog_index_relations, index_access_method_oid, pg_class_catalog_row, pg_class_row,
pg_class_row_with_lifecycle, table_relation_oid_from, table_rowtype_oid_from,
};
use super::{Engine, ResultRow, SQLError};
use crate::engine_capabilities::{CatalogReadView, RelationNameResolution};
#[expect(
clippy::too_many_lines,
reason = "preserves catalog column and OID order"
)]
pub(super) fn build_pg_class(
engine: &Engine,
catalog: &CatalogReadView,
resolution: &RelationNameResolution,
) -> Result<Vec<ResultRow>, SQLError> {
let mut out = vec![pg_class_catalog_row(
13_313,
13_315,
"information_schema",
"information_schema_catalog_name",
"v",
1,
-1.0,
false,
)];
let catalog_indexes = catalog_index_relations(catalog, resolution)?;
for name in catalog.table_names() {
let (schema, table) = split_schema_name(&name)?;
let table_snapshot = catalog
.table(resolution, &name)?
.ok_or_else(|| SQLError::UnknownTable(name.clone()))?;
let columns = &table_snapshot.columns;
let hierarchy = &table_snapshot.hierarchy;
let relkind = if hierarchy.partition_spec.is_some() {
"p"
} else {
"r"
};
let tuples = if hierarchy.partition_spec.is_some() {
let mut total = 0_u64;
for member in catalog.hierarchy_scan_tables(resolution, &name, true)? {
total = total
.checked_add(engine.table_doc_count(&member)?)
.ok_or_else(|| {
SQLError::Internal(format!(
"pg_class.reltuples overflow for hierarchy `{name}`"
))
})?;
}
total
} else {
engine.table_doc_count(&name)?
};
let mut row = pg_class_row_with_lifecycle(
&schema,
&table,
relkind,
catalog_usize(columns.len(), "pg_class column count")?,
tuples as f64,
catalog_indexes.iter().any(|index| index.table_name == name),
table_snapshot.persistence,
true,
&[],
);
row.insert(
"oid".into(),
int_value(table_relation_oid_from(catalog, resolution, &name)?),
);
row.insert(
"reltype".into(),
int_value(table_rowtype_oid_from(catalog, resolution, &name)?),
);
row.insert(
"relowner".into(),
int_value(crate::engine_roles::role_oid(
&table_snapshot.security.role_owner,
)),
);
row.insert(
"relacl".into(),
table_acl_catalog_value(
&table_snapshot.security.role_owner,
table_snapshot.security.acl.as_ref(),
)?,
);
row.insert(
"relispartition".into(),
bool_value(hierarchy.partition_bound.is_some()),
);
row.insert(
"relhassubclass".into(),
bool_value(
!catalog
.direct_hierarchy_children(resolution, &name)?
.is_empty(),
),
);
row.insert(
"relhastriggers".into(),
bool_value(catalog.relation_has_triggers(resolution, &name)?),
);
row.insert(
"relhasrules".into(),
bool_value(catalog.table_has_rules(resolution, &name)?),
);
if let Some(bound) = hierarchy.partition_bound.as_ref() {
row.insert(
"relpartbound".into(),
str_value(partition_bound_node(
engine, catalog, resolution, &name, bound,
)?),
);
}
out.push(row);
}
for (name, definition) in catalog.views_of_kind(crate::StoredViewKind::View) {
let (schema, view) = split_schema_name(&name)?;
let columns = view_columns_for(engine, catalog, resolution, &definition)?;
let mut row = pg_class_row_with_lifecycle(
&schema,
&view,
"v",
catalog_usize(columns.len(), "pg_class view column count")?,
0.0,
false,
definition.persistence,
true,
&definition.options,
);
row.insert(
"oid".into(),
int_value(super::view_relation_oid(&definition)),
);
row.insert(
"reltype".into(),
int_value(super::view_rowtype_oid(&definition)),
);
row.insert(
"relhastriggers".into(),
bool_value(catalog.relation_has_triggers(resolution, &name)?),
);
row.insert(
"relowner".into(),
int_value(crate::engine_roles::role_oid(&definition.role_owner)),
);
row.insert(
"relacl".into(),
table_acl_catalog_value(&definition.role_owner, definition.acl.as_ref())?,
);
out.push(row);
}
for (name, definition) in catalog.views_of_kind(crate::StoredViewKind::Materialized) {
let (schema, view) = split_schema_name(&name)?;
let columns = view_columns_for(engine, catalog, resolution, &definition)?;
let mut row = pg_class_row_with_lifecycle(
&schema,
&view,
"m",
catalog_usize(columns.len(), "pg_class materialized-view column count")?,
definition.materialized_rows.len() as f64,
false,
definition.persistence,
definition.populated,
&definition.options,
);
row.insert(
"oid".into(),
int_value(super::view_relation_oid(&definition)),
);
row.insert(
"reltype".into(),
int_value(super::view_rowtype_oid(&definition)),
);
row.insert(
"relowner".into(),
int_value(crate::engine_roles::role_oid(&definition.role_owner)),
);
row.insert(
"relacl".into(),
table_acl_catalog_value(&definition.role_owner, definition.acl.as_ref())?,
);
out.push(row);
}
for (name, foreign_table) in catalog.foreign_tables() {
let (schema, table) = split_schema_name(&name)?;
let security = catalog.foreign_table_security(&name)?;
let mut row = pg_class_row(
&schema,
&table,
"f",
catalog_usize(
foreign_table.columns.len(),
"pg_class foreign-table column count",
)?,
0.0,
false,
);
row.insert(
"oid".into(),
int_value(super::foreign_table_relation_oid(&foreign_table)),
);
row.insert(
"reltype".into(),
int_value(super::foreign_table_rowtype_oid(&foreign_table)),
);
row.insert(
"relowner".into(),
int_value(crate::engine_roles::role_oid(&security.role_owner)),
);
row.insert(
"relacl".into(),
table_acl_catalog_value(&security.role_owner, security.acl.as_ref())?,
);
row.insert(
"relhastriggers".into(),
bool_value(catalog.relation_has_triggers(resolution, &name)?),
);
out.push(row);
}
for (sequence, persistence, object_id, security) in catalog.sequences() {
let (schema, name) = split_schema_name(&sequence)?;
let mut row =
pg_class_row_with_lifecycle(&schema, &name, "S", 3, 0.0, false, persistence, true, &[]);
row.insert(
"oid".into(),
int_value(stable_object_oid("relation", &object_id)),
);
row.insert(
"relowner".into(),
int_value(crate::engine_roles::role_oid(&security.role_owner)),
);
row.insert("relacl".into(), sequence_acl_catalog_value(&security)?);
out.push(row);
}
for index in catalog_indexes {
let mut index_row = pg_class_row(
&index.relation.schema,
&index.relation.name,
index.relkind,
catalog_usize(
index.columns.len() + index.definition.included_columns.len(),
"pg_class index column count",
)?,
0.0,
false,
);
index_row.insert(
"relam".into(),
int_value(index_access_method_oid(&index.index_type)),
);
let table = catalog
.table(resolution, &index.table_name)?
.ok_or_else(|| SQLError::UnknownTable(index.table_name.clone()))?;
index_row.insert(
"relowner".into(),
int_value(crate::engine_roles::role_oid(&table.security.role_owner)),
);
index_row.insert("relispartition".into(), bool_value(index.is_partition));
index_row.insert("relhassubclass".into(), bool_value(index.has_children));
out.push(index_row);
}
out.extend(super::ag_catalog::age_pg_class_rows(catalog)?);
Ok(out)
}
pub(in crate::sql::catalog) fn table_acl_catalog_value(
owner: &str,
acl: Option<&Vec<uqa_storage::TableAclEntry>>,
) -> Result<uqa_core::Value, SQLError> {
let Some(acl) = acl else {
return Ok(uqa_core::Value::Null);
};
catalog_array(
acl.iter()
.map(|entry| {
let grantee = if entry.role == "PUBLIC" {
String::new()
} else {
acl_identifier(&entry.role)
};
let grantor = acl_identifier(entry.grantor.as_deref().unwrap_or(owner));
let mut privileges = String::new();
for (enabled, grant_option, code) in [
(entry.privileges.insert, entry.grant_options.insert, 'a'),
(entry.privileges.select, entry.grant_options.select, 'r'),
(entry.privileges.update, entry.grant_options.update, 'w'),
(entry.privileges.delete, entry.grant_options.delete, 'd'),
(entry.privileges.truncate, entry.grant_options.truncate, 'D'),
(
entry.privileges.references,
entry.grant_options.references,
'x',
),
(entry.privileges.trigger, entry.grant_options.trigger, 't'),
(entry.privileges.maintain, entry.grant_options.maintain, 'm'),
] {
if enabled {
privileges.push(code);
if grant_option {
privileges.push('*');
}
}
}
str_value(format!("{grantee}={privileges}/{grantor}"))
})
.collect(),
"pg_class.relacl",
)
}
fn sequence_acl_catalog_value(
security: &crate::engine_state::SequenceSecurity,
) -> Result<uqa_core::Value, SQLError> {
let Some(acl) = security.acl.as_ref() else {
return Ok(uqa_core::Value::Null);
};
catalog_array(
acl.iter()
.map(|entry| {
let grantee = if entry.role == "PUBLIC" {
String::new()
} else {
acl_identifier(&entry.role)
};
let grantor =
acl_identifier(entry.grantor.as_deref().unwrap_or(&security.role_owner));
let mut privileges = String::new();
for (enabled, grant_option, code) in [
(entry.privileges.select, entry.grant_options.select, 'r'),
(entry.privileges.update, entry.grant_options.update, 'w'),
(entry.privileges.usage, entry.grant_options.usage, 'U'),
] {
if enabled {
privileges.push(code);
if grant_option {
privileges.push('*');
}
}
}
str_value(format!("{grantee}={privileges}/{grantor}"))
})
.collect(),
"pg_class.relacl",
)
}
pub(super) fn build_pg_inherits(
catalog: &CatalogReadView,
resolution: &RelationNameResolution,
) -> Result<Vec<ResultRow>, SQLError> {
let mut out = Vec::new();
for child in catalog.table_names() {
let hierarchy = &catalog
.table(resolution, &child)?
.ok_or_else(|| SQLError::UnknownTable(child.clone()))?
.hierarchy;
for (position, parent) in hierarchy.parents.iter().enumerate() {
out.push(row([
(
"inhrelid",
int_value(table_relation_oid_from(catalog, resolution, &child)?),
),
(
"inhparent",
int_value(table_relation_oid_from(catalog, resolution, parent)?),
),
(
"inhseqno",
int_value(i64::from(hierarchy.parent_sequence_number(position))),
),
("inhdetachpending", bool_value(false)),
]));
}
}
for index in catalog_index_relations(catalog, resolution)? {
if let Some(parent_oid) = index.parent_index_oid {
out.push(row([
("inhrelid", int_value(index.oid())),
("inhparent", int_value(parent_oid)),
("inhseqno", int_value(1)),
("inhdetachpending", bool_value(false)),
]));
}
}
Ok(out)
}