use anyhow::{Context, Result};
use sqlx::postgres::PgConnection;
use sqlx::postgres::types::Oid;
use tracing::info;
use super::exclusion::{Converted, Excluded, ExclusionReason, is_system_schema};
use super::oid_index::OidIndex;
use super::shared::{SharedCatalog, class};
use crate::catalog::collation::{Collation, CollationProvider};
use crate::catalog::id::DbObjectId;
#[derive(Debug, Clone)]
pub struct RawCollation {
pub oid: Oid,
pub namespace: Oid,
pub name: String,
pub provider: String,
pub deterministic: bool,
pub collcollate: Option<String>,
pub collctype: Option<String>,
pub colllocale: Option<String>,
pub colliculocale: Option<String>,
pub collicurules: Option<String>,
}
pub async fn fetch(conn: &mut PgConnection) -> Result<Vec<RawCollation>> {
info!("Fetching collations...");
let rows = sqlx::query!(
r#"
SELECT
c.oid AS "oid!",
c.collnamespace AS "namespace!",
c.collname AS "name!",
c.collprovider::text AS "provider!",
c.collisdeterministic AS "deterministic!",
to_jsonb(c)->>'collcollate' AS "collcollate?",
to_jsonb(c)->>'collctype' AS "collctype?",
to_jsonb(c)->>'colllocale' AS "colllocale?",
to_jsonb(c)->>'colliculocale' AS "colliculocale?",
to_jsonb(c)->>'collicurules' AS "collicurules?"
FROM pg_collation c
ORDER BY c.oid
"#
)
.fetch_all(&mut *conn)
.await?;
Ok(rows
.into_iter()
.map(|row| RawCollation {
oid: row.oid,
namespace: row.namespace,
name: row.name,
provider: row.provider,
deterministic: row.deterministic,
collcollate: row.collcollate,
collctype: row.collctype,
colllocale: row.colllocale,
colliculocale: row.colliculocale,
collicurules: row.collicurules,
})
.collect())
}
#[allow(dead_code)]
pub async fn load(conn: &mut PgConnection, shared: &SharedCatalog) -> Result<Vec<Collation>> {
Ok(load_with_exclusions(conn, shared)
.await?
.log_and_take_objects("collation"))
}
pub async fn load_with_exclusions(
conn: &mut PgConnection,
shared: &SharedCatalog,
) -> Result<Converted<Collation>> {
let raw = fetch(conn).await?;
let mut converted = convert(&raw, shared)?;
let index = OidIndex::from_pairs(
class::PG_COLLATION,
converted
.objects
.iter()
.map(|(oid, collation)| (*oid, collation.id())),
)?;
let comments = index.object_comments(&shared.descriptions, class::PG_COLLATION);
for (_, collation) in &mut converted.objects {
collation.comment = comments.get(&collation.id()).map(|text| text.to_string());
}
converted.index = index;
Ok(converted.map(|(_, collation)| collation))
}
pub fn convert(
raw: &[RawCollation],
shared: &SharedCatalog,
) -> Result<Converted<(Oid, Collation)>> {
let namespaces = &shared.namespaces;
let mut converted: Converted<(Oid, Collation)> = Converted::new();
for row in raw {
let schema = namespaces
.name(row.namespace)
.with_context(|| format!("collation {} has no namespace entry", row.name))?;
if is_system_schema(schema) {
converted.excluded.push(Excluded::new(
row.oid,
"collation",
schema,
&row.name,
ExclusionReason::SystemSchema,
));
continue;
}
if let Some(extension) = shared.extensions.owner(class::PG_COLLATION, row.oid) {
converted.excluded.push(Excluded::new(
row.oid,
"collation",
schema,
&row.name,
ExclusionReason::ExtensionOwned {
extension: extension.to_string(),
},
));
continue;
}
let provider = match row.provider.as_str() {
"c" => CollationProvider::Libc,
"i" => CollationProvider::Icu,
"b" => CollationProvider::Builtin,
other => anyhow::bail!(
"unexpected collation provider {:?} for collation {}.{}",
other,
schema,
row.name
),
};
let locale = || {
row.colllocale
.clone()
.or_else(|| row.colliculocale.clone())
.or_else(|| row.collcollate.clone())
};
let (locale, lc_collate, lc_ctype, rules) = match provider {
CollationProvider::Libc => (None, row.collcollate.clone(), row.collctype.clone(), None),
CollationProvider::Icu => (locale(), None, None, row.collicurules.clone()),
CollationProvider::Builtin => (locale(), None, None, None),
};
converted.objects.push((
row.oid,
Collation {
schema: schema.to_string(),
name: row.name.clone(),
provider,
deterministic: row.deterministic,
locale,
lc_collate,
lc_ctype,
rules,
comment: None,
depends_on: vec![DbObjectId::Schema {
name: schema.to_string(),
}],
},
));
}
converted
.objects
.sort_by(|(_, a), (_, b)| (&a.schema, &a.name).cmp(&(&b.schema, &b.name)));
Ok(converted)
}