Repository: diesel-rs/diesel (language: rust)

Issue:
Custom types with same name in different schemas don't work due to oid caching
When you have a custom type (e.g. using `diesel-derive-enum`) with the same name in different schemas, it doesn't work with diesel, it doesn't consider the search_path when looking up types.
Instead, diesel uses the first type that matches the name, regardless of the schema it's in.

It seems like this PR introduced this behavior: https://github.com/diesel-rs/diesel/pull/2088

Btw, it looks like jdbc had the same issue:
https://www.postgresql.org/message-id/flat/20110308183723.08A5B1337B93%40mail.postgresql.org


Discussion / hints:
Please not that this behaviour was not introduced in that PR, the query does just not check the corresponding search path.
I think it should be not that hard to fix issue. Basically we only need to add way to have a optional schema/search path for custom types. Otherwise public will be assumed as search patch. Fortunately the next release will be 2.0 so we are able to tweak the API a bit here.

Steps to implement this change:
* Change [`PgMetadataLookup::lookup_type`](https://github.com/diesel-rs/diesel/blob/8396134f1fb581529070de0b2a946f1c7d5f9e91/diesel/src/pg/metadata_lookup.rs#L25) to have a additional optional parameter for the schema name.
* Adjust [this](https://github.com/diesel-rs/diesel/blob/8396134f1fb581529070de0b2a946f1c7d5f9e91/diesel/src/pg/metadata_lookup.rs#L30-L34) query to use that optional schema name.
* Change [`PgMetadataCache`](https://github.com/diesel-rs/diesel/blob/8396134f1fb581529070de0b2a946f1c7d5f9e91/diesel/src/pg/metadata_lookup.rs#L44) to store also the schema name.
* Change the [`SqlType` derive](https://github.com/diesel-rs/diesel/blob/8396134f1fb581529070de0b2a946f1c7d5f9e91/diesel_derives/src/sql_type.rs#L90-L140) to have an optional schema parameter (basically like the type_name parameter, but optional) and pass it [here](https://github.com/diesel-rs/diesel/blob/8396134f1fb581529070de0b2a946f1c7d5f9e91/diesel_derives/src/sql_type.rs#L124) to the changed lookup function.
* Add a test case to [this file](https://github.com/diesel-rs/diesel/blob/8396134f1fb581529070de0b2a946f1c7d5f9e91/diesel_tests/tests/custom_types.rs)
> - Change the SqlType derive to have an optional schema parameter (basically like the type_name parameter, but optional) and pass it here to the changed lookup function.

@weiznich But wouldn't that hardcode the schema at compile-time, so that that custom type only works in that one schema? 
That wouldn't solve our problem, we are using the same enum type in multiple schemas that are created at runtime based on user input (but structurally the same for all tables).
So the set of schemas that the enum type will be used in is not fixed/known at compile-time.
It needs to do the lookup based on the current `search_path` at runtime!
If you have "the same" type in multiple schema they remain different types. You wouldn't consider the same rust type definitions in different modules to be resolved to the same type, right?
@weiznich It's static definition vs dynamic instances. Like having 1 struct and multiple instances at runtime. We have 1 "schema" (in the sense of a set of schemas for a set of tables) and multiple instances at runtime (multiple pg schemas). 
Being able to "mount" one of these instances to a set of static types (that represent this "schema") based on the `search_path` is a valid use case IMO. 
At least it's our use case (multi-tenant system with one schema instance per tenant).
Would it be possible to accommodate this use case in Diesel or do you think it'd be too disruptive / inefficient?
If you have the "same" types in different schemas that's only a interpretation added on top of the actual types. It's nothing we can infer (because matching by name/whatever will fail for constructed edge cases). That means it will be never something that will be supported by diesel out of the box in my opinion. That said it should be possible to support such an setup by implementing some things manually.
The probably easiest way to do that with the latest released diesel version is to just set the search schema dynamically by just calling `SELECT pg_catalog.set_config('search_path', 'your_schema', false);`.
One thing that's important to note is that getting the current search_path requires a round trip to the database, which defeats the purpose of caching the result in the first place. I'm not sure what the right answer is here, but I think there's some missing context here. Duplicating a type per-schema seems like an anti-pattern to me. If the type is the same for all schemas, why does it need to be schema specific in the first place?
@Boscop Can you clarify what you're asking for here? Is your issue that the query itself doesn't take the search path into account? Or that we cache the answer, but that may change if the search path changes?
So it seems like there are three issues here:

- We don't take the search path into account on lookup
- There's no way to specify an explicit schema
- If the result *were* based on the search path, the answer could change between calls

I believe the first two can be solved by changing the where clause on our query to `WHERE oid = $1::regtype::oid`. This would also resolve schemas for us (though we should make sure this still works when given an explicit schema that isn't in the search path). We can also just narrow the schema manually if the string contains a `.`, but that'd be more complicated and also break any types with a `.` in the name (which probably won't actually break any real code)

The third problem is something I consider to be extremely niche, but it seems like we could support it by providing a second lookup function which doesn't cache the answer. But if you can't tell us the schema you want us to look in, I don't see any way we could possibly support that without doing a query every time the lookup needs to be performed.

Fix patch:
diff --git a/diesel/src/connection/statement_cache.rs b/diesel/src/connection/statement_cache.rs
index 614105a579a6..218d960e7af4 100644
--- a/diesel/src/connection/statement_cache.rs
+++ b/diesel/src/connection/statement_cache.rs
@@ -215,7 +215,7 @@ where
             None => {
                 let sql = Self::construct_sql(source)?;
                 Ok(StatementCacheKey::Sql {
-                    sql: sql,
+                    sql,
                     bind_types: bind_types.into(),
                 })
             }
diff --git a/diesel/src/pg/backend.rs b/diesel/src/pg/backend.rs
index 27195a2ca8f0..58a283fc2ed4 100644
--- a/diesel/src/pg/backend.rs
+++ b/diesel/src/pg/backend.rs
@@ -6,6 +6,7 @@ use super::query_builder::PgQueryBuilder;
 use super::{PgMetadataLookup, PgValue};
 use crate::backend::*;
 use crate::deserialize::Queryable;
+use crate::pg::metadata_lookup::PgMetadataCacheKey;
 use crate::query_builder::bind_collector::RawBytesBindCollector;
 use crate::sql_types::TypeMetadata;
 
@@ -13,19 +14,72 @@ use crate::sql_types::TypeMetadata;
 #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
 pub struct Pg;
 
+#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Queryable)]
+pub(in crate::pg) struct InnerPgTypeMetadata {
+    pub oid: u32,
+    pub array_oid: u32,
+}
+
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+pub(in crate::pg) struct FailedToLookupTypeError(Box<PgMetadataCacheKey<'static>>);
+
+impl FailedToLookupTypeError {
+    pub(crate) fn new(cache_key: PgMetadataCacheKey<'static>) -> Self {
+        Self(Box::new(cache_key))
+    }
+}
+
+impl std::error::Error for FailedToLookupTypeError {}
+
+impl std::fmt::Display for FailedToLookupTypeError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if let Some(schema) = self.0.schema.as_ref() {
+            write!(
+                f,
+                "Failed to find a type oid for `{}`.`{}`",
+                schema, self.0.type_name
+            )
+        } else {
+            write!(f, "Failed to find a type oid for `{}`", self.0.type_name)
+        }
+    }
+}
+
 /// The [OIDs] for a SQL type
 ///
 /// [OIDs]: https://www.postgresql.org/docs/current/static/datatype-oid.html
-#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default, Queryable)]
-pub struct PgTypeMetadata {
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+#[must_use]
+pub struct PgTypeMetadata(pub(in crate::pg) Result<InnerPgTypeMetadata, FailedToLookupTypeError>);
+
+impl PgTypeMetadata {
+    /// Create a new instance of this type based on known constant [OIDs].
+    ///
+    /// Please refer to [PgMetadataLookup] for a way to query [OIDs]
+    /// of custom types at run time
+    ///
+    /// [OIDs]: https://www.postgresql.org/docs/current/static/datatype-oid.html
+    /// [PgMetadataLookup]: struct.PgMetadataLookup.html
+    pub fn new(type_oid: u32, array_oid: u32) -> Self {
+        Self(Ok(InnerPgTypeMetadata {
+            oid: type_oid,
+            array_oid,
+        }))
+    }
+
     /// The [OID] of `T`
     ///
     /// [OID]: https://www.postgresql.org/docs/current/static/datatype-oid.html
-    pub oid: u32,
+    pub fn oid(&self) -> Result<u32, impl std::error::Error + Send + Sync> {
+        self.0.as_ref().map(|i| i.oid).map_err(Clone::clone)
+    }
+
     /// The [OID] of `T[]`
     ///
     /// [OID]: https://www.postgresql.org/docs/current/static/datatype-oid.html
-    pub array_oid: u32,
+    pub fn array_oid(self) -> Result<u32, impl std::error::Error + Send + Sync> {
+        self.0.as_ref().map(|i| i.array_oid).map_err(Clone::clone)
+    }
 }
 
 impl Backend for Pg {
diff --git a/diesel/src/pg/connection/stmt/mod.rs b/diesel/src/pg/connection/stmt/mod.rs
index d017d97ea1a0..ebe65425ad1c 100644
--- a/diesel/src/pg/connection/stmt/mod.rs
+++ b/diesel/src/pg/connection/stmt/mod.rs
@@ -57,7 +57,11 @@ impl Statement {
     ) -> QueryResult<Self> {
         let name = CString::new(name.unwrap_or(""))?;
         let sql = CString::new(sql)?;
-        let param_types_vec = param_types.iter().map(|x| x.oid).collect();
+        let param_types_vec = param_types
+            .iter()
+            .map(|x| x.oid())
+            .collect::<Result<Vec<_>, _>>()
+            .map_err(|e| crate::result::Error::SerializationError(Box::new(e)))?;
 
         let internal_result = unsafe {
             conn.raw_connection.prepare(
diff --git a/diesel/src/pg/metadata_lookup.rs b/diesel/src/pg/metadata_lookup.rs
index 8de80c2560dc..2695c7c7f44f 100644
--- a/diesel/src/pg/metadata_lookup.rs
+++ b/diesel/src/pg/metadata_lookup.rs
@@ -1,8 +1,10 @@
 #![allow(unused_parens)] // FIXME: Remove this attribute once false positive is resolved.
 
+use super::backend::{FailedToLookupTypeError, InnerPgTypeMetadata};
 use super::{PgConnection, PgTypeMetadata};
 use crate::prelude::*;
 
+use std::borrow::Cow;
 use std::cell::RefCell;
 use std::collections::HashMap;
 
@@ -24,26 +26,78 @@ impl PgMetadataLookup {
     /// This function should only be used for user defined types, or types which
     /// come from an extension. This function may perform a SQL query to look
     /// up the type. For built-in types, a static OID should be preferred.
-    pub fn lookup_type(&self, type_name: &str) -> PgTypeMetadata {
+    pub fn lookup_type(&self, type_name: &str, schema: Option<&str>) -> PgTypeMetadata {
         let metadata_cache = self.conn.get_metadata_cache();
-        metadata_cache.lookup_type(type_name).unwrap_or_else(|| {
-            use self::pg_type::dsl::*;
-
-            let type_metadata = pg_type
-                .select((oid, typarray))
-                .filter(typname.eq(type_name))
-                .first(&self.conn)
-                .unwrap_or_default();
-            metadata_cache.store_type(type_name, type_metadata);
-            type_metadata
+        let cache_key = PgMetadataCacheKey {
+            schema: schema.map(Cow::Borrowed),
+            type_name: Cow::Borrowed(type_name),
+        };
+
+        metadata_cache.lookup_type(&cache_key).unwrap_or_else(|| {
+            let r = lookup_type(&cache_key, &self.conn);
+
+            match r {
+                Ok(type_metadata) => {
+                    metadata_cache.store_type(cache_key, type_metadata);
+                    PgTypeMetadata(Ok(type_metadata))
+                }
+                Err(_e) => {
+                    PgTypeMetadata(Err(FailedToLookupTypeError::new(cache_key.into_owned())))
+                }
+            }
         })
     }
 }
 
+fn lookup_type(
+    cache_key: &PgMetadataCacheKey<'_>,
+    conn: &PgConnection,
+) -> QueryResult<InnerPgTypeMetadata> {
+    let search_path: String;
+
+    let search_schema = if let Some(schema) = cache_key.schema.as_ref() {
+        vec![schema.as_ref()]
+    } else {
+        search_path = crate::dsl::sql("SHOW search_path").get_result::<String>(conn)?;
+
+        search_path
+            .split(',')
+            // skip the `$user` entry for now
+            .filter(|f| !f.starts_with("\"$"))
+            .map(|s| s.trim())
+            .collect()
+    };
+
+    let metadata = pg_type::table
+        .inner_join(pg_namespace::table)
+        .filter(pg_namespace::nspname.eq(crate::dsl::any(search_schema)))
+        .filter(pg_type::typname.eq(&cache_key.type_name))
+        .select((pg_type::oid, pg_type::typarray))
+        .first(conn)?;
+
+    Ok(metadata)
+}
+
+#[derive(Hash, PartialEq, Eq, Debug, Clone)]
+pub(in crate::pg) struct PgMetadataCacheKey<'a> {
+    pub(crate) schema: Option<Cow<'a, str>>,
+    pub(crate) type_name: Cow<'a, str>,
+}
+
+impl<'a> PgMetadataCacheKey<'a> {
+    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()),
+        }
+    }
+}
+
 /// Stores a cache for the OID of custom types
 #[allow(missing_debug_implementations)]
 pub struct PgMetadataCache {
-    cache: RefCell<HashMap<String, PgTypeMetadata>>,
+    cache: RefCell<HashMap<PgMetadataCacheKey<'static>, InnerPgTypeMetadata>>,
 }
 
 impl PgMetadataCache {
@@ -53,14 +107,14 @@ impl PgMetadataCache {
         }
     }
 
-    fn lookup_type(&self, type_name: &str) -> Option<PgTypeMetadata> {
-        Some(*self.cache.borrow().get(type_name)?)
+    fn lookup_type(&self, type_name: &PgMetadataCacheKey) -> Option<PgTypeMetadata> {
+        Some(PgTypeMetadata(Ok(*self.cache.borrow().get(type_name)?)))
     }
 
-    fn store_type(&self, type_name: &str, type_metadata: PgTypeMetadata) {
+    fn store_type(&self, type_name: PgMetadataCacheKey, type_metadata: InnerPgTypeMetadata) {
         self.cache
             .borrow_mut()
-            .insert(type_name.to_owned(), type_metadata);
+            .insert(type_name.into_owned(), type_metadata);
     }
 }
 
@@ -69,5 +123,16 @@ table! {
         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);
diff --git a/diesel/src/pg/types/array.rs b/diesel/src/pg/types/array.rs
index 7d799b325ba0..05d0a5a7fe2c 100644
--- a/diesel/src/pg/types/array.rs
+++ b/diesel/src/pg/types/array.rs
@@ -12,9 +12,9 @@ where
     Pg: HasSqlType<T>,
 {
     fn metadata(lookup: &PgMetadataLookup) -> PgTypeMetadata {
-        PgTypeMetadata {
-            oid: <Pg as HasSqlType<T>>::metadata(lookup).array_oid,
-            array_oid: 0,
+        match <Pg as HasSqlType<T>>::metadata(lookup).0 {
+            Ok(tpe) => PgTypeMetadata::new(tpe.array_oid, 0),
+            c @ Err(_) => PgTypeMetadata(c),
         }
     }
 }
@@ -91,7 +91,7 @@ where
         out.write_i32::<NetworkEndian>(num_dimensions)?;
         let flags = 0;
         out.write_i32::<NetworkEndian>(flags)?;
-        let element_oid = Pg::metadata(out.metadata_lookup()).oid;
+        let element_oid = Pg::metadata(out.metadata_lookup()).oid()?;
         out.write_u32::<NetworkEndian>(element_oid)?;
         out.write_i32::<NetworkEndian>(self.len() as i32)?;
         let lower_bound = 1;
diff --git a/diesel/src/pg/types/ranges.rs b/diesel/src/pg/types/ranges.rs
index 94a3e31969a8..35ad3966b841 100644
--- a/diesel/src/pg/types/ranges.rs
+++ b/diesel/src/pg/types/ranges.rs
@@ -160,54 +160,36 @@ where
 
 impl HasSqlType<Int4range> for Pg {
     fn metadata(_: &PgMetadataLookup) -> PgTypeMetadata {
-        PgTypeMetadata {
-            oid: 3904,
-            array_oid: 3905,
-        }
+        PgTypeMetadata::new(3904, 3905)
     }
 }
 
 impl HasSqlType<Numrange> for Pg {
     fn metadata(_: &PgMetadataLookup) -> PgTypeMetadata {
-        PgTypeMetadata {
-            oid: 3906,
-            array_oid: 3907,
-        }
+        PgTypeMetadata::new(3906, 3907)
     }
 }
 
 impl HasSqlType<Tsrange> for Pg {
     fn metadata(_: &PgMetadataLookup) -> PgTypeMetadata {
-        PgTypeMetadata {
-            oid: 3908,
-            array_oid: 3909,
-        }
+        PgTypeMetadata::new(3908, 3909)
     }
 }
 
 impl HasSqlType<Tstzrange> for Pg {
     fn metadata(_: &PgMetadataLookup) -> PgTypeMetadata {
-        PgTypeMetadata {
-            oid: 3910,
-            array_oid: 3911,
-        }
+        PgTypeMetadata::new(3910, 3911)
     }
 }
 
 impl HasSqlType<Daterange> for Pg {
     fn metadata(_: &PgMetadataLookup) -> PgTypeMetadata {
-        PgTypeMetadata {
-            oid: 3912,
-            array_oid: 3913,
-        }
+        PgTypeMetadata::new(3912, 3913)
     }
 }
 
 impl HasSqlType<Int8range> for Pg {
     fn metadata(_: &PgMetadataLookup) -> PgTypeMetadata {
-        PgTypeMetadata {
-            oid: 3926,
-            array_oid: 3927,
-        }
+        PgTypeMetadata::new(3926, 3927)
     }
 }
diff --git a/diesel/src/pg/types/record.rs b/diesel/src/pg/types/record.rs
index fb45530f356b..f3752ed17a83 100644
--- a/diesel/src/pg/types/record.rs
+++ b/diesel/src/pg/types/record.rs
@@ -103,7 +103,7 @@ macro_rules! tuple_impls {
                 out.write_i32::<NetworkEndian>($Tuple)?;
 
                 $(
-                    let oid = <Pg as HasSqlType<$ST>>::metadata(out.metadata_lookup()).oid;
+                    let oid = <Pg as HasSqlType<$ST>>::metadata(out.metadata_lookup()).oid()?;
                     out.write_u32::<NetworkEndian>(oid)?;
                     let is_null = self.$idx.to_sql(&mut buffer)?;
 
diff --git a/diesel_derives/src/lib.rs b/diesel_derives/src/lib.rs
index 0e4b60ed0ae5..579279b8b887 100644
--- a/diesel_derives/src/lib.rs
+++ b/diesel_derives/src/lib.rs
@@ -752,7 +752,7 @@ pub fn derive_queryable_by_name(input: TokenStream) -> TokenStream {
 /// You don't need to specify every backend,
 /// only the ones supported by your type.
 ///
-/// For PostgreSQL, add  `#[postgres(type_name = "pg_type_name")]`
+/// For PostgreSQL, add  `#[postgres(type_name = "pg_type_name", type_schema = "pg_schema_name")]`
 /// or `#[postgres(oid = "some_oid", array_oid = "some_oid")]` for
 /// builtin types.
 /// For MySQL, specify which variant of `MysqlType` should be used
@@ -764,9 +764,10 @@ pub fn derive_queryable_by_name(input: TokenStream) -> TokenStream {
 ///
 /// ## Type attributes
 ///
-/// * `#[postgres(type_name = "TypeName")]` specifies support for
-/// a postgresql type with the name `TypeName`. Prefer this variant
-/// for types with no stable OID (== everything but the builtin types)
+/// * `#[postgres(type_name = "TypeName", type_schema = "public")]` specifies support for
+/// a postgresql type with the name `TypeName` in the schema `public`. Prefer this variant
+/// for types with no stable OID (== everything but the builtin types). It's possible to leaf
+/// of the `type_schema` part. In that case diesel defaults to the default postgres search path.
 /// * `#[postgres(oid = 42, array_oid = 142)]`, specifies support for a
 /// postgresql type with the given `oid` and `array_oid`. This variant
 /// should only be used with types that have a stable OID.
diff --git a/diesel_derives/src/sql_type.rs b/diesel_derives/src/sql_type.rs
index 2f8ea7a2bc38..787b6c526c45 100644
--- a/diesel_derives/src/sql_type.rs
+++ b/diesel_derives/src/sql_type.rs
@@ -106,15 +106,17 @@ fn pg_tokens(item: &syn::DeriveInput) -> Option<proc_macro2::TokenStream> {
             let metadata_fn = match ty {
                 PgType::Fixed { oid, array_oid } => quote!(
                     fn metadata(_: &PgMetadataLookup) -> PgTypeMetadata {
-                        PgTypeMetadata {
-                            oid: #oid,
-                            array_oid: #array_oid,
-                        }
+                        PgTypeMetadata::new(#oid, #array_oid)
                     }
                 ),
-                PgType::Lookup(type_name) => quote!(
+                PgType::Lookup(type_name, Some(type_schema)) => quote!(
                     fn metadata(lookup: &PgMetadataLookup) -> PgTypeMetadata {
-                        lookup.lookup_type(#type_name)
+                        lookup.lookup_type(#type_name, Some(#type_schema))
+                    }
+                ),
+                PgType::Lookup(type_name, None) => quote!(
+                    fn metadata(lookup: &PgMetadataLookup) -> PgTypeMetadata {
+                        lookup.lookup_type(#type_name, None)
                     }
                 ),
             };
@@ -133,9 +135,13 @@ fn pg_tokens(item: &syn::DeriveInput) -> Option<proc_macro2::TokenStream> {
 }
 
 fn get_type_name(attr: &MetaItem) -> Result<Option<PgType>, Diagnostic> {
+    let schema = attr.nested_item("type_schema")?;
     Ok(attr.nested_item("type_name")?.map(|ty| {
-        attr.warn_if_other_options(&["type_name"]);
-        PgType::Lookup(ty.expect_str_value())
+        attr.warn_if_other_options(&["type_name", "type_schema"]);
+        PgType::Lookup(
+            ty.expect_str_value(),
+            schema.map(|schema| schema.expect_str_value()),
+        )
     }))
 }
 
@@ -155,5 +161,5 @@ fn get_oids(attr: &MetaItem) -> Result<Option<PgType>, Diagnostic> {
 
 enum PgType {
     Fixed { oid: u32, array_oid: u32 },
-    Lookup(String),
+    Lookup(String, Option<String>),
 }


Test patch:
diff --git a/diesel_compile_tests/tests/fail/array_expressions_must_be_same_type.stderr b/diesel_compile_tests/tests/fail/array_expressions_must_be_same_type.stderr
index bd1196c84705..dacef94de1c8 100644
--- a/diesel_compile_tests/tests/fail/array_expressions_must_be_same_type.stderr
+++ b/diesel_compile_tests/tests/fail/array_expressions_must_be_same_type.stderr
@@ -106,7 +106,7 @@ error[E0277]: the trait bound `{integer}: diesel::SelectableExpression<()>` is n
               <(A, B) as diesel::SelectableExpression<QS>>
               <(A, B, C) as diesel::SelectableExpression<QS>>
               <(A, B, C, D) as diesel::SelectableExpression<QS>>
-            and 108 others
+            and 124 others
     = note: required because of the requirements on the impl of `diesel::SelectableExpression<()>` for `({integer}, diesel::expression::bound::Bound<diesel::sql_types::Double, f64>)`
     = note: required because of the requirements on the impl of `diesel::SelectableExpression<()>` for `diesel::pg::expression::array::ArrayLiteral<({integer}, diesel::expression::bound::Bound<diesel::sql_types::Double, f64>), diesel::sql_types::Double>`
     = note: required because of the requirements on the impl of `diesel::query_dsl::select_dsl::SelectDsl<diesel::pg::expression::array::ArrayLiteral<({integer}, diesel::expression::bound::Bound<diesel::sql_types::Double, f64>), diesel::sql_types::Double>>` for `diesel::query_builder::SelectStatement<()>`
@@ -127,7 +127,7 @@ error[E0277]: the trait bound `{integer}: diesel::expression::ValidGrouping<()>`
               <(A, B) as diesel::expression::ValidGrouping<__GroupByClause>>
               <(A, B, C) as diesel::expression::ValidGrouping<__GroupByClause>>
               <(A, B, C, D) as diesel::expression::ValidGrouping<__GroupByClause>>
-            and 111 others
+            and 118 others
     = note: required because of the requirements on the impl of `diesel::expression::ValidGrouping<()>` for `({integer}, diesel::expression::bound::Bound<diesel::sql_types::Double, f64>)`
     = note: required because of the requirements on the impl of `diesel::expression::ValidGrouping<()>` for `diesel::pg::expression::array::ArrayLiteral<({integer}, diesel::expression::bound::Bound<diesel::sql_types::Double, f64>), diesel::sql_types::Double>`
     = note: required because of the requirements on the impl of `diesel::query_dsl::select_dsl::SelectDsl<diesel::pg::expression::array::ArrayLiteral<({integer}, diesel::expression::bound::Bound<diesel::sql_types::Double, f64>), diesel::sql_types::Double>>` for `diesel::query_builder::SelectStatement<()>`
@@ -143,7 +143,7 @@ error[E0277]: the trait bound `{integer}: diesel::SelectableExpression<()>` is n
              <(A, B) as diesel::SelectableExpression<QS>>
              <(A, B, C) as diesel::SelectableExpression<QS>>
              <(A, B, C, D) as diesel::SelectableExpression<QS>>
-           and 108 others
+           and 124 others
    = note: required because of the requirements on the impl of `diesel::SelectableExpression<()>` for `({integer}, diesel::expression::bound::Bound<diesel::sql_types::Double, f64>)`
    = note: required because of the requirements on the impl of `diesel::SelectableExpression<()>` for `diesel::pg::expression::array::ArrayLiteral<({integer}, diesel::expression::bound::Bound<diesel::sql_types::Double, f64>), diesel::sql_types::Double>`
    = note: required because of the requirements on the impl of `diesel::query_builder::SelectClauseExpression<()>` for `diesel::query_builder::select_clause::SelectClause<diesel::pg::expression::array::ArrayLiteral<({integer}, diesel::expression::bound::Bound<diesel::sql_types::Double, f64>), diesel::sql_types::Double>>`
@@ -161,7 +161,7 @@ error[E0277]: the trait bound `{integer}: diesel::expression::ValidGrouping<()>`
              <(A, B) as diesel::expression::ValidGrouping<__GroupByClause>>
              <(A, B, C) as diesel::expression::ValidGrouping<__GroupByClause>>
              <(A, B, C, D) as diesel::expression::ValidGrouping<__GroupByClause>>
-           and 111 others
+           and 118 others
    = note: required because of the requirements on the impl of `diesel::expression::ValidGrouping<()>` for `({integer}, diesel::expression::bound::Bound<diesel::sql_types::Double, f64>)`
    = note: required because of the requirements on the impl of `diesel::expression::ValidGrouping<()>` for `diesel::pg::expression::array::ArrayLiteral<({integer}, diesel::expression::bound::Bound<diesel::sql_types::Double, f64>), diesel::sql_types::Double>`
    = note: required because of the requirements on the impl of `diesel::query_builder::Query` for `diesel::query_builder::SelectStatement<(), diesel::query_builder::select_clause::SelectClause<diesel::pg::expression::array::ArrayLiteral<({integer}, diesel::expression::bound::Bound<diesel::sql_types::Double, f64>), diesel::sql_types::Double>>>`
@@ -178,7 +178,7 @@ error[E0277]: the trait bound `{integer}: diesel::query_builder::QueryId` is not
              <() as diesel::query_builder::QueryId>
              <(A, B) as diesel::query_builder::QueryId>
              <(A, B, C) as diesel::query_builder::QueryId>
-           and 180 others
+           and 185 others
    = note: required because of the requirements on the impl of `diesel::query_builder::QueryId` for `({integer}, diesel::expression::bound::Bound<diesel::sql_types::Double, f64>)`
    = note: required because of the requirements on the impl of `diesel::query_builder::QueryId` for `diesel::pg::expression::array::ArrayLiteral<({integer}, diesel::expression::bound::Bound<diesel::sql_types::Double, f64>), diesel::sql_types::Double>`
    = note: required because of the requirements on the impl of `diesel::query_builder::QueryId` for `diesel::query_builder::select_clause::SelectClause<diesel::pg::expression::array::ArrayLiteral<({integer}, diesel::expression::bound::Bound<diesel::sql_types::Double, f64>), diesel::sql_types::Double>>`
@@ -196,7 +196,7 @@ error[E0277]: the trait bound `{integer}: diesel::query_builder::QueryFragment<_
              <() as diesel::query_builder::QueryFragment<DB>>
              <(A, B) as diesel::query_builder::QueryFragment<__DB>>
              <(A, B, C) as diesel::query_builder::QueryFragment<__DB>>
-           and 211 others
+           and 215 others
    = note: required because of the requirements on the impl of `diesel::query_builder::QueryFragment<_>` for `({integer}, diesel::expression::bound::Bound<diesel::sql_types::Double, f64>)`
    = note: required because of the requirements on the impl of `for<'a> diesel::query_builder::QueryFragment<_>` for `&'a ({integer}, diesel::expression::bound::Bound<diesel::sql_types::Double, f64>)`
    = note: required because of the requirements on the impl of `diesel::query_builder::QueryFragment<_>` for `diesel::pg::expression::array::ArrayLiteral<({integer}, diesel::expression::bound::Bound<diesel::sql_types::Double, f64>), diesel::sql_types::Double>`
@@ -220,7 +220,7 @@ error[E0277]: the trait bound `{integer}: diesel::Expression` is not satisfied
              <(A, B) as diesel::Expression>
              <(A, B, C) as diesel::Expression>
              <(A, B, C, D) as diesel::Expression>
-           and 96 others
+           and 100 others
    = note: required because of the requirements on the impl of `diesel::expression::AsExpression<diesel::sql_types::Double>` for `{integer}`
    = note: required because of the requirements on the impl of `diesel::expression::AsExpressionList<diesel::sql_types::Double>` for `({integer}, f64)`
 
diff --git a/diesel_compile_tests/tests/fail/find_requires_correct_type.stderr b/diesel_compile_tests/tests/fail/find_requires_correct_type.stderr
index 3fcdab585f20..ed30c9b6946d 100644
--- a/diesel_compile_tests/tests/fail/find_requires_correct_type.stderr
+++ b/diesel_compile_tests/tests/fail/find_requires_correct_type.stderr
@@ -30,7 +30,7 @@ error[E0277]: the trait bound `{integer}: diesel::Expression` is not satisfied
              <(A, B) as diesel::Expression>
              <(A, B, C) as diesel::Expression>
              <(A, B, C, D) as diesel::Expression>
-           and 100 others
+           and 104 others
    = note: required because of the requirements on the impl of `diesel::Expression` for `diesel::expression::operators::Eq<string_primary_key::columns::id, {integer}>`
    = note: required because of the requirements on the impl of `diesel::Expression` for `diesel::expression::nullable::Nullable<diesel::expression::grouped::Grouped<diesel::expression::operators::Eq<string_primary_key::columns::id, {integer}>>>`
    = note: required because of the requirements on the impl of `diesel::query_dsl::filter_dsl::FilterDsl<diesel::expression::nullable::Nullable<diesel::expression::grouped::Grouped<diesel::expression::operators::Eq<string_primary_key::columns::id, {integer}>>>>` for `diesel::query_builder::SelectStatement<string_primary_key::table>`
@@ -46,6 +46,6 @@ error[E0277]: the trait bound `{integer}: diesel::expression::ValidGrouping<()>`
              <(A, B) as diesel::expression::ValidGrouping<__GroupByClause>>
              <(A, B, C) as diesel::expression::ValidGrouping<__GroupByClause>>
              <(A, B, C, D) as diesel::expression::ValidGrouping<__GroupByClause>>
-           and 117 others
+           and 124 others
    = note: required because of the requirements on the impl of `diesel::expression::ValidGrouping<()>` for `diesel::expression::operators::Eq<string_primary_key::columns::id, {integer}>`
    = note: required because of the requirements on the impl of `diesel::query_dsl::filter_dsl::FilterDsl<diesel::expression::nullable::Nullable<diesel::expression::grouped::Grouped<diesel::expression::operators::Eq<string_primary_key::columns::id, {integer}>>>>` for `diesel::query_builder::SelectStatement<string_primary_key::table>`
diff --git a/diesel_compile_tests/tests/fail/insert_requires_value_of_same_type_as_column.stderr b/diesel_compile_tests/tests/fail/insert_requires_value_of_same_type_as_column.stderr
index dfde23ad7667..82190542606c 100644
--- a/diesel_compile_tests/tests/fail/insert_requires_value_of_same_type_as_column.stderr
+++ b/diesel_compile_tests/tests/fail/insert_requires_value_of_same_type_as_column.stderr
@@ -9,5 +9,5 @@ error[E0277]: the trait bound `{integer}: diesel::Expression` is not satisfied
              <(A, B) as diesel::Expression>
              <(A, B, C) as diesel::Expression>
              <(A, B, C, D) as diesel::Expression>
-           and 100 others
+           and 104 others
    = note: required because of the requirements on the impl of `diesel::expression::AsExpression<diesel::sql_types::Text>` for `{integer}`
diff --git a/diesel_tests/tests/custom_types.rs b/diesel_tests/tests/custom_types.rs
index 915e81703486..5bb6f6a71f1c 100644
--- a/diesel_tests/tests/custom_types.rs
+++ b/diesel_tests/tests/custom_types.rs
@@ -84,3 +84,83 @@ fn custom_types_round_trip() {
         .unwrap();
     assert_eq!(data, inserted);
 }
+
+table! {
+    use diesel::sql_types::*;
+    use super::MyTypeInCustomSchema;
+    custom_schema.custom_types_with_custom_schema {
+        id -> Integer,
+        custom_enum -> MyTypeInCustomSchema,
+    }
+}
+
+#[derive(SqlType)]
+#[postgres(type_name = "my_type", type_schema = "custom_schema")]
+pub struct MyTypeInCustomSchema;
+
+#[derive(Debug, PartialEq, FromSqlRow, AsExpression)]
+#[sql_type = "MyTypeInCustomSchema"]
+pub enum MyEnumInCustomSchema {
+    Foo,
+    Bar,
+}
+
+impl ToSql<MyTypeInCustomSchema, Pg> for MyEnumInCustomSchema {
+    fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> serialize::Result {
+        match *self {
+            MyEnumInCustomSchema::Foo => out.write_all(b"foo")?,
+            MyEnumInCustomSchema::Bar => out.write_all(b"bar")?,
+        }
+        Ok(IsNull::No)
+    }
+}
+
+impl FromSql<MyTypeInCustomSchema, Pg> for MyEnumInCustomSchema {
+    fn from_sql(bytes: PgValue<'_>) -> deserialize::Result<Self> {
+        match bytes.as_bytes() {
+            b"foo" => Ok(MyEnumInCustomSchema::Foo),
+            b"bar" => Ok(MyEnumInCustomSchema::Bar),
+            _ => Err("Unrecognized enum variant".into()),
+        }
+    }
+}
+
+#[derive(Insertable, Queryable, Identifiable, Debug, PartialEq)]
+#[table_name = "custom_types_with_custom_schema"]
+struct HasCustomTypesInCustomSchema {
+    id: i32,
+    custom_enum: MyEnumInCustomSchema,
+}
+
+#[test]
+fn custom_types_in_custom_schema_round_trip() {
+    let data = vec![
+        HasCustomTypesInCustomSchema {
+            id: 1,
+            custom_enum: MyEnumInCustomSchema::Foo,
+        },
+        HasCustomTypesInCustomSchema {
+            id: 2,
+            custom_enum: MyEnumInCustomSchema::Bar,
+        },
+    ];
+    let connection = connection();
+    connection
+        .batch_execute(
+            r#"
+        CREATE SCHEMA IF NOT EXISTS custom_schema;
+        CREATE TYPE custom_schema.my_type AS ENUM ('foo', 'bar');
+        CREATE TABLE custom_schema.custom_types_with_custom_schema (
+            id SERIAL PRIMARY KEY,
+            custom_enum custom_schema.my_type NOT NULL
+        );
+    "#,
+        )
+        .unwrap();
+
+    let inserted = insert_into(custom_types_with_custom_schema::table)
+        .values(&data)
+        .get_results(&connection)
+        .unwrap();
+    assert_eq!(data, inserted);
+}