use super::{CStr, TViewResult, pg_sys};
pub(super) struct ColumnRename {
pub(super) relation: *const pg_sys::RangeVar,
pub(super) old_name: String,
pub(super) new_name: String,
}
impl ColumnRename {
pub(super) fn resolve(self) -> Option<(pg_sys::Oid, String, String)> {
let relid = unsafe {
pg_sys::RangeVarGetRelidExtended(
self.relation,
pg_sys::NoLock.cast_signed(),
pg_sys::RVROption::RVR_MISSING_OK,
None,
std::ptr::null_mut(),
)
};
(relid != pg_sys::InvalidOid).then_some((relid, self.old_name, self.new_name))
}
}
pub(super) fn extension_installed() -> bool {
unsafe {
pg_sys::IsTransactionState()
&& pg_sys::get_extension_oid(c"pg_tviews".as_ptr(), true) != pg_sys::InvalidOid
}
}
pub(super) unsafe fn column_rename_of(pstmt: *const pg_sys::PlannedStmt) -> Option<ColumnRename> {
unsafe {
if pstmt.is_null() || (*pstmt).utilityStmt.is_null() {
return None;
}
let node = (*pstmt).utilityStmt;
if (*node).type_ != pg_sys::NodeTag::T_RenameStmt {
return None;
}
#[allow(clippy::cast_ptr_alignment)] let stmt = &*node.cast::<pg_sys::RenameStmt>();
if stmt.renameType != pg_sys::ObjectType::OBJECT_COLUMN
|| stmt.relation.is_null()
|| stmt.subname.is_null()
|| stmt.newname.is_null()
{
return None;
}
Some(ColumnRename {
relation: stmt.relation,
old_name: CStr::from_ptr(stmt.subname).to_string_lossy().into_owned(),
new_name: CStr::from_ptr(stmt.newname).to_string_lossy().into_owned(),
})
}
}
pub(super) struct ColumnChange {
pub(super) relation: *const pg_sys::RangeVar,
pub(super) removes: Option<&'static str>,
pub(super) retypes: Vec<(String, *const pg_sys::TypeName)>,
}
pub(super) unsafe fn column_change_of(node: *mut pg_sys::Node) -> Option<ColumnChange> {
unsafe {
#[allow(clippy::cast_ptr_alignment)] let change = match (*node).type_ {
pg_sys::NodeTag::T_RenameStmt => {
let stmt = &*node.cast::<pg_sys::RenameStmt>();
if stmt.renameType != pg_sys::ObjectType::OBJECT_COLUMN {
return None;
}
ColumnChange {
relation: stmt.relation,
removes: Some("RENAME COLUMN"),
retypes: Vec::new(),
}
}
pg_sys::NodeTag::T_AlterTableStmt => {
let stmt = &*node.cast::<pg_sys::AlterTableStmt>();
let mut change = ColumnChange {
relation: stmt.relation,
removes: None,
retypes: Vec::new(),
};
for i in 0..pg_sys::list_length(stmt.cmds) {
let cmd = pg_sys::list_nth(stmt.cmds, i).cast::<pg_sys::AlterTableCmd>();
if cmd.is_null() {
continue;
}
match (*cmd).subtype {
pg_sys::AlterTableType::AT_DropColumn => {
change.removes = Some("DROP COLUMN");
}
pg_sys::AlterTableType::AT_AlterColumnType
if !(*cmd).name.is_null() && !(*cmd).def.is_null() =>
{
let def = (*cmd).def.cast::<pg_sys::ColumnDef>();
change.retypes.push((
CStr::from_ptr((*cmd).name).to_string_lossy().into_owned(),
(*def).typeName,
));
}
_ => {}
}
}
change
}
_ => return None,
};
(!change.relation.is_null() && (change.removes.is_some() || !change.retypes.is_empty()))
.then_some(change)
}
}
pub(super) unsafe fn table_move_of(pstmt: *const pg_sys::PlannedStmt) -> Option<pg_sys::Oid> {
unsafe {
if pstmt.is_null() || (*pstmt).utilityStmt.is_null() {
return None;
}
let node = (*pstmt).utilityStmt;
#[allow(clippy::cast_ptr_alignment)] let relation = match (*node).type_ {
pg_sys::NodeTag::T_RenameStmt => {
let stmt = &*node.cast::<pg_sys::RenameStmt>();
(stmt.renameType == pg_sys::ObjectType::OBJECT_TABLE).then_some(stmt.relation)
}
pg_sys::NodeTag::T_AlterObjectSchemaStmt => {
let stmt = &*node.cast::<pg_sys::AlterObjectSchemaStmt>();
(stmt.objectType == pg_sys::ObjectType::OBJECT_TABLE).then_some(stmt.relation)
}
_ => None,
}?;
if relation.is_null() {
return None;
}
let relid = pg_sys::RangeVarGetRelidExtended(
relation,
pg_sys::NoLock.cast_signed(),
pg_sys::RVROption::RVR_MISSING_OK,
None,
std::ptr::null_mut(),
);
(relid != pg_sys::InvalidOid).then_some(relid)
}
}
pub(super) unsafe fn matview_refresh_of(pstmt: *const pg_sys::PlannedStmt) -> Option<pg_sys::Oid> {
unsafe {
if pstmt.is_null() || (*pstmt).utilityStmt.is_null() {
return None;
}
let node = (*pstmt).utilityStmt;
if (*node).type_ != pg_sys::NodeTag::T_RefreshMatViewStmt {
return None;
}
#[allow(clippy::cast_ptr_alignment)] let stmt = &*node.cast::<pg_sys::RefreshMatViewStmt>();
if stmt.skipData || stmt.relation.is_null() {
return None;
}
let relid = pg_sys::RangeVarGetRelidExtended(
stmt.relation,
pg_sys::NoLock.cast_signed(),
pg_sys::RVROption::RVR_MISSING_OK,
None,
std::ptr::null_mut(),
);
(relid != pg_sys::InvalidOid).then_some(relid)
}
}
pub(super) struct PrivilegesChange {
pub(super) owners: bool,
}
pub(super) unsafe fn privileges_change_of(
pstmt: *const pg_sys::PlannedStmt,
) -> Option<PrivilegesChange> {
unsafe {
if pstmt.is_null() || (*pstmt).utilityStmt.is_null() {
return None;
}
let node = (*pstmt).utilityStmt;
#[allow(clippy::cast_ptr_alignment)] match (*node).type_ {
pg_sys::NodeTag::T_GrantStmt => {
let stmt = &*node.cast::<pg_sys::GrantStmt>();
(stmt.objtype == pg_sys::ObjectType::OBJECT_TABLE)
.then_some(PrivilegesChange { owners: false })
}
pg_sys::NodeTag::T_AlterTableStmt => {
let stmt = &*node.cast::<pg_sys::AlterTableStmt>();
let cmds = stmt.cmds;
let changes_owner = stmt.objtype == pg_sys::ObjectType::OBJECT_TABLE
&& (0..pg_sys::list_length(cmds)).any(|i| {
let cmd = pg_sys::list_nth(cmds, i).cast::<pg_sys::AlterTableCmd>();
!cmd.is_null() && (*cmd).subtype == pg_sys::AlterTableType::AT_ChangeOwner
});
changes_owner.then_some(PrivilegesChange { owners: true })
}
pg_sys::NodeTag::T_ReassignOwnedStmt => Some(PrivilegesChange { owners: true }),
_ => None,
}
}
}
pub(super) struct PartitionDdl {
pub(super) tables: Vec<*mut pg_sys::RangeVar>,
pub(super) changed_rows_of: Option<*mut pg_sys::RangeVar>,
}
impl PartitionDdl {
pub(super) unsafe fn apply(self) -> TViewResult<()> {
crate::cache::invalidate_all();
for rv in self.tables {
let oid = unsafe { resolve_relation_oid(rv) };
if oid != pg_sys::InvalidOid {
crate::dependency::triggers::ensure_partition_triggers(oid)?;
}
}
let parent = self
.changed_rows_of
.map_or(pg_sys::InvalidOid, |rv| unsafe { resolve_relation_oid(rv) });
if parent != pg_sys::InvalidOid {
crate::delta::refresh_tviews_over(parent)?;
}
Ok(())
}
}
pub(super) unsafe fn partition_ddl_of(pstmt: *const pg_sys::PlannedStmt) -> Option<PartitionDdl> {
unsafe {
if pstmt.is_null() || (*pstmt).utilityStmt.is_null() {
return None;
}
let node = (*pstmt).utilityStmt;
let mut tables = Vec::new();
let mut changed_rows_of = None;
match (*node).type_ {
pg_sys::NodeTag::T_CreateStmt => {
#[allow(clippy::cast_ptr_alignment)] let stmt = &*node.cast::<pg_sys::CreateStmt>();
if !stmt.partbound.is_null() && !stmt.relation.is_null() {
tables.push(stmt.relation);
}
}
pg_sys::NodeTag::T_AlterTableStmt => {
#[allow(clippy::cast_ptr_alignment)]
let stmt = &*node.cast::<pg_sys::AlterTableStmt>();
for i in 0..pg_sys::list_length(stmt.cmds) {
let cmd = pg_sys::list_nth(stmt.cmds, i).cast::<pg_sys::AlterTableCmd>();
if cmd.is_null()
|| !matches!(
(*cmd).subtype,
pg_sys::AlterTableType::AT_AttachPartition
| pg_sys::AlterTableType::AT_DetachPartition
| pg_sys::AlterTableType::AT_DetachPartitionFinalize
)
|| (*cmd).def.is_null()
{
continue;
}
#[allow(clippy::cast_ptr_alignment)]
let partition = &*(*cmd).def.cast::<pg_sys::PartitionCmd>();
if !partition.name.is_null() {
tables.push(partition.name);
changed_rows_of = Some(stmt.relation);
}
}
}
_ => {}
}
(!tables.is_empty()).then_some(PartitionDdl {
tables,
changed_rows_of,
})
}
}
pub(super) unsafe fn extension_statement_names(node: *mut pg_sys::Node) -> Option<Vec<String>> {
unsafe {
let tag = (*node).type_;
if tag == pg_sys::NodeTag::T_CreateExtensionStmt {
#[allow(clippy::cast_ptr_alignment)]
let stmt = node.cast::<pg_sys::CreateExtensionStmt>();
let name = if (*stmt).extname.is_null() {
String::new()
} else {
CStr::from_ptr((*stmt).extname)
.to_string_lossy()
.into_owned()
};
return Some(vec![name]);
}
if tag == pg_sys::NodeTag::T_DropStmt {
#[allow(clippy::cast_ptr_alignment)] let stmt = node.cast::<pg_sys::DropStmt>();
if (*stmt).removeType != pg_sys::ObjectType::OBJECT_EXTENSION {
return None;
}
let mut names = Vec::new();
let objects = (*stmt).objects;
for i in 0..pg_sys::list_length(objects) {
let item = pg_sys::list_nth(objects, i).cast::<pg_sys::String>();
if !item.is_null() && !(*item).sval.is_null() {
names.push(CStr::from_ptr((*item).sval).to_string_lossy().into_owned());
}
}
return Some(names);
}
None
}
}
pub(super) unsafe fn drops_pg_tviews(pstmt: *const pg_sys::PlannedStmt) -> bool {
unsafe {
if pstmt.is_null() || (*pstmt).utilityStmt.is_null() {
return false;
}
let node = (*pstmt).utilityStmt;
(*node).type_ == pg_sys::NodeTag::T_DropStmt
&& extension_statement_names(node)
.is_some_and(|names| names.iter().any(|n| n == "pg_tviews"))
}
}
pub(super) unsafe fn resolve_relation_oid(rv: *const pg_sys::RangeVar) -> pg_sys::Oid {
if rv.is_null() {
return pg_sys::InvalidOid;
}
unsafe {
pg_sys::RangeVarGetRelidExtended(
rv,
pg_sys::NoLock.cast_signed(),
pg_sys::RVROption::RVR_MISSING_OK,
None,
std::ptr::null_mut(),
)
}
}