2025-11-01 00:17:07 +00:00
use std ::fmt ::Display ;
use sqlx ::{ Encode , Postgres , postgres ::types ::Oid , query_as , query_as_unchecked } ;
2025-05-26 22:08:21 -07:00
2025-09-14 16:19:44 -04:00
use crate ::{
client ::WorkspaceClient , escape_identifier , pg_acl ::PgAclItem , pg_namespace ::PgNamespace ,
} ;
2025-05-26 22:08:21 -07:00
2025-08-04 13:59:42 -07:00
#[ derive(Clone, Debug) ]
2025-05-26 22:08:21 -07:00
pub struct PgClass {
/// Row identifier
pub oid : Oid ,
/// Name of the table, index, view, etc.
pub relname : String ,
/// The OID of the namespace that contains this relation
pub relnamespace : Oid ,
2025-07-18 16:20:03 -07:00
/// SYNTHESIZED: name of this relation's namespace as text
pub regnamespace : String ,
2025-05-26 22:08:21 -07:00
/// The OID of the data type that corresponds to this table's row type, if any; zero for indexes, sequences, and toast tables, which have no pg_type entry
pub reltype : Oid ,
/// For typed tables, the OID of the underlying composite type; zero for all other relations
pub reloftype : Oid ,
/// Owner of the relation
pub relowner : Oid ,
2025-08-09 00:14:58 -07:00
/// SYNTHESIZED: name of this relation's owner role as text
pub regowner : String ,
2025-05-26 22:08:21 -07:00
/// r = ordinary table, i = index, S = sequence, t = TOAST table, v = view, m = materialized view, c = composite type, f = foreign table, p = partitioned table, I = partitioned index
pub relkind : i8 ,
/// Number of user columns in the relation (system columns not counted). There must be this many corresponding entries in pg_attribute. See also pg_attribute.attnum.
pub relnatts : i16 ,
/// Number of CHECK constraints on the table; see pg_constraint catalog
pub relchecks : i16 ,
/// True if table has (or once had) rules; see pg_rewrite catalog
pub relhasrules : bool ,
/// True if table has (or once had) triggers; see pg_trigger catalog
pub relhastriggers : bool ,
/// True if table or index has (or once had) any inheritance children or partitions
pub relhassubclass : bool ,
/// True if table has row-level security enabled; see pg_policy catalog
pub relrowsecurity : bool ,
/// True if row-level security (when enabled) will also apply to table owner; see pg_policy catalog
pub relforcerowsecurity : bool ,
/// True if relation is populated (this is true for all relations other than some materialized views)
pub relispopulated : bool ,
/// True if table or index is a partition
pub relispartition : bool ,
pub relacl : Option < Vec < PgAclItem > > ,
}
impl PgClass {
2025-08-04 13:59:42 -07:00
pub async fn fetch_namespace (
& self ,
2025-09-14 16:19:44 -04:00
client : & mut WorkspaceClient ,
2025-08-04 13:59:42 -07:00
) -> Result < PgNamespace , sqlx ::Error > {
2025-11-19 01:31:09 +00:00
PgNamespace ::fetch_by_oid ( self . relnamespace , client . get_conn ( ) )
2025-08-04 13:59:42 -07:00
. await ?
// If client has access to the class, it would expect to have access
// to the namespace that contains it. If not, that's an error.
. ok_or ( sqlx ::Error ::RowNotFound )
}
/// Get ecaped identifier, including namespace.
pub fn get_identifier ( & self ) -> String {
format! (
" {0}.{1} " ,
escape_identifier ( & self . regnamespace ) ,
escape_identifier ( & self . relname )
)
}
2025-11-01 00:17:07 +00:00
2025-08-04 13:59:42 -07:00
pub fn with_oid ( oid : Oid ) -> WithOidQuery {
WithOidQuery { oid }
}
pub fn with_kind_in < I : IntoIterator < Item = PgRelKind > > ( kinds : I ) -> WithKindInQuery {
WithKindInQuery {
kinds : kinds . into_iter ( ) . collect ( ) ,
}
}
2025-11-01 00:17:07 +00:00
pub fn belonging_to_namespace <
T : Clone + Copy + Display + sqlx ::Type < Postgres > + for < ' a > Encode < ' a , Postgres > ,
> (
namespace : T ,
) -> BelongingToNamespaceQuery < T > {
BelongingToNamespaceQuery { namespace }
}
}
#[ derive(Clone, Copy, Debug) ]
pub struct BelongingToNamespaceQuery <
T : Clone + Copy + Display + sqlx ::Type < Postgres > + for < ' a > Encode < ' a , Postgres > ,
> {
namespace : T ,
}
impl < T : Clone + Copy + Display + sqlx ::Type < Postgres > + for < ' a > Encode < ' a , Postgres > >
BelongingToNamespaceQuery < T >
{
pub async fn fetch_all ( self , client : & mut WorkspaceClient ) -> sqlx ::Result < Vec < PgClass > > {
// `query_as!()` rightly complains that there may not be a built-in type
// mapping for `T` to `regnamespace`.
// TODO: Figure out whether it's possible to add a trait bound that
// ensures the mapping exists.
query_as_unchecked! (
PgClass ,
r #"
select
oid ,
relname ,
relnamespace ,
relnamespace ::regnamespace ::text as " regnamespace! " ,
reltype ,
reloftype ,
relowner ,
relowner ::regrole ::text as " regowner! " ,
relkind ,
relnatts ,
relchecks ,
relhasrules ,
relhastriggers ,
relhassubclass ,
relrowsecurity ,
relforcerowsecurity ,
relispopulated ,
relispartition ,
relacl ::text [ ] as " relacl: Vec<PgAclItem> "
from pg_class
where
relnamespace = $ 1 ::regnamespace ::oid
" #,
self . namespace ,
)
. fetch_all ( client . get_conn ( ) )
. await
}
2025-08-04 13:59:42 -07:00
}
pub struct WithOidQuery {
oid : Oid ,
}
// Extracted as macro so that fetch_one() and fetch_optional() methods can
// reuse the same code.
macro_rules ! with_oid_sqlx_query {
( $value :expr ) = > {
2025-05-28 16:35:00 -07:00
query_as! (
2025-08-04 13:59:42 -07:00
PgClass ,
2025-05-28 16:35:00 -07:00
r #"
select
oid ,
relname ,
relnamespace ,
2025-07-18 16:20:03 -07:00
relnamespace ::regnamespace ::text as " regnamespace! " ,
2025-05-28 16:35:00 -07:00
reltype ,
reloftype ,
relowner ,
2025-08-09 00:14:58 -07:00
relowner ::regrole ::text as " regowner! " ,
2025-05-28 16:35:00 -07:00
relkind ,
relnatts ,
relchecks ,
relhasrules ,
relhastriggers ,
relhassubclass ,
relrowsecurity ,
relforcerowsecurity ,
relispopulated ,
relispartition ,
relacl ::text [ ] as " relacl: Vec<PgAclItem> "
from pg_class
where
oid = $ 1
" #,
2025-08-04 13:59:42 -07:00
$value ,
2025-05-28 16:35:00 -07:00
)
2025-08-04 13:59:42 -07:00
} ;
}
impl WithOidQuery {
2025-09-14 16:19:44 -04:00
pub async fn fetch_one ( self , client : & mut WorkspaceClient ) -> Result < PgClass , sqlx ::Error > {
2025-08-04 13:59:42 -07:00
with_oid_sqlx_query! ( self . oid )
2025-11-19 01:31:09 +00:00
. fetch_one ( client . get_conn ( ) )
2025-08-04 13:59:42 -07:00
. await
}
pub async fn fetch_optional (
self ,
2025-09-14 16:19:44 -04:00
client : & mut WorkspaceClient ,
2025-08-04 13:59:42 -07:00
) -> Result < Option < PgClass > , sqlx ::Error > {
with_oid_sqlx_query! ( self . oid )
2025-11-19 01:31:09 +00:00
. fetch_optional ( client . get_conn ( ) )
2025-08-04 13:59:42 -07:00
. await
2025-05-28 16:35:00 -07:00
}
2025-08-04 13:59:42 -07:00
}
pub struct WithKindInQuery {
kinds : Vec < PgRelKind > ,
}
2025-05-28 16:35:00 -07:00
2025-08-04 13:59:42 -07:00
impl WithKindInQuery {
2025-09-14 16:19:44 -04:00
pub async fn fetch_all (
self ,
client : & mut WorkspaceClient ,
) -> Result < Vec < PgClass > , sqlx ::Error > {
2025-08-04 13:59:42 -07:00
let kinds_i8 : Vec < _ > = self
. kinds
2025-05-26 22:08:21 -07:00
. into_iter ( )
. map ( | kind | kind . to_u8 ( ) as i8 )
2025-08-04 13:59:42 -07:00
. collect ( ) ;
2025-05-26 22:08:21 -07:00
query_as! (
2025-08-04 13:59:42 -07:00
PgClass ,
2025-05-26 22:08:21 -07:00
r #"
select
oid ,
relname ,
relnamespace ,
2025-07-18 16:20:03 -07:00
relnamespace ::regnamespace ::text as " regnamespace! " ,
2025-05-26 22:08:21 -07:00
reltype ,
reloftype ,
relowner ,
2025-08-09 00:14:58 -07:00
relowner ::regrole ::text as " regowner! " ,
2025-05-26 22:08:21 -07:00
relkind ,
relnatts ,
relchecks ,
relhasrules ,
relhastriggers ,
relhassubclass ,
relrowsecurity ,
relforcerowsecurity ,
relispopulated ,
relispartition ,
relacl ::text [ ] as " relacl: Vec<PgAclItem> "
from pg_class
where
relkind = any ( $ 1 )
" #,
kinds_i8 . as_slice ( ) ,
)
2025-11-19 01:31:09 +00:00
. fetch_all ( client . get_conn ( ) )
2025-05-26 22:08:21 -07:00
. await
}
}
pub enum PgRelKind {
OrdinaryTable ,
Index ,
Sequence ,
ToastTable ,
View ,
MaterializedView ,
CompositeType ,
ForeignTable ,
PartitionedTable ,
PartitionedIndex ,
}
impl PgRelKind {
pub fn to_u8 ( & self ) -> u8 {
let ch = match self {
Self ::OrdinaryTable = > 'r' ,
Self ::Index = > 'i' ,
Self ::Sequence = > 'S' ,
Self ::ToastTable = > 't' ,
Self ::View = > 'v' ,
Self ::MaterializedView = > 'm' ,
Self ::CompositeType = > 'c' ,
Self ::ForeignTable = > 'f' ,
Self ::PartitionedTable = > 'p' ,
Self ::PartitionedIndex = > 'I' ,
} ;
ch as u8
}
}