use anyhow::{Context, Result};
use sqlx::postgres::PgConnection;
use sqlx::postgres::types::Oid;
use tracing::info;
use super::exclusion::{BUILT_IN_EXTENSIONS, Converted, Excluded, ExclusionReason};
use super::oid_index::OidIndex;
use super::shared::{SharedCatalog, class};
use crate::catalog::DependsOn;
use crate::catalog::extension::Extension;
use crate::catalog::id::DbObjectId;
#[derive(Debug, Clone)]
pub struct RawExtension {
pub oid: Oid,
pub name: String,
pub namespace: Oid,
pub version: String,
pub relocatable: bool,
}
pub async fn fetch(conn: &mut PgConnection) -> Result<Vec<RawExtension>> {
info!("Fetching extensions...");
let rows = sqlx::query!(
r#"
SELECT
e.oid AS "oid!",
e.extname AS "name!",
e.extnamespace AS "namespace!",
e.extversion AS "version!",
e.extrelocatable AS "relocatable!"
FROM pg_extension e
ORDER BY e.oid
"#
)
.fetch_all(&mut *conn)
.await?;
Ok(rows
.into_iter()
.map(|row| RawExtension {
oid: row.oid,
name: row.name,
namespace: row.namespace,
version: row.version,
relocatable: row.relocatable,
})
.collect())
}
#[allow(dead_code)]
pub async fn load(conn: &mut PgConnection, shared: &SharedCatalog) -> Result<Vec<Extension>> {
Ok(load_with_exclusions(conn, shared)
.await?
.log_and_take_objects("extension"))
}
pub async fn load_with_exclusions(
conn: &mut PgConnection,
shared: &SharedCatalog,
) -> Result<Converted<Extension>> {
let raw = fetch(conn).await?;
let mut converted = convert(&raw, shared)?;
let index = OidIndex::from_pairs(
class::PG_EXTENSION,
converted
.objects
.iter()
.map(|(oid, extension)| (*oid, extension.id())),
)?;
let comments = index.object_comments(&shared.descriptions, class::PG_EXTENSION);
for (_, extension) in &mut converted.objects {
extension.comment = comments.get(&extension.id()).map(|text| text.to_string());
}
converted.index = index;
Ok(converted.map(|(_, extension)| extension))
}
pub fn convert(
raw: &[RawExtension],
shared: &SharedCatalog,
) -> Result<Converted<(Oid, Extension)>> {
let mut converted: Converted<(Oid, Extension)> = Converted::new();
for row in raw {
let schema = shared
.namespaces
.name(row.namespace)
.with_context(|| format!("extension {} has no namespace entry", row.name))?;
if BUILT_IN_EXTENSIONS.contains(&row.name.as_str()) {
converted.excluded.push(Excluded::new(
row.oid,
"extension",
schema,
&row.name,
ExclusionReason::BuiltInExtension,
));
continue;
}
let depends_on = if schema == "public" {
Vec::new()
} else {
vec![DbObjectId::Schema {
name: schema.to_string(),
}]
};
converted.objects.push((
row.oid,
Extension {
name: row.name.clone(),
schema: schema.to_string(),
version: row.version.clone(),
relocatable: row.relocatable,
comment: None,
depends_on,
},
));
}
converted
.objects
.sort_by(|(_, a), (_, b)| a.name.cmp(&b.name));
Ok(converted)
}