diesel/pg/
metadata_lookup.rs

1// FIXME: Remove this attribute once false positive is resolved.
2#![allow(unused_parens)]
3// conditionally allow deprecated items to allow using the
4// "deprecated" table! macro
5#![cfg_attr(
6    any(feature = "huge-tables", feature = "large-tables"),
7    allow(deprecated)
8)]
9
10use super::backend::{FailedToLookupTypeError, InnerPgTypeMetadata};
11use super::{Pg, PgTypeMetadata};
12use crate::connection::{DefaultLoadingMode, LoadConnection};
13use crate::prelude::*;
14
15use std::borrow::Cow;
16use std::collections::HashMap;
17
18/// Determines the OID of types at runtime
19///
20/// Custom implementations of `Connection<Backend = Pg>` should not implement this trait directly.
21/// Instead `GetPgMetadataCache` should be implemented, afterwards the generic implementation will provide
22/// the necessary functions to perform the type lookup.
23#[cfg(feature = "postgres_backend")]
24pub trait PgMetadataLookup {
25    /// Determine the type metadata for the given `type_name`
26    ///
27    /// This function should only be used for user defined types, or types which
28    /// come from an extension. This function may perform a SQL query to look
29    /// up the type. For built-in types, a static OID should be preferred.
30    fn lookup_type(&mut self, type_name: &str, schema: Option<&str>) -> PgTypeMetadata;
31
32    /// Convert this lookup instance to a `std::any::Any` pointer
33    ///
34    /// Implementing this method is required to support `#[derive(MultiConnection)]`
35    // We provide a default implementation here, so that adding this method is no breaking
36    // change
37    #[diesel_derives::__diesel_public_if(
38        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
39    )]
40    fn as_any<'a>(&mut self) -> &mut (dyn std::any::Any + 'a)
41    where
42        Self: 'a,
43    {
44        unimplemented!()
45    }
46}
47
48impl<T> PgMetadataLookup for T
49where
50    T: Connection<Backend = Pg> + GetPgMetadataCache + LoadConnection<DefaultLoadingMode>,
51{
52    fn lookup_type(&mut self, type_name: &str, schema: Option<&str>) -> PgTypeMetadata {
53        let cache_key = PgMetadataCacheKey {
54            schema: schema.map(Cow::Borrowed),
55            type_name: Cow::Borrowed(type_name),
56        };
57
58        {
59            let metadata_cache = self.get_metadata_cache();
60
61            if let Some(metadata) = metadata_cache.lookup_type(&cache_key) {
62                return metadata;
63            }
64        }
65
66        let r = lookup_type(&cache_key, self);
67
68        match r {
69            Ok(type_metadata) => {
70                self.get_metadata_cache()
71                    .store_type(cache_key, type_metadata);
72                PgTypeMetadata(Ok(type_metadata))
73            }
74            Err(_e) => PgTypeMetadata(Err(FailedToLookupTypeError::new_internal(
75                cache_key.into_owned(),
76            ))),
77        }
78    }
79
80    fn as_any<'a>(&mut self) -> &mut (dyn std::any::Any + 'a)
81    where
82        Self: 'a,
83    {
84        self
85    }
86}
87
88/// Gets the `PgMetadataCache` for a `Connection<Backend=Pg>`
89/// so that the lookup of user defined types, or types which come from an extension can be cached.
90///
91/// Implementing this trait for a `Connection<Backend=Pg>` will cause `PgMetadataLookup` to be auto implemented.
92#[cfg_attr(
93    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
94    cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")
95)]
96pub trait GetPgMetadataCache {
97    /// Get the `PgMetadataCache`
98    fn get_metadata_cache(&mut self) -> &mut PgMetadataCache;
99}
100
101fn lookup_type<T: Connection<Backend = Pg> + LoadConnection<DefaultLoadingMode>>(
102    cache_key: &PgMetadataCacheKey<'_>,
103    conn: &mut T,
104) -> QueryResult<InnerPgTypeMetadata> {
105    let metadata_query = pg_type::table.select((pg_type::oid, pg_type::typarray));
106
107    let metadata = if let Some(schema) = cache_key.schema.as_deref() {
108        // We have an explicit type name and schema given here
109        // that should resolve to an unique type
110        metadata_query
111            .inner_join(pg_namespace::table)
112            .filter(pg_type::typname.eq(&cache_key.type_name))
113            .filter(pg_namespace::nspname.eq(schema))
114            .first(conn)?
115    } else {
116        // We don't have a schema here. A type with the same name could exists
117        // in more than one schema at once, even in more than one schema that
118        // is in the current search path. As we don't want to reimplement
119        // postgres name resolution here we just cast the time name to a concrete type
120        // and let postgres figure out the name resolution on it's own.
121        metadata_query
122            .filter(
123                pg_type::oid.eq(crate::dsl::sql("quote_ident(")
124                    .bind::<crate::sql_types::Text, _>(&cache_key.type_name)
125                    .sql(")::regtype::oid")),
126            )
127            .first(conn)?
128    };
129
130    Ok(metadata)
131}
132
133/// The key used to lookup cached type oid's inside of
134/// a [PgMetadataCache].
135#[derive(Hash, PartialEq, Eq, Debug, Clone)]
136#[cfg_attr(
137    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
138    cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")
139)]
140pub struct PgMetadataCacheKey<'a> {
141    pub(in crate::pg) schema: Option<Cow<'a, str>>,
142    pub(in crate::pg) type_name: Cow<'a, str>,
143}
144
145impl<'a> PgMetadataCacheKey<'a> {
146    /// Construct a new cache key from an optional schema name and
147    /// a type name
148    pub fn new(schema: Option<Cow<'a, str>>, type_name: Cow<'a, str>) -> Self {
149        Self { schema, type_name }
150    }
151
152    /// Convert the possibly borrowed version of this metadata cache key
153    /// into a lifetime independent owned version
154    pub fn into_owned(self) -> PgMetadataCacheKey<'static> {
155        let PgMetadataCacheKey { schema, type_name } = self;
156        PgMetadataCacheKey {
157            schema: schema.map(|s| Cow::Owned(s.into_owned())),
158            type_name: Cow::Owned(type_name.into_owned()),
159        }
160    }
161}
162
163/// Cache for the [OIDs] of custom Postgres types
164///
165/// [OIDs]: https://www.postgresql.org/docs/current/static/datatype-oid.html
166#[allow(missing_debug_implementations)]
167#[derive(Default)]
168#[cfg_attr(
169    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
170    cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")
171)]
172pub struct PgMetadataCache {
173    cache: HashMap<PgMetadataCacheKey<'static>, InnerPgTypeMetadata>,
174}
175
176impl PgMetadataCache {
177    /// Construct a new `PgMetadataCache`
178    pub fn new() -> Self {
179        Default::default()
180    }
181
182    /// Lookup the OID of a custom type
183    pub fn lookup_type(&self, type_name: &PgMetadataCacheKey<'_>) -> Option<PgTypeMetadata> {
184        Some(PgTypeMetadata(Ok(*self.cache.get(type_name)?)))
185    }
186
187    /// Store the OID of a custom type
188    pub fn store_type(
189        &mut self,
190        type_name: PgMetadataCacheKey<'_>,
191        type_metadata: impl Into<InnerPgTypeMetadata>,
192    ) {
193        self.cache
194            .insert(type_name.into_owned(), type_metadata.into());
195    }
196}
197
198table! {
199    pg_type (oid) {
200        oid -> Oid,
201        typname -> Text,
202        typarray -> Oid,
203        typnamespace -> Oid,
204    }
205}
206
207table! {
208    pg_namespace (oid) {
209        oid -> Oid,
210        nspname -> Text,
211    }
212}
213
214joinable!(pg_type -> pg_namespace(typnamespace));
215allow_tables_to_appear_in_same_query!(pg_type, pg_namespace);
216
217#[declare_sql_function]
218extern "SQL" {
219    fn pg_my_temp_schema() -> Oid;
220}