#![allow(unused_parens)]
#![cfg_attr(
any(feature = "huge-tables", feature = "large-tables"),
allow(deprecated)
)]
use super::backend::{FailedToLookupTypeError, InnerPgTypeMetadata};
use super::{Pg, PgTypeMetadata};
use crate::connection::{DefaultLoadingMode, LoadConnection};
use crate::prelude::*;
use std::borrow::Cow;
use std::collections::HashMap;
#[cfg(feature = "postgres_backend")]
pub trait PgMetadataLookup {
fn lookup_type(&mut self, type_name: &str, schema: Option<&str>) -> PgTypeMetadata;
}
impl<T> PgMetadataLookup for T
where
T: Connection<Backend = Pg> + GetPgMetadataCache + LoadConnection<DefaultLoadingMode>,
{
fn lookup_type(&mut self, type_name: &str, schema: Option<&str>) -> PgTypeMetadata {
let cache_key = PgMetadataCacheKey {
schema: schema.map(Cow::Borrowed),
type_name: Cow::Borrowed(type_name),
};
{
let metadata_cache = self.get_metadata_cache();
if let Some(metadata) = metadata_cache.lookup_type(&cache_key) {
return metadata;
}
}
let r = lookup_type(&cache_key, self);
match r {
Ok(type_metadata) => {
self.get_metadata_cache()
.store_type(cache_key, type_metadata);
PgTypeMetadata(Ok(type_metadata))
}
Err(_e) => PgTypeMetadata(Err(FailedToLookupTypeError::new_internal(
cache_key.into_owned(),
))),
}
}
}
#[cfg_attr(
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")
)]
pub trait GetPgMetadataCache {
fn get_metadata_cache(&mut self) -> &mut PgMetadataCache;
}
fn lookup_type<T: Connection<Backend = Pg> + LoadConnection<DefaultLoadingMode>>(
cache_key: &PgMetadataCacheKey<'_>,
conn: &mut T,
) -> QueryResult<InnerPgTypeMetadata> {
let metadata_query = pg_type::table.select((pg_type::oid, pg_type::typarray));
let metadata = if let Some(schema) = cache_key.schema.as_deref() {
metadata_query
.inner_join(pg_namespace::table)
.filter(pg_type::typname.eq(&cache_key.type_name))
.filter(pg_namespace::nspname.eq(schema))
.first(conn)?
} else {
metadata_query
.filter(
pg_type::oid.eq(crate::dsl::sql("quote_ident(")
.bind::<crate::sql_types::Text, _>(&cache_key.type_name)
.sql(")::regtype::oid")),
)
.first(conn)?
};
Ok(metadata)
}
#[derive(Hash, PartialEq, Eq, Debug, Clone)]
#[cfg_attr(
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")
)]
pub struct PgMetadataCacheKey<'a> {
pub(in crate::pg) schema: Option<Cow<'a, str>>,
pub(in crate::pg) type_name: Cow<'a, str>,
}
impl<'a> PgMetadataCacheKey<'a> {
pub fn new(schema: Option<Cow<'a, str>>, type_name: Cow<'a, str>) -> Self {
Self { schema, type_name }
}
pub fn into_owned(self) -> PgMetadataCacheKey<'static> {
let PgMetadataCacheKey { schema, type_name } = self;
PgMetadataCacheKey {
schema: schema.map(|s| Cow::Owned(s.into_owned())),
type_name: Cow::Owned(type_name.into_owned()),
}
}
}
#[allow(missing_debug_implementations)]
#[derive(Default)]
#[cfg_attr(
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")
)]
pub struct PgMetadataCache {
cache: HashMap<PgMetadataCacheKey<'static>, InnerPgTypeMetadata>,
}
impl PgMetadataCache {
pub fn new() -> Self {
Default::default()
}
pub fn lookup_type(&self, type_name: &PgMetadataCacheKey<'_>) -> Option<PgTypeMetadata> {
Some(PgTypeMetadata(Ok(*self.cache.get(type_name)?)))
}
pub fn store_type(
&mut self,
type_name: PgMetadataCacheKey<'_>,
type_metadata: impl Into<InnerPgTypeMetadata>,
) {
self.cache
.insert(type_name.into_owned(), type_metadata.into());
}
}
table! {
pg_type (oid) {
oid -> Oid,
typname -> Text,
typarray -> Oid,
typnamespace -> Oid,
}
}
table! {
pg_namespace (oid) {
oid -> Oid,
nspname -> Text,
}
}
joinable!(pg_type -> pg_namespace(typnamespace));
allow_tables_to_appear_in_same_query!(pg_type, pg_namespace);
sql_function! { fn pg_my_temp_schema() -> Oid; }