pub mod cluster;
pub mod error;
pub mod filter;
#[cfg(feature = "tokio-postgres-querier")]
pub mod pg_querier;
pub mod queries;
pub mod rows;
pub mod version;
pub use error::CatalogError;
pub use filter::CatalogFilter;
pub use rows::{Row, Value};
pub use version::PgVersion;
mod assemble;
pub(crate) mod collations;
pub(crate) mod grants;
pub(crate) mod publications;
pub(crate) mod reloptions;
pub(crate) mod statistics;
pub(crate) mod subscriptions;
use crate::identifier::{Identifier, QualifiedName};
use crate::ir::catalog::Catalog;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct DriftReport {
pub pending_validation: Vec<(QualifiedName, Identifier)>,
pub invalid_indexes: Vec<QualifiedName>,
pub unmanaged_language_routines: Vec<(QualifiedName, String)>,
pub unmanaged_aggregates: Vec<QualifiedName>,
pub unmanaged_casts: Vec<(QualifiedName, QualifiedName)>,
pub unreadable_subscriptions: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CatalogQuery {
PgVersion,
Schemas,
Tables,
Columns,
Constraints,
Indexes,
Sequences,
Comments,
Dependencies,
ViewsAndMvs,
ViewColumns,
UserTypes,
EnumValues,
DomainDetails,
DomainChecks,
CompositeAttributes,
Functions,
Aggregates,
Extensions,
Triggers,
PartitionedTables,
Partitions,
ClusterRoles,
ClusterMembers,
ClusterTablespaces,
DefaultPrivileges,
Policies,
Publications,
PublicationRel,
PublicationNamespace,
PublicationAttributes,
EventTriggers,
Subscriptions,
Statistics,
StatisticAttributes,
StatisticExpressions,
Collations,
Casts,
TsDictionaries,
TsConfigurations,
TsConfigMappings,
}
impl CatalogQuery {
#[must_use]
pub const fn takes_text_array_param(self) -> bool {
!matches!(
self,
Self::PgVersion
| Self::Extensions
| Self::DefaultPrivileges
| Self::Publications
| Self::PublicationRel
| Self::PublicationNamespace
| Self::PublicationAttributes
| Self::EventTriggers
| Self::Subscriptions
| Self::Casts
)
}
}
pub trait CatalogQuerier {
fn fetch(
&self,
query: CatalogQuery,
text_array_param: &[&str],
) -> Result<Vec<Row>, CatalogError>;
}
pub fn read_catalog(
querier: &dyn CatalogQuerier,
filter: &CatalogFilter,
) -> Result<(Catalog, DriftReport), CatalogError> {
let version = PgVersion::detect(querier)?;
let managed: Vec<&str> = filter.managed_schemas_param();
let schemas_rows = querier.fetch(CatalogQuery::Schemas, &managed)?;
let tables_rows = querier.fetch(CatalogQuery::Tables, &managed)?;
let columns_rows = querier.fetch(CatalogQuery::Columns, &managed)?;
let constraints_rows = querier.fetch(CatalogQuery::Constraints, &managed)?;
let indexes_rows = querier.fetch(CatalogQuery::Indexes, &managed)?;
let sequences_rows = querier.fetch(CatalogQuery::Sequences, &managed)?;
let dependencies_rows = querier.fetch(CatalogQuery::Dependencies, &managed)?;
let views_and_mvs_rows = querier.fetch(CatalogQuery::ViewsAndMvs, &managed)?;
let view_columns_rows = querier.fetch(CatalogQuery::ViewColumns, &managed)?;
let user_types_rows = querier.fetch(CatalogQuery::UserTypes, &managed)?;
let enum_values_rows = querier.fetch(CatalogQuery::EnumValues, &managed)?;
let domain_details_rows = querier.fetch(CatalogQuery::DomainDetails, &managed)?;
let domain_checks_rows = querier.fetch(CatalogQuery::DomainChecks, &managed)?;
let composite_attributes_rows = querier.fetch(CatalogQuery::CompositeAttributes, &managed)?;
let functions_rows = querier.fetch(CatalogQuery::Functions, &managed)?;
let aggregates_rows = querier.fetch(CatalogQuery::Aggregates, &managed)?;
let extensions_rows = querier.fetch(CatalogQuery::Extensions, &managed)?;
let triggers_rows = querier.fetch(CatalogQuery::Triggers, &managed)?;
let partitioned_tables_rows = querier.fetch(CatalogQuery::PartitionedTables, &managed)?;
let partitions_rows = querier.fetch(CatalogQuery::Partitions, &managed)?;
let default_privileges_rows = querier.fetch(CatalogQuery::DefaultPrivileges, &[])?;
let policies_rows = querier.fetch(CatalogQuery::Policies, &managed)?;
let publications_rows = querier.fetch(CatalogQuery::Publications, &[])?;
let publication_rels_rows = querier.fetch(CatalogQuery::PublicationRel, &[])?;
let publication_namespaces_rows = querier.fetch(CatalogQuery::PublicationNamespace, &[])?;
let publication_attributes_rows = querier.fetch(CatalogQuery::PublicationAttributes, &[])?;
let event_triggers_rows = querier.fetch(CatalogQuery::EventTriggers, &[])?;
let (subscriptions_rows, unreadable_subscriptions) =
match querier.fetch(CatalogQuery::Subscriptions, &[]) {
Ok(rows) => (rows, false),
Err(CatalogError::QueryFailed { message, .. })
if message.contains("42501") || message.contains("insufficient_privilege") =>
{
(vec![], true)
}
Err(e) => return Err(e),
};
let statistics_rows = querier.fetch(CatalogQuery::Statistics, &managed)?;
let statistic_attributes_rows = querier.fetch(CatalogQuery::StatisticAttributes, &managed)?;
let statistic_expressions_rows = querier.fetch(CatalogQuery::StatisticExpressions, &managed)?;
let collations_rows = querier.fetch(CatalogQuery::Collations, &managed)?;
let casts_rows = querier.fetch(CatalogQuery::Casts, &[])?;
let ts_dictionaries_rows = querier.fetch(CatalogQuery::TsDictionaries, &managed)?;
let ts_configurations_rows = querier.fetch(CatalogQuery::TsConfigurations, &managed)?;
let ts_config_mappings_rows = querier.fetch(CatalogQuery::TsConfigMappings, &managed)?;
let raw = assemble::RawRows {
version,
schemas: schemas_rows,
tables: tables_rows,
columns: columns_rows,
constraints: constraints_rows,
indexes: indexes_rows,
sequences: sequences_rows,
dependencies: dependencies_rows,
views_and_mvs: views_and_mvs_rows,
view_columns: view_columns_rows,
user_types: user_types_rows,
enum_values: enum_values_rows,
domain_details: domain_details_rows,
domain_checks: domain_checks_rows,
composite_attributes: composite_attributes_rows,
functions: functions_rows,
aggregates: aggregates_rows,
extensions: extensions_rows,
triggers: triggers_rows,
partitioned_tables: partitioned_tables_rows,
partitions: partitions_rows,
default_privileges: default_privileges_rows,
policies: policies_rows,
publications: publications_rows,
publication_rels: publication_rels_rows,
publication_namespaces: publication_namespaces_rows,
publication_attributes: publication_attributes_rows,
event_triggers: event_triggers_rows,
subscriptions: subscriptions_rows,
ts_dictionaries: ts_dictionaries_rows,
ts_configurations: ts_configurations_rows,
ts_config_mappings: ts_config_mappings_rows,
};
let (mut catalog, mut drift) = assemble::assemble(raw, filter)?;
drift.unreadable_subscriptions = unreadable_subscriptions;
catalog.statistics = assemble::statistics::assemble_statistics(
&statistics_rows,
&statistic_attributes_rows,
&statistic_expressions_rows,
)?;
catalog.collations = assemble::collations::build_collations(&collations_rows)?;
catalog.casts = assemble::casts::assemble_casts(&casts_rows, &mut drift)?;
Ok((catalog.canonicalize()?, drift))
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
use std::collections::HashMap;
struct MockQuerier {
rows: RefCell<HashMap<CatalogQuery, Vec<Row>>>,
}
impl MockQuerier {
fn new() -> Self {
Self {
rows: RefCell::new(HashMap::new()),
}
}
fn set(&self, q: CatalogQuery, rows: Vec<Row>) {
self.rows.borrow_mut().insert(q, rows);
}
}
impl CatalogQuerier for MockQuerier {
fn fetch(
&self,
q: CatalogQuery,
_text_array_param: &[&str],
) -> Result<Vec<Row>, CatalogError> {
Ok(self.rows.borrow().get(&q).cloned().unwrap_or_default())
}
}
#[test]
fn empty_catalog_round_trips() {
let m = MockQuerier::new();
m.set(
CatalogQuery::PgVersion,
vec![Row::new().with("server_version_num", Value::Integer(160_000))],
);
let filter = CatalogFilter::new(vec![], vec![]).unwrap();
let (cat, drift) = read_catalog(&m, &filter).expect("reads");
assert!(cat.tables.is_empty());
assert!(cat.schemas.is_empty());
assert!(drift.pending_validation.is_empty());
assert!(drift.invalid_indexes.is_empty());
}
}