diff --git a/COPYING b/COPYING
--- a/COPYING
+++ b/COPYING
@@ -1,4 +1,4 @@
-Copyright (c) 2014, 2015, Dylan Simon
+Copyright (c) 2014-2017, Dylan Simon
 Portions Copyright (c) 2010, 2011, Chris Forno
 All rights reserved.
 
@@ -9,9 +9,9 @@
     * Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.
-    * Neither the name of Chris Forno nor the names of its contributors may be
-      used to endorse or promote products derived from this software without
-      specific prior written permission.
+    * Neither the name of postgresql-typed nor the names of its contributors
+      may be used to endorse or promote products derived from this software
+      without specific prior written permission.
 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
diff --git a/Database/PostgreSQL/Typed.hs b/Database/PostgreSQL/Typed.hs
--- a/Database/PostgreSQL/Typed.hs
+++ b/Database/PostgreSQL/Typed.hs
@@ -176,7 +176,7 @@
 -- You can make as many 'PGParameter' and 'PGColumn' instances as you want if you want to support different representations of your type.
 -- If you want to use any of the functions in "Database.PostgreSQL.Typed.Dynamic", however, such as 'Database.PostgreSQL.Typed.Dynamic.pgSafeLiteral', you must define a default representation:
 --
--- > instance PGRep "mytype" MyType
+-- > instance PGRep MyType where type PGRepType MyType = "mytype"
 --
 -- If you want to support arrays of your new type, you should also provide a 'Database.PostgreSQL.Typed.Array.PGArrayType' instance (or 'Database.PostgreSQL.Typed.Range.PGRangeType' for new ranges).
 -- Currently only 1-dimensional arrays are supported.
diff --git a/Database/PostgreSQL/Typed/Array.hs b/Database/PostgreSQL/Typed/Array.hs
--- a/Database/PostgreSQL/Typed/Array.hs
+++ b/Database/PostgreSQL/Typed/Array.hs
@@ -1,4 +1,7 @@
-{-# LANGUAGE CPP, MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies, UndecidableInstances, DataKinds, OverloadedStrings #-}
+{-# LANGUAGE CPP, MultiParamTypeClasses, FlexibleContexts, FlexibleInstances, UndecidableInstances, DataKinds, OverloadedStrings, TypeFamilies #-}
+#if __GLASGOW_HASKELL__ >= 800
+{-# LANGUAGE UndecidableSuperClasses #-}
+#endif
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 #if __GLASGOW_HASKELL__ >= 800
 {-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
@@ -25,6 +28,7 @@
 #if !MIN_VERSION_base(4,8,0)
 import Data.Monoid (mconcat)
 #endif
+import GHC.TypeLits (Symbol)
 
 import Database.PostgreSQL.Typed.Types
 
@@ -35,165 +39,298 @@
 -- |Class indicating that the first PostgreSQL type is an array of the second.
 -- This implies 'PGParameter' and 'PGColumn' instances that will work for any type using comma as a delimiter (i.e., anything but @box@).
 -- This will only work with 1-dimensional arrays.
-class (PGType ta, PGType t) => PGArrayType ta t | ta -> t, t -> ta where
-  pgArrayElementType :: PGTypeName ta -> PGTypeName t
+class (PGType t, PGType (PGElemType t)) => PGArrayType t where
+  type PGElemType t :: Symbol
+  pgArrayElementType :: PGTypeID t -> PGTypeID (PGElemType t)
   pgArrayElementType PGTypeProxy = PGTypeProxy
   -- |The character used as a delimeter.  The default @,@ is correct for all standard types (except @box@).
-  pgArrayDelim :: PGTypeName ta -> Char
+  pgArrayDelim :: PGTypeID t -> Char
   pgArrayDelim _ = ','
 
 instance
 #if __GLASGOW_HASKELL__ >= 710
     {-# OVERLAPPING #-}
 #endif
-    (PGArrayType ta t, PGParameter t a) => PGParameter ta (PGArray a) where
+    (PGArrayType t, PGParameter (PGElemType t) a) => PGParameter t (PGArray a) where
   pgEncode ta l = buildPGValue $ BSB.char7 '{' <> mconcat (intersperse (BSB.char7 $ pgArrayDelim ta) $ map el l) <> BSB.char7 '}' where
     el Nothing = BSB.string7 "null"
     el (Just e) = pgDQuote (pgArrayDelim ta : "{}") $ pgEncode (pgArrayElementType ta) e
 #if __GLASGOW_HASKELL__ >= 710
 -- |Allow entirely non-null arrays as parameter inputs only.
 -- (Only supported on ghc >= 7.10 due to instance overlap.)
-instance {-# OVERLAPPABLE #-} (PGArrayType ta t, PGParameter t a) => PGParameter ta [a] where
+instance {-# OVERLAPPABLE #-} (PGArrayType t, PGParameter (PGElemType t) a) => PGParameter t [a] where
   pgEncode ta = pgEncode ta . map Just
 #endif
-instance (PGArrayType ta t, PGColumn t a) => PGColumn ta (PGArray a) where
+instance (PGArrayType t, PGColumn (PGElemType t) a) => PGColumn t (PGArray a) where
   pgDecode ta a = either (error . ("pgDecode array (" ++) . (++ ("): " ++ BSC.unpack a))) id $ P.parseOnly pa a where
     pa = P.char '{' *> P.sepBy (P.skipSpace *> el <* P.skipSpace) (P.char (pgArrayDelim ta)) <* P.char '}' <* P.endOfInput
     el = fmap (pgDecode (pgArrayElementType ta)) <$>
       parsePGDQuote False (pgArrayDelim ta : "{}") (("null" ==) . BSC.map toLower)
 
 -- Just a dump of pg_type:
-instance PGType "boolean" => PGType "boolean[]"
-instance PGType "boolean" => PGArrayType "boolean[]" "boolean"
-instance PGType "bytea" => PGType "bytea[]"
-instance PGType "bytea" => PGArrayType "bytea[]" "bytea"
-instance PGType "\"char\"" => PGType "\"char\"[]"
-instance PGType "\"char\"" => PGArrayType "\"char\"[]" "\"char\""
-instance PGType "name" => PGType "name[]"
-instance PGType "name" => PGArrayType "name[]" "name"
-instance PGType "bigint" => PGType "bigint[]"
-instance PGType "bigint" => PGArrayType "bigint[]" "bigint"
-instance PGType "smallint" => PGType "smallint[]"
-instance PGType "smallint" => PGArrayType "smallint[]" "smallint"
-instance PGType "int2vector" => PGType "int2vector[]"
-instance PGType "int2vector" => PGArrayType "int2vector[]" "int2vector"
-instance PGType "integer" => PGType "integer[]"
-instance PGType "integer" => PGArrayType "integer[]" "integer"
-instance PGType "regproc" => PGType "regproc[]"
-instance PGType "regproc" => PGArrayType "regproc[]" "regproc"
-instance PGType "text" => PGType "text[]"
-instance PGType "text" => PGArrayType "text[]" "text"
-instance PGType "oid" => PGType "oid[]"
-instance PGType "oid" => PGArrayType "oid[]" "oid"
-instance PGType "tid" => PGType "tid[]"
-instance PGType "tid" => PGArrayType "tid[]" "tid"
-instance PGType "xid" => PGType "xid[]"
-instance PGType "xid" => PGArrayType "xid[]" "xid"
-instance PGType "cid" => PGType "cid[]"
-instance PGType "cid" => PGArrayType "cid[]" "cid"
-instance PGType "oidvector" => PGType "oidvector[]"
-instance PGType "oidvector" => PGArrayType "oidvector[]" "oidvector"
-instance PGType "json" => PGType "json[]"
-instance PGType "json" => PGArrayType "json[]" "json"
-instance PGType "xml" => PGType "xml[]"
-instance PGType "xml" => PGArrayType "xml[]" "xml"
-instance PGType "point" => PGType "point[]"
-instance PGType "point" => PGArrayType "point[]" "point"
-instance PGType "lseg" => PGType "lseg[]"
-instance PGType "lseg" => PGArrayType "lseg[]" "lseg"
-instance PGType "path" => PGType "path[]"
-instance PGType "path" => PGArrayType "path[]" "path"
-instance PGType "box" => PGType "box[]"
-instance PGType "box" => PGArrayType "box[]" "box" where
+instance PGType "boolean" => PGType "boolean[]" where
+  type PGVal "boolean[]" = PGArray (PGVal "boolean")
+instance PGType "boolean" => PGArrayType "boolean[]" where
+  type PGElemType "boolean[]" = "boolean"
+instance PGType "bytea" => PGType "bytea[]" where
+  type PGVal "bytea[]" = PGArray (PGVal "bytea")
+instance PGType "bytea" => PGArrayType "bytea[]" where
+  type PGElemType "bytea[]" = "bytea"
+instance PGType "\"char\"" => PGType "\"char\"[]" where
+  type PGVal "\"char\"[]" = PGArray (PGVal "\"char\"")
+instance PGType "\"char\"" => PGArrayType "\"char\"[]" where
+  type PGElemType "\"char\"[]" = "\"char\""
+instance PGType "name" => PGType "name[]" where
+  type PGVal "name[]" = PGArray (PGVal "name")
+instance PGType "name" => PGArrayType "name[]" where
+  type PGElemType "name[]" = "name"
+instance PGType "bigint" => PGType "bigint[]" where
+  type PGVal "bigint[]" = PGArray (PGVal "bigint")
+instance PGType "bigint" => PGArrayType "bigint[]" where
+  type PGElemType "bigint[]" = "bigint"
+instance PGType "smallint" => PGType "smallint[]" where
+  type PGVal "smallint[]" = PGArray (PGVal "smallint")
+instance PGType "smallint" => PGArrayType "smallint[]" where
+  type PGElemType "smallint[]" = "smallint"
+instance PGType "int2vector" => PGType "int2vector[]" where
+  type PGVal "int2vector[]" = PGArray (PGVal "int2vector")
+instance PGType "int2vector" => PGArrayType "int2vector[]" where
+  type PGElemType "int2vector[]" = "int2vector"
+instance PGType "integer" => PGType "integer[]" where
+  type PGVal "integer[]" = PGArray (PGVal "integer")
+instance PGType "integer" => PGArrayType "integer[]" where
+  type PGElemType "integer[]" = "integer"
+instance PGType "regproc" => PGType "regproc[]" where
+  type PGVal "regproc[]" = PGArray (PGVal "regproc")
+instance PGType "regproc" => PGArrayType "regproc[]" where
+  type PGElemType "regproc[]" = "regproc"
+instance PGType "text" => PGType "text[]" where
+  type PGVal "text[]" = PGArray (PGVal "text")
+instance PGType "text" => PGArrayType "text[]" where
+  type PGElemType "text[]" = "text"
+instance PGType "oid" => PGType "oid[]" where
+  type PGVal "oid[]" = PGArray (PGVal "oid")
+instance PGType "oid" => PGArrayType "oid[]" where
+  type PGElemType "oid[]" = "oid"
+instance PGType "tid" => PGType "tid[]" where
+  type PGVal "tid[]" = PGArray (PGVal "tid")
+instance PGType "tid" => PGArrayType "tid[]" where
+  type PGElemType "tid[]" = "tid"
+instance PGType "xid" => PGType "xid[]" where
+  type PGVal "xid[]" = PGArray (PGVal "xid")
+instance PGType "xid" => PGArrayType "xid[]" where
+  type PGElemType "xid[]" = "xid"
+instance PGType "cid" => PGType "cid[]" where
+  type PGVal "cid[]" = PGArray (PGVal "cid")
+instance PGType "cid" => PGArrayType "cid[]" where
+  type PGElemType "cid[]" = "cid"
+instance PGType "oidvector" => PGType "oidvector[]" where
+  type PGVal "oidvector[]" = PGArray (PGVal "oidvector")
+instance PGType "oidvector" => PGArrayType "oidvector[]" where
+  type PGElemType "oidvector[]" = "oidvector"
+instance PGType "json" => PGType "json[]" where
+  type PGVal "json[]" = PGArray (PGVal "json")
+instance PGType "json" => PGArrayType "json[]" where
+  type PGElemType "json[]" = "json"
+instance PGType "xml" => PGType "xml[]" where
+  type PGVal "xml[]" = PGArray (PGVal "xml")
+instance PGType "xml" => PGArrayType "xml[]" where
+  type PGElemType "xml[]" = "xml"
+instance PGType "point" => PGType "point[]" where
+  type PGVal "point[]" = PGArray (PGVal "point")
+instance PGType "point" => PGArrayType "point[]" where
+  type PGElemType "point[]" = "point"
+instance PGType "lseg" => PGType "lseg[]" where
+  type PGVal "lseg[]" = PGArray (PGVal "lseg")
+instance PGType "lseg" => PGArrayType "lseg[]" where
+  type PGElemType "lseg[]" = "lseg"
+instance PGType "path" => PGType "path[]" where
+  type PGVal "path[]" = PGArray (PGVal "path")
+instance PGType "path" => PGArrayType "path[]" where
+  type PGElemType "path[]" = "path"
+instance PGType "box" => PGType "box[]" where
+  type PGVal "box[]" = PGArray (PGVal "box")
+instance PGType "box" => PGArrayType "box[]" where
+  type PGElemType "box[]" = "box"
   pgArrayDelim _ = ';'
-instance PGType "polygon" => PGType "polygon[]"
-instance PGType "polygon" => PGArrayType "polygon[]" "polygon"
-instance PGType "line" => PGType "line[]"
-instance PGType "line" => PGArrayType "line[]" "line"
-instance PGType "cidr" => PGType "cidr[]"
-instance PGType "cidr" => PGArrayType "cidr[]" "cidr"
-instance PGType "real" => PGType "real[]"
-instance PGType "real" => PGArrayType "real[]" "real"
-instance PGType "double precision" => PGType "double precision[]"
-instance PGType "double precision" => PGArrayType "double precision[]" "double precision"
-instance PGType "abstime" => PGType "abstime[]"
-instance PGType "abstime" => PGArrayType "abstime[]" "abstime"
-instance PGType "reltime" => PGType "reltime[]"
-instance PGType "reltime" => PGArrayType "reltime[]" "reltime"
-instance PGType "tinterval" => PGType "tinterval[]"
-instance PGType "tinterval" => PGArrayType "tinterval[]" "tinterval"
-instance PGType "circle" => PGType "circle[]"
-instance PGType "circle" => PGArrayType "circle[]" "circle"
-instance PGType "money" => PGType "money[]"
-instance PGType "money" => PGArrayType "money[]" "money"
-instance PGType "macaddr" => PGType "macaddr[]"
-instance PGType "macaddr" => PGArrayType "macaddr[]" "macaddr"
-instance PGType "inet" => PGType "inet[]"
-instance PGType "inet" => PGArrayType "inet[]" "inet"
-instance PGType "aclitem" => PGType "aclitem[]"
-instance PGType "aclitem" => PGArrayType "aclitem[]" "aclitem"
-instance PGType "bpchar" => PGType "bpchar[]"
-instance PGType "bpchar" => PGArrayType "bpchar[]" "bpchar"
-instance PGType "character varying" => PGType "character varying[]"
-instance PGType "character varying" => PGArrayType "character varying[]" "character varying"
-instance PGType "date" => PGType "date[]"
-instance PGType "date" => PGArrayType "date[]" "date"
-instance PGType "time without time zone" => PGType "time without time zone[]"
-instance PGType "time without time zone" => PGArrayType "time without time zone[]" "time without time zone"
-instance PGType "timestamp without time zone" => PGType "timestamp without time zone[]"
-instance PGType "timestamp without time zone" => PGArrayType "timestamp without time zone[]" "timestamp without time zone"
-instance PGType "timestamp with time zone" => PGType "timestamp with time zone[]"
-instance PGType "timestamp with time zone" => PGArrayType "timestamp with time zone[]" "timestamp with time zone"
-instance PGType "interval" => PGType "interval[]"
-instance PGType "interval" => PGArrayType "interval[]" "interval"
-instance PGType "time with time zone" => PGType "time with time zone[]"
-instance PGType "time with time zone" => PGArrayType "time with time zone[]" "time with time zone"
-instance PGType "bit" => PGType "bit[]"
-instance PGType "bit" => PGArrayType "bit[]" "bit"
-instance PGType "varbit" => PGType "varbit[]"
-instance PGType "varbit" => PGArrayType "varbit[]" "varbit"
-instance PGType "numeric" => PGType "numeric[]"
-instance PGType "numeric" => PGArrayType "numeric[]" "numeric"
-instance PGType "refcursor" => PGType "refcursor[]"
-instance PGType "refcursor" => PGArrayType "refcursor[]" "refcursor"
-instance PGType "regprocedure" => PGType "regprocedure[]"
-instance PGType "regprocedure" => PGArrayType "regprocedure[]" "regprocedure"
-instance PGType "regoper" => PGType "regoper[]"
-instance PGType "regoper" => PGArrayType "regoper[]" "regoper"
-instance PGType "regoperator" => PGType "regoperator[]"
-instance PGType "regoperator" => PGArrayType "regoperator[]" "regoperator"
-instance PGType "regclass" => PGType "regclass[]"
-instance PGType "regclass" => PGArrayType "regclass[]" "regclass"
-instance PGType "regtype" => PGType "regtype[]"
-instance PGType "regtype" => PGArrayType "regtype[]" "regtype"
-instance PGType "record" => PGType "record[]"
-instance PGType "record" => PGArrayType "record[]" "record"
-instance PGType "cstring" => PGType "cstring[]"
-instance PGType "cstring" => PGArrayType "cstring[]" "cstring"
-instance PGType "uuid" => PGType "uuid[]"
-instance PGType "uuid" => PGArrayType "uuid[]" "uuid"
-instance PGType "txid_snapshot" => PGType "txid_snapshot[]"
-instance PGType "txid_snapshot" => PGArrayType "txid_snapshot[]" "txid_snapshot"
-instance PGType "tsvector" => PGType "tsvector[]"
-instance PGType "tsvector" => PGArrayType "tsvector[]" "tsvector"
-instance PGType "tsquery" => PGType "tsquery[]"
-instance PGType "tsquery" => PGArrayType "tsquery[]" "tsquery"
-instance PGType "gtsvector" => PGType "gtsvector[]"
-instance PGType "gtsvector" => PGArrayType "gtsvector[]" "gtsvector"
-instance PGType "regconfig" => PGType "regconfig[]"
-instance PGType "regconfig" => PGArrayType "regconfig[]" "regconfig"
-instance PGType "regdictionary" => PGType "regdictionary[]"
-instance PGType "regdictionary" => PGArrayType "regdictionary[]" "regdictionary"
-instance PGType "int4range" => PGType "int4range[]"
-instance PGType "int4range" => PGArrayType "int4range[]" "int4range"
-instance PGType "numrange" => PGType "numrange[]"
-instance PGType "numrange" => PGArrayType "numrange[]" "numrange"
-instance PGType "tsrange" => PGType "tsrange[]"
-instance PGType "tsrange" => PGArrayType "tsrange[]" "tsrange"
-instance PGType "tstzrange" => PGType "tstzrange[]"
-instance PGType "tstzrange" => PGArrayType "tstzrange[]" "tstzrange"
-instance PGType "daterange" => PGType "daterange[]"
-instance PGType "daterange" => PGArrayType "daterange[]" "daterange"
-instance PGType "int8range" => PGType "int8range[]"
-instance PGType "int8range" => PGArrayType "int8range[]" "int8range"
+instance PGType "polygon" => PGType "polygon[]" where
+  type PGVal "polygon[]" = PGArray (PGVal "polygon")
+instance PGType "polygon" => PGArrayType "polygon[]" where
+  type PGElemType "polygon[]" = "polygon"
+instance PGType "line" => PGType "line[]" where
+  type PGVal "line[]" = PGArray (PGVal "line")
+instance PGType "line" => PGArrayType "line[]" where
+  type PGElemType "line[]" = "line"
+instance PGType "cidr" => PGType "cidr[]" where
+  type PGVal "cidr[]" = PGArray (PGVal "cidr")
+instance PGType "cidr" => PGArrayType "cidr[]" where
+  type PGElemType "cidr[]" = "cidr"
+instance PGType "real" => PGType "real[]" where
+  type PGVal "real[]" = PGArray (PGVal "real")
+instance PGType "real" => PGArrayType "real[]" where
+  type PGElemType "real[]" = "real"
+instance PGType "double precision" => PGType "double precision[]" where
+  type PGVal "double precision[]" = PGArray (PGVal "double precision")
+instance PGType "double precision" => PGArrayType "double precision[]" where
+  type PGElemType "double precision[]" = "double precision"
+instance PGType "abstime" => PGType "abstime[]" where
+  type PGVal "abstime[]" = PGArray (PGVal "abstime")
+instance PGType "abstime" => PGArrayType "abstime[]" where
+  type PGElemType "abstime[]" = "abstime"
+instance PGType "reltime" => PGType "reltime[]" where
+  type PGVal "reltime[]" = PGArray (PGVal "reltime")
+instance PGType "reltime" => PGArrayType "reltime[]" where
+  type PGElemType "reltime[]" = "reltime"
+instance PGType "tinterval" => PGType "tinterval[]" where
+  type PGVal "tinterval[]" = PGArray (PGVal "tinterval")
+instance PGType "tinterval" => PGArrayType "tinterval[]" where
+  type PGElemType "tinterval[]" = "tinterval"
+instance PGType "circle" => PGType "circle[]" where
+  type PGVal "circle[]" = PGArray (PGVal "circle")
+instance PGType "circle" => PGArrayType "circle[]" where
+  type PGElemType "circle[]" = "circle"
+instance PGType "money" => PGType "money[]" where
+  type PGVal "money[]" = PGArray (PGVal "money")
+instance PGType "money" => PGArrayType "money[]" where
+  type PGElemType "money[]" = "money"
+instance PGType "macaddr" => PGType "macaddr[]" where
+  type PGVal "macaddr[]" = PGArray (PGVal "macaddr")
+instance PGType "macaddr" => PGArrayType "macaddr[]" where
+  type PGElemType "macaddr[]" = "macaddr"
+instance PGType "inet" => PGType "inet[]" where
+  type PGVal "inet[]" = PGArray (PGVal "inet")
+instance PGType "inet" => PGArrayType "inet[]" where
+  type PGElemType "inet[]" = "inet"
+instance PGType "aclitem" => PGType "aclitem[]" where
+  type PGVal "aclitem[]" = PGArray (PGVal "aclitem")
+instance PGType "aclitem" => PGArrayType "aclitem[]" where
+  type PGElemType "aclitem[]" = "aclitem"
+instance PGType "bpchar" => PGType "bpchar[]" where
+  type PGVal "bpchar[]" = PGArray (PGVal "bpchar")
+instance PGType "bpchar" => PGArrayType "bpchar[]" where
+  type PGElemType "bpchar[]" = "bpchar"
+instance PGType "character varying" => PGType "character varying[]" where
+  type PGVal "character varying[]" = PGArray (PGVal "character varying")
+instance PGType "character varying" => PGArrayType "character varying[]" where
+  type PGElemType "character varying[]" = "character varying"
+instance PGType "date" => PGType "date[]" where
+  type PGVal "date[]" = PGArray (PGVal "date")
+instance PGType "date" => PGArrayType "date[]" where
+  type PGElemType "date[]" = "date"
+instance PGType "time without time zone" => PGType "time without time zone[]" where
+  type PGVal "time without time zone[]" = PGArray (PGVal "time without time zone")
+instance PGType "time without time zone" => PGArrayType "time without time zone[]" where
+  type PGElemType "time without time zone[]" = "time without time zone"
+instance PGType "timestamp without time zone" => PGType "timestamp without time zone[]" where
+  type PGVal "timestamp without time zone[]" = PGArray (PGVal "timestamp without time zone")
+instance PGType "timestamp without time zone" => PGArrayType "timestamp without time zone[]" where
+  type PGElemType "timestamp without time zone[]" = "timestamp without time zone"
+instance PGType "timestamp with time zone" => PGType "timestamp with time zone[]" where
+  type PGVal "timestamp with time zone[]" = PGArray (PGVal "timestamp with time zone")
+instance PGType "timestamp with time zone" => PGArrayType "timestamp with time zone[]" where
+  type PGElemType "timestamp with time zone[]" = "timestamp with time zone"
+instance PGType "interval" => PGType "interval[]" where
+  type PGVal "interval[]" = PGArray (PGVal "interval")
+instance PGType "interval" => PGArrayType "interval[]" where
+  type PGElemType "interval[]" = "interval"
+instance PGType "time with time zone" => PGType "time with time zone[]" where
+  type PGVal "time with time zone[]" = PGArray (PGVal "time with time zone")
+instance PGType "time with time zone" => PGArrayType "time with time zone[]" where
+  type PGElemType "time with time zone[]" = "time with time zone"
+instance PGType "bit" => PGType "bit[]" where
+  type PGVal "bit[]" = PGArray (PGVal "bit")
+instance PGType "bit" => PGArrayType "bit[]" where
+  type PGElemType "bit[]" = "bit"
+instance PGType "varbit" => PGType "varbit[]" where
+  type PGVal "varbit[]" = PGArray (PGVal "varbit")
+instance PGType "varbit" => PGArrayType "varbit[]" where
+  type PGElemType "varbit[]" = "varbit"
+instance PGType "numeric" => PGType "numeric[]" where
+  type PGVal "numeric[]" = PGArray (PGVal "numeric")
+instance PGType "numeric" => PGArrayType "numeric[]" where
+  type PGElemType "numeric[]" = "numeric"
+instance PGType "refcursor" => PGType "refcursor[]" where
+  type PGVal "refcursor[]" = PGArray (PGVal "refcursor")
+instance PGType "refcursor" => PGArrayType "refcursor[]" where
+  type PGElemType "refcursor[]" = "refcursor"
+instance PGType "regprocedure" => PGType "regprocedure[]" where
+  type PGVal "regprocedure[]" = PGArray (PGVal "regprocedure")
+instance PGType "regprocedure" => PGArrayType "regprocedure[]" where
+  type PGElemType "regprocedure[]" = "regprocedure"
+instance PGType "regoper" => PGType "regoper[]" where
+  type PGVal "regoper[]" = PGArray (PGVal "regoper")
+instance PGType "regoper" => PGArrayType "regoper[]" where
+  type PGElemType "regoper[]" = "regoper"
+instance PGType "regoperator" => PGType "regoperator[]" where
+  type PGVal "regoperator[]" = PGArray (PGVal "regoperator")
+instance PGType "regoperator" => PGArrayType "regoperator[]" where
+  type PGElemType "regoperator[]" = "regoperator"
+instance PGType "regclass" => PGType "regclass[]" where
+  type PGVal "regclass[]" = PGArray (PGVal "regclass")
+instance PGType "regclass" => PGArrayType "regclass[]" where
+  type PGElemType "regclass[]" = "regclass"
+instance PGType "regtype" => PGType "regtype[]" where
+  type PGVal "regtype[]" = PGArray (PGVal "regtype")
+instance PGType "regtype" => PGArrayType "regtype[]" where
+  type PGElemType "regtype[]" = "regtype"
+instance PGType "record" => PGType "record[]" where
+  type PGVal "record[]" = PGArray (PGVal "record")
+instance PGType "record" => PGArrayType "record[]" where
+  type PGElemType "record[]" = "record"
+instance PGType "cstring" => PGType "cstring[]" where
+  type PGVal "cstring[]" = PGArray (PGVal "cstring")
+instance PGType "cstring" => PGArrayType "cstring[]" where
+  type PGElemType "cstring[]" = "cstring"
+instance PGType "uuid" => PGType "uuid[]" where
+  type PGVal "uuid[]" = PGArray (PGVal "uuid")
+instance PGType "uuid" => PGArrayType "uuid[]" where
+  type PGElemType "uuid[]" = "uuid"
+instance PGType "txid_snapshot" => PGType "txid_snapshot[]" where
+  type PGVal "txid_snapshot[]" = PGArray (PGVal "txid_snapshot")
+instance PGType "txid_snapshot" => PGArrayType "txid_snapshot[]" where
+  type PGElemType "txid_snapshot[]" = "txid_snapshot"
+instance PGType "tsvector" => PGType "tsvector[]" where
+  type PGVal "tsvector[]" = PGArray (PGVal "tsvector")
+instance PGType "tsvector" => PGArrayType "tsvector[]" where
+  type PGElemType "tsvector[]" = "tsvector"
+instance PGType "tsquery" => PGType "tsquery[]" where
+  type PGVal "tsquery[]" = PGArray (PGVal "tsquery")
+instance PGType "tsquery" => PGArrayType "tsquery[]" where
+  type PGElemType "tsquery[]" = "tsquery"
+instance PGType "gtsvector" => PGType "gtsvector[]" where
+  type PGVal "gtsvector[]" = PGArray (PGVal "gtsvector")
+instance PGType "gtsvector" => PGArrayType "gtsvector[]" where
+  type PGElemType "gtsvector[]" = "gtsvector"
+instance PGType "regconfig" => PGType "regconfig[]" where
+  type PGVal "regconfig[]" = PGArray (PGVal "regconfig")
+instance PGType "regconfig" => PGArrayType "regconfig[]" where
+  type PGElemType "regconfig[]" = "regconfig"
+instance PGType "regdictionary" => PGType "regdictionary[]" where
+  type PGVal "regdictionary[]" = PGArray (PGVal "regdictionary")
+instance PGType "regdictionary" => PGArrayType "regdictionary[]" where
+  type PGElemType "regdictionary[]" = "regdictionary"
+instance PGType "int4range" => PGType "int4range[]" where
+  type PGVal "int4range[]" = PGArray (PGVal "int4range")
+instance PGType "int4range" => PGArrayType "int4range[]" where
+  type PGElemType "int4range[]" = "int4range"
+instance PGType "numrange" => PGType "numrange[]" where
+  type PGVal "numrange[]" = PGArray (PGVal "numrange")
+instance PGType "numrange" => PGArrayType "numrange[]" where
+  type PGElemType "numrange[]" = "numrange"
+instance PGType "tsrange" => PGType "tsrange[]" where
+  type PGVal "tsrange[]" = PGArray (PGVal "tsrange")
+instance PGType "tsrange" => PGArrayType "tsrange[]" where
+  type PGElemType "tsrange[]" = "tsrange"
+instance PGType "tstzrange" => PGType "tstzrange[]" where
+  type PGVal "tstzrange[]" = PGArray (PGVal "tstzrange")
+instance PGType "tstzrange" => PGArrayType "tstzrange[]" where
+  type PGElemType "tstzrange[]" = "tstzrange"
+instance PGType "daterange" => PGType "daterange[]" where
+  type PGVal "daterange[]" = PGArray (PGVal "daterange")
+instance PGType "daterange" => PGArrayType "daterange[]" where
+  type PGElemType "daterange[]" = "daterange"
+instance PGType "int8range" => PGType "int8range[]" where
+  type PGVal "int8range[]" = PGArray (PGVal "int8range")
+instance PGType "int8range" => PGArrayType "int8range[]" where
+  type PGElemType "int8range[]" = "int8range"
 
diff --git a/Database/PostgreSQL/Typed/Dynamic.hs b/Database/PostgreSQL/Typed/Dynamic.hs
--- a/Database/PostgreSQL/Typed/Dynamic.hs
+++ b/Database/PostgreSQL/Typed/Dynamic.hs
@@ -1,4 +1,7 @@
-{-# LANGUAGE CPP, FlexibleInstances, MultiParamTypeClasses, ScopedTypeVariables, FunctionalDependencies, UndecidableInstances, DataKinds, DefaultSignatures, PatternGuards, GADTs, TemplateHaskell, AllowAmbiguousTypes #-}
+{-# LANGUAGE CPP, FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, ScopedTypeVariables, DataKinds, DefaultSignatures, TemplateHaskell, TypeFamilies #-}
+#if __GLASGOW_HASKELL__ >= 800
+{-# LANGUAGE UndecidableSuperClasses #-}
+#endif
 -- |
 -- Module: Database.PostgreSQL.Typed.Dynamic
 -- Copyright: 2015 Dylan Simon
@@ -8,6 +11,11 @@
 
 module Database.PostgreSQL.Typed.Dynamic 
   ( PGRep(..)
+  , pgTypeOf
+  , pgTypeOfProxy
+  , pgEncodeRep
+  , pgDecodeRep
+  , pgLiteralRep
   , pgLiteralString
   , pgSafeLiteral
   , pgSafeLiteralString
@@ -17,22 +25,28 @@
 #if !MIN_VERSION_base(4,8,0)
 import Control.Applicative ((<$>))
 #endif
+#ifdef VERSION_aeson
+import qualified Data.Aeson as JSON
+#endif
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Char8 as BSC
+import Data.ByteString.Internal (w2c)
 import qualified Data.ByteString.Lazy as BSL
-import Data.Monoid ((<>))
 import Data.Int
-#ifdef USE_SCIENTIFIC
+import Data.Monoid ((<>))
+import Data.Proxy (Proxy)
+#ifdef VERSION_scientific
 import Data.Scientific (Scientific)
 #endif
 import Data.String (fromString)
-#ifdef USE_TEXT
+#ifdef VERSION_text
 import qualified Data.Text as T
 #endif
 import qualified Data.Time as Time
-#ifdef USE_UUID
+#ifdef VERSION_uuid
 import qualified Data.UUID as UUID
 #endif
+import GHC.TypeLits (Symbol)
 import Language.Haskell.Meta.Parse (parseExp)
 import qualified Language.Haskell.TH as TH
 
@@ -40,71 +54,96 @@
 import Database.PostgreSQL.Typed.SQLToken
 
 -- |Represents canonical/default PostgreSQL representation for various Haskell types, allowing convenient type-driven marshalling.
-class PGType t => PGRep t a | a -> t where
-  pgTypeOf :: a -> PGTypeName t
-  pgTypeOf _ = PGTypeProxy
-  pgEncodeRep :: a -> PGValue
-  default pgEncodeRep :: PGParameter t a => a -> PGValue
-  pgEncodeRep x = pgEncodeValue unknownPGTypeEnv (pgTypeOf x) x
-  -- |Produce a literal value for interpolation in a SQL statement.  Using 'pgSafeLiteral' is usually safer as it includes type cast.
-  pgLiteralRep :: a -> BS.ByteString
-  default pgLiteralRep :: PGParameter t a => a -> BS.ByteString
-  pgLiteralRep x = pgLiteral (pgTypeOf x) x
-  pgDecodeRep :: PGValue -> a
-#ifdef USE_BINARY_XXX
-  default pgDecodeRep :: PGBinaryColumn t a => PGValue -> a
-  pgDecodeRep (PGBinaryValue v) = pgDecodeBinary unknownPGTypeEnv (PGTypeProxy :: PGTypeName t) v
-#else
-  default pgDecodeRep :: PGColumn t a => PGValue -> a
-#endif
-  pgDecodeRep (PGTextValue v) = pgDecode (PGTypeProxy :: PGTypeName t) v
-  pgDecodeRep _ = error $ "pgDecodeRep " ++ pgTypeName (PGTypeProxy :: PGTypeName t) ++ ": unsupported PGValue"
+class (PGParameter (PGRepType a) a, PGColumn (PGRepType a) a) => PGRep a where
+  -- |The PostgreSOL type that this type should be converted to.
+  type PGRepType a :: Symbol
 
+pgTypeOf :: a -> PGTypeID (PGRepType a)
+pgTypeOf _ = PGTypeProxy
+
+pgTypeOfProxy :: Proxy a -> PGTypeID (PGRepType a)
+pgTypeOfProxy _ = PGTypeProxy
+
+-- |Encode a value using 'pgEncodeValue'.
+pgEncodeRep :: PGRep a => a -> PGValue
+pgEncodeRep x = pgEncodeValue unknownPGTypeEnv (pgTypeOf x) x
+
+-- |Produce a literal value for interpolation in a SQL statement using 'pgLiteral'.  Using 'pgSafeLiteral' is usually safer as it includes type cast.
+pgLiteralRep :: PGRep a => a -> BS.ByteString
+pgLiteralRep x = pgLiteral (pgTypeOf x) x
+
+-- |Decode a value using 'pgDecodeValue'.
+pgDecodeRep :: forall a . PGRep a => PGValue -> a
+pgDecodeRep = pgDecodeValue unknownPGTypeEnv (PGTypeProxy :: PGTypeID (PGRepType a))
+
 -- |Produce a raw SQL literal from a value. Using 'pgSafeLiteral' is usually safer when interpolating in a SQL statement.
-pgLiteralString :: PGRep t a => a -> String
+pgLiteralString :: PGRep a => a -> String
 pgLiteralString = BSC.unpack . pgLiteralRep
 
 -- |Produce a safely type-cast literal value for interpolation in a SQL statement, e.g., "'123'::integer".
-pgSafeLiteral :: PGRep t a => a -> BS.ByteString
-pgSafeLiteral x = pgLiteralRep x <> BSC.pack "::" <> fromString (pgTypeName (pgTypeOf x))
+pgSafeLiteral :: PGRep a => a -> BS.ByteString
+pgSafeLiteral x = pgLiteralRep x <> BSC.pack "::" <> pgNameBS (pgTypeName (pgTypeOf x))
 
 -- |Identical to @'BSC.unpack' . 'pgSafeLiteral'@ but more efficient.
-pgSafeLiteralString :: PGRep t a => a -> String
-pgSafeLiteralString x = pgLiteralString x ++ "::" ++ pgTypeName (pgTypeOf x)
+pgSafeLiteralString :: PGRep a => a -> String
+pgSafeLiteralString x = pgLiteralString x ++ "::" ++ map w2c (pgNameBytes (pgTypeName (pgTypeOf x)))
 
-instance PGRep t a => PGRep t (Maybe a) where
-  pgEncodeRep Nothing = PGNullValue
-  pgEncodeRep (Just x) = pgEncodeRep x
-  pgLiteralRep Nothing = BSC.pack "NULL"
-  pgLiteralRep (Just x) = pgLiteralRep x
-  pgDecodeRep PGNullValue = Nothing
-  pgDecodeRep v = Just (pgDecodeRep v)
+instance PGRep a => PGRep (Maybe a) where
+  type PGRepType (Maybe a) = PGRepType a
 
-instance PGRep "boolean" Bool
-instance PGRep "oid" OID
-instance PGRep "smallint" Int16
-instance PGRep "integer" Int32
-instance PGRep "bigint" Int64
-instance PGRep "real" Float
-instance PGRep "double precision" Double
-instance PGRep "\"char\"" Char
-instance PGRep "text" String
-instance PGRep "text" BS.ByteString
-#ifdef USE_TEXT
-instance PGRep "text" T.Text
+instance PGRep () where
+  type PGRepType () = "void"
+instance PGRep Bool where
+  type PGRepType Bool = "boolean"
+instance PGRep OID where
+  type PGRepType OID = "oid"
+instance PGRep Int16 where
+  type PGRepType Int16 = "smallint"
+instance PGRep Int32 where
+  type PGRepType Int32 = "integer"
+instance PGRep Int64 where
+  type PGRepType Int64 = "bigint"
+instance PGRep Float where
+  type PGRepType Float = "real"
+instance PGRep Double where
+  type PGRepType Double = "double precision"
+instance PGRep Char where
+  type PGRepType Char = "\"char\""
+instance PGRep String where
+  type PGRepType String = "text"
+instance PGRep BS.ByteString where
+  type PGRepType BS.ByteString = "text"
+instance PGRep PGName where
+  type PGRepType PGName = "text" -- superset of "name"
+#ifdef VERSION_text
+instance PGRep T.Text where
+  type PGRepType T.Text = "text"
 #endif
-instance PGRep "date" Time.Day
-instance PGRep "time without time zone" Time.TimeOfDay
-instance PGRep "time with time zone" (Time.TimeOfDay, Time.TimeZone)
-instance PGRep "timestamp without time zone" Time.LocalTime
-instance PGRep "timestamp with time zone" Time.UTCTime
-instance PGRep "interval" Time.DiffTime
-instance PGRep "numeric" Rational
-#ifdef USE_SCIENTIFIC
-instance PGRep "numeric" Scientific
+instance PGRep Time.Day where
+  type PGRepType Time.Day = "date"
+instance PGRep Time.TimeOfDay where
+  type PGRepType Time.TimeOfDay = "time without time zone"
+instance PGRep (Time.TimeOfDay, Time.TimeZone) where
+  type PGRepType (Time.TimeOfDay, Time.TimeZone) = "time with time zone"
+instance PGRep Time.LocalTime where
+  type PGRepType Time.LocalTime = "timestamp without time zone"
+instance PGRep Time.UTCTime where
+  type PGRepType Time.UTCTime = "timestamp with time zone"
+instance PGRep Time.DiffTime where
+  type PGRepType Time.DiffTime = "interval"
+instance PGRep Rational where
+  type PGRepType Rational = "numeric"
+#ifdef VERSION_scientific
+instance PGRep Scientific where
+  type PGRepType Scientific = "numeric"
 #endif
-#ifdef USE_UUID
-instance PGRep "uuid" UUID.UUID
+#ifdef VERSION_uuid
+instance PGRep UUID.UUID where
+  type PGRepType UUID.UUID = "uuid"
+#endif
+#ifdef VERSION_aeson
+instance PGRep JSON.Value where
+  type PGRepType JSON.Value = "jsonb"
 #endif
 
 -- |Create an expression that literally substitutes each instance of @${expr}@ for the result of @pgSafeLiteral expr@, producing a lazy 'BSL.ByteString'.
diff --git a/Database/PostgreSQL/Typed/Enum.hs b/Database/PostgreSQL/Typed/Enum.hs
--- a/Database/PostgreSQL/Typed/Enum.hs
+++ b/Database/PostgreSQL/Typed/Enum.hs
@@ -1,97 +1,135 @@
 {-# LANGUAGE CPP, TemplateHaskell, FlexibleInstances, MultiParamTypeClasses, DataKinds #-}
+{-# LANGUAGE OverloadedStrings #-}
+#if __GLASGOW_HASKELL__ >= 800
+{-# LANGUAGE UndecidableSuperClasses #-}
+#endif
 -- |
 -- Module: Database.PostgreSQL.Typed.Enum
 -- Copyright: 2015 Dylan Simon
 -- 
 -- Support for PostgreSQL enums.
 
-module Database.PostgreSQL.Typed.Enum 
-  ( PGEnum
-  , pgEnumValues
-  , makePGEnum
+module Database.PostgreSQL.Typed.Enum
+  ( PGEnum(..)
+  , dataPGEnum
   ) where
 
-import Control.Monad (when)
+import           Control.Arrow ((&&&))
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Char8 as BSC
 import qualified Data.ByteString.Lazy as BSL
-import Data.Ix (Ix)
-import Data.String (fromString)
-import Data.Typeable (Typeable)
+import           Data.Ix (Ix)
+import           Data.Maybe (fromJust, fromMaybe)
+import           Data.Tuple (swap)
+import           Data.Typeable (Typeable)
 import qualified Language.Haskell.TH as TH
 
-import Database.PostgreSQL.Typed.Protocol
-import Database.PostgreSQL.Typed.TH
 import Database.PostgreSQL.Typed.Types
 import Database.PostgreSQL.Typed.Dynamic
-
--- |A type based on a PostgreSQL enum. Automatically instantiated by 'makePGEnum'.
-class (Eq a, Ord a, Enum a, Bounded a, Show a) => PGEnum a
+import Database.PostgreSQL.Typed.Protocol
+import Database.PostgreSQL.Typed.TypeCache
+import Database.PostgreSQL.Typed.TH
 
--- |List of all the values in the enum along with their database names.
-pgEnumValues :: PGEnum a => [(a, String)]
-pgEnumValues = map (\e -> (e, show e)) $ enumFromTo minBound maxBound
+-- |A type based on a PostgreSQL enum. Automatically instantiated by 'dataPGEnum'.
+class (Eq a, Ord a, Enum a, Bounded a, PGRep a) => PGEnum a where
+  {-# MINIMAL pgEnumName | pgEnumValues #-}
+  -- |The database name of a value.
+  pgEnumName :: a -> PGName
+  pgEnumName a = fromJust $ lookup a pgEnumValues
+  -- |Lookup a value matching the given database name.
+  pgEnumValue :: PGName -> Maybe a
+  pgEnumValue n = lookup n $ map swap pgEnumValues
+  -- |List of all the values in the enum along with their database names.
+  pgEnumValues :: [(a, PGName)]
+  pgEnumValues = map (id &&& pgEnumName) $ enumFromTo minBound maxBound
 
 -- |Create a new enum type corresponding to the given PostgreSQL enum type.
--- For example, if you have @CREATE TYPE foo AS ENUM (\'abc\', \'DEF\');@, then
--- @makePGEnum \"foo\" \"Foo\" (\"Foo_\"++)@ will be equivalent to:
+-- For example, if you have @CREATE TYPE foo AS ENUM (\'abc\', \'DEF\')@, then
+-- @dataPGEnum \"Foo\" \"foo\" (\"Foo_\"++)@ will be equivalent to:
 -- 
 -- > data Foo = Foo_abc | Foo_DEF deriving (Eq, Ord, Enum, Bounded, Typeable)
--- > instance Show Foo where show Foo_abc = "abc" ...
--- > instance PGType "foo"
+-- > instance PGType "foo" where PGVal "foo" = Foo
 -- > instance PGParameter "foo" Foo where ...
 -- > instance PGColumn "foo" Foo where ...
--- > instance PGRep "foo" Foo
+-- > instance PGRep Foo where PGRepType = "foo"
 -- > instance PGEnum Foo where pgEnumValues = [(Foo_abc, "abc"), (Foo_DEF, "DEF")]
 --
--- Requires language extensions: TemplateHaskell, FlexibleInstances, MultiParamTypeClasses, DeriveDataTypeable, DataKinds
-makePGEnum :: String -- ^ PostgreSQL enum type name
-  -> String -- ^ Haskell type to create
-  -> (String -> String) -- ^ How to generate constructor names from enum values, e.g. @(\"Type_\"++)@
+-- Requires language extensions: TemplateHaskell, FlexibleInstances, MultiParamTypeClasses, DeriveDataTypeable, DataKinds, TypeFamilies
+dataPGEnum :: String -- ^ Haskell type to create
+  -> PGName -- ^ PostgreSQL enum type name
+  -> (String -> String) -- ^ How to generate constructor names from enum values, e.g. @(\"Type_\"++)@ (input is 'pgNameString')
   -> TH.DecsQ
-makePGEnum name typs valnf = do
-  (_, vals) <- TH.runIO $ withTPGConnection $ \c ->
-    pgSimpleQuery c $ BSL.fromChunks [BSC.pack "SELECT enumlabel FROM pg_catalog.pg_enum JOIN pg_catalog.pg_type t ON enumtypid = t.oid WHERE typtype = 'e' AND format_type(t.oid, -1) = ", pgQuote (fromString name), BSC.pack " ORDER BY enumsortorder"]
-  when (null vals) $ fail $ "makePGEnum: enum " ++ name ++ " not found"
-  let 
-    valn = map (\[PGTextValue v] -> let u = BSC.unpack v in (TH.mkName $ valnf u, map (TH.IntegerL . fromIntegral) $ BS.unpack v, TH.StringL u)) vals
+dataPGEnum typs pgenum valnf = do
+  (pgid, vals) <- TH.runIO $ withTPGTypeConnection $ \tpg -> do
+    vals <- map (\([eo, v]) -> (pgDecodeRep eo, pgDecodeRep v)) . snd
+      <$> pgSimpleQuery (pgConnection tpg) (BSL.fromChunks
+        [ "SELECT enumtypid, enumlabel"
+        ,  " FROM pg_catalog.pg_enum"
+        , " WHERE enumtypid = ", pgLiteralRep pgenum, "::regtype"
+        , " ORDER BY enumsortorder"
+        ])
+    case vals of
+      [] -> fail $ "dataPGEnum " ++ typs ++ " = " ++ show pgenum ++ ": no values found"
+      (eo, _):_ -> do
+        et <- maybe (fail $ "dataPGEnum " ++ typs ++ " = " ++ show pgenum ++ ": enum type not found (you may need to use reloadTPGTypes or adjust search_path)") return
+          =<< lookupPGType tpg eo
+        return (et, map snd vals)
+  let valn = map (TH.mkName . valnf . pgNameString &&& map (TH.IntegerL . fromIntegral) . pgNameBytes) vals
+      typl = TH.LitT (TH.StrTyLit $ pgNameString pgid)
   dv <- TH.newName "x"
-  return
+  return $
     [ TH.DataD [] typn []
 #if MIN_VERSION_template_haskell(2,11,0)
       Nothing
 #endif
-      (map (\(n, _, _) -> TH.NormalC n []) valn) $
+      (map (\(n, _) -> TH.NormalC n []) valn) $
 #if MIN_VERSION_template_haskell(2,11,0)
       map TH.ConT
 #endif
       [''Eq, ''Ord, ''Enum, ''Ix, ''Bounded, ''Typeable]
-    , instanceD [] (TH.ConT ''Show `TH.AppT` typt)
-      [ TH.FunD 'show $ map (\(n, _, v) -> TH.Clause [TH.ConP n []]
-        (TH.NormalB $ TH.LitE v) []) valn
+    , instanceD [] (TH.ConT ''PGType `TH.AppT` typl)
+      [ TH.TySynInstD ''PGVal $ TH.TySynEqn [typl] typt
       ]
-    , instanceD [] (TH.ConT ''PGType `TH.AppT` typl) []
     , instanceD [] (TH.ConT ''PGParameter `TH.AppT` typl `TH.AppT` typt)
-      [ TH.FunD 'pgEncode $ map (\(n, l, _) -> TH.Clause [TH.WildP, TH.ConP n []]
-        (TH.NormalB $ TH.VarE 'BS.pack `TH.AppE` TH.ListE (map TH.LitE l)) []) valn
+      [ TH.FunD 'pgEncode [TH.Clause [TH.WildP, TH.VarP dv]
+        (TH.NormalB $ TH.VarE 'pgNameBS `TH.AppE` (TH.VarE 'pgEnumName `TH.AppE` TH.VarE dv))
+        []]
       ]
     , instanceD [] (TH.ConT ''PGColumn `TH.AppT` typl `TH.AppT` typt)
       [ TH.FunD 'pgDecode [TH.Clause [TH.WildP, TH.VarP dv]
-        (TH.NormalB $ TH.CaseE (TH.VarE 'BS.unpack `TH.AppE` TH.VarE dv) $ map (\(n, l, _) ->
-          TH.Match (TH.ListP (map TH.LitP l)) (TH.NormalB $ TH.ConE n) []) valn ++
-          [TH.Match TH.WildP (TH.NormalB $ TH.AppE (TH.VarE 'error) $
-            TH.InfixE (Just $ TH.LitE (TH.StringL ("pgDecode " ++ name ++ ": "))) (TH.VarE '(++)) (Just $ TH.VarE 'BSC.unpack `TH.AppE` TH.VarE dv))
-            []])
-        []] 
+        (TH.NormalB $ TH.VarE 'fromMaybe
+          `TH.AppE` (TH.AppE (TH.VarE 'error) $
+            TH.InfixE (Just $ TH.LitE (TH.StringL ("pgEnumValue " ++ show pgid ++ ": "))) (TH.VarE '(++)) (Just $ TH.VarE 'BSC.unpack `TH.AppE` TH.VarE dv))
+          `TH.AppE` (TH.VarE 'pgEnumValue `TH.AppE` (TH.ConE 'PGName
+            `TH.AppE` (TH.VarE 'BS.unpack `TH.AppE` TH.VarE dv))))
+        []]
       ]
-    , instanceD [] (TH.ConT ''PGRep `TH.AppT` typl `TH.AppT` typt) []
-    , instanceD [] (TH.ConT ''PGEnum `TH.AppT` typt) []
+    , instanceD [] (TH.ConT ''PGRep `TH.AppT` typt)
+      [ TH.TySynInstD ''PGRepType $ TH.TySynEqn [typt] typl
+      ]
+    , instanceD [] (TH.ConT ''PGEnum `TH.AppT` typt)
+      [ TH.FunD 'pgEnumName $ map (\(n, l) -> TH.Clause [TH.ConP n []]
+        (TH.NormalB $ namelit l)
+        []) valn
+      , TH.FunD 'pgEnumValue $ map (\(n, l) ->
+          TH.Clause [TH.ConP 'PGName [TH.ListP (map TH.LitP l)]]
+            (TH.NormalB $ TH.ConE 'Just `TH.AppE` TH.ConE n)
+            []) valn
+          ++ [TH.Clause [TH.WildP] (TH.NormalB $ TH.ConE 'Nothing) []]
+      , TH.FunD 'pgEnumValues [TH.Clause []
+        (TH.NormalB $ TH.ListE $ map (\(n, l) ->
+          TH.ConE '(,) `TH.AppE` TH.ConE n `TH.AppE` namelit l) valn)
+        []]
+      ]
+    , TH.PragmaD $ TH.AnnP (TH.TypeAnnotation typn) $ namelit $ map (TH.IntegerL . fromIntegral) $ pgNameBytes pgid
     ]
+    ++ map (\(n, l) ->
+      TH.PragmaD $ TH.AnnP (TH.ValueAnnotation n) $ namelit l) valn
   where
   typn = TH.mkName typs
   typt = TH.ConT typn
-  typl = TH.LitT (TH.StrTyLit name)
   instanceD = TH.InstanceD
 #if MIN_VERSION_template_haskell(2,11,0)
       Nothing
 #endif
+  namelit l = TH.ConE 'PGName `TH.AppE` TH.ListE (map TH.LitE l)
diff --git a/Database/PostgreSQL/Typed/HDBC.hs b/Database/PostgreSQL/Typed/HDBC.hs
--- a/Database/PostgreSQL/Typed/HDBC.hs
+++ b/Database/PostgreSQL/Typed/HDBC.hs
@@ -10,9 +10,10 @@
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
 module Database.PostgreSQL.Typed.HDBC
-  ( Connection, connectionPG
+  ( Connection
   , connect
   , fromPGConnection
+  , withPGConnection
   , reloadTypes
   , connectionFetchSize
   , setFetchSize
@@ -39,10 +40,10 @@
 import System.Mem.Weak (addFinalizer)
 import Text.Read (readMaybe)
 
-import Database.PostgreSQL.Typed.Protocol
 import Database.PostgreSQL.Typed.Types
+import Database.PostgreSQL.Typed.Protocol
 import Database.PostgreSQL.Typed.Dynamic
-import Database.PostgreSQL.Typed.TH
+import Database.PostgreSQL.Typed.TypeCache
 import Database.PostgreSQL.Typed.SQLToken
 import Paths_postgresql_typed (version)
 
@@ -54,7 +55,7 @@
 --   3. It provides a mutex around the underlying 'PGConnection' for thread-safety
 --
 data Connection = Connection
-  { connectionPG :: MVar PGConnection -- ^Access the underlying 'PGConnection' directly. You must be careful to ensure that the first invariant is preserved: you should not call 'pgBegin', 'pgCommit', or 'pgRollback' on it. All other operations should be safe.
+  { connectionPG :: MVar PGConnection
   , connectionServerVer :: String
   , connectionTypes :: IntMap.IntMap SqlType
   , connectionFetchSize :: Word32 -- ^Number of rows to fetch (and cache) with 'HDBC.execute' and each time 'HDBC.fetchRow' requires more rows. A higher value will result in fewer round-trips to the database but potentially more wasted data. Defaults to 1. 0 means fetch all rows.
@@ -71,8 +72,9 @@
     , HDBC.seErrorMsg = f 'S' ++ ": " ++ f 'M' ++ if null fD then fD else '\n':fD
     }
 
-withPG :: Connection -> (PGConnection -> IO a) -> IO a
-withPG c = sqlError . withMVar (connectionPG c)
+-- |Use the underlying 'PGConnection' directly. You must be careful to ensure that the first invariant is preserved: you should not call 'pgBegin', 'pgCommit', or 'pgRollback' on it. All other operations should be safe.
+withPGConnection :: Connection -> (PGConnection -> IO a) -> IO a
+withPGConnection c = sqlError . withMVar (connectionPG c)
 
 takePGConnection :: PGConnection -> IO (MVar PGConnection)
 takePGConnection pg = do
@@ -101,9 +103,9 @@
 -- |Reload the table of all types from the database.
 -- This may be needed if you make structural changes to the database.
 reloadTypes :: Connection -> IO Connection
-reloadTypes c = withPG c $ \pg -> do
-  t <- pgLoadTypes pg
-  return c{ connectionTypes = IntMap.map (sqlType $ pgTypeEnv pg) t }
+reloadTypes c = withPGConnection c $ \pg -> do
+  t <- pgGetTypes pg
+  return c{ connectionTypes = IntMap.map (sqlType (pgTypeEnv pg) . pgNameString) t }
 
 -- |Change the 'connectionFetchSize' for new 'HDBC.Statement's created with 'HDBC.prepare'.
 -- Ideally this could be set with each call to 'HDBC.execute' and 'HDBC.fetchRow', but the HDBC interface provides no way to do this.
@@ -149,26 +151,26 @@
   } where t = IntMap.findWithDefault (sqlType (pgTypeEnv pg) $ show colType) (fromIntegral colType) (connectionTypes c)
 
 instance HDBC.IConnection Connection where
-  disconnect c = withPG c
+  disconnect c = withPGConnection c
     pgDisconnectOnce
-  commit c = withPG c $ \pg -> do
+  commit c = withPGConnection c $ \pg -> do
     pgCommitAll pg
     pgBegin pg
-  rollback c = withPG c $ \pg -> do
+  rollback c = withPGConnection c $ \pg -> do
     pgRollbackAll pg
     pgBegin pg
-  runRaw c q = withPG c $ \pg ->
+  runRaw c q = withPGConnection c $ \pg ->
     pgSimpleQueries_ pg $ sqls q
-  run c q v = withPG c $ \pg -> do
+  run c q v = withPGConnection c $ \pg -> do
     let q' = sqls $ show $ placeholders 1 $ sqlTokens q
         v' = map encode v
     fromMaybe 0 <$> pgRun pg q' [] v'
   prepare c q = do
     let q' = sqls $ show $ placeholders 1 $ sqlTokens q
-    n <- withPG c $ \pg -> pgPrepare pg q' []
+    n <- withPGConnection c $ \pg -> pgPrepare pg q' []
     cr <- newIORef $ error "Cursor"
     let
-      execute v = withPG c $ \pg -> do
+      execute v = withPGConnection c $ \pg -> do
         d <- pgBind pg n (map encode v)
         (r, e) <- pgFetch pg n (connectionFetchSize c)
         modifyIORef' cr $ \p -> p
@@ -181,10 +183,10 @@
         { HDBC.execute = execute
         , HDBC.executeRaw = void $ execute []
         , HDBC.executeMany = mapM_ execute
-        , HDBC.finish = withPG c $ \pg -> do
+        , HDBC.finish = withPGConnection c $ \pg -> do
           writeIORef cr $ noCursor stmt
           pgClose pg n
-        , HDBC.fetchRow = withPG c $ \pg -> do
+        , HDBC.fetchRow = withPGConnection c $ \pg -> do
           p <- readIORef cr
           fmap (zipWith colDescDecode (cursorDesc p)) <$> case cursorRow p of
             [] | cursorActive p -> do
@@ -207,9 +209,9 @@
           map (colDescName &&& colDesc) . cursorDesc <$> readIORef cr
         }
     writeIORef cr $ noCursor stmt
-    addFinalizer stmt $ withPG c $ \pg -> pgClose pg n
+    addFinalizer stmt $ withPGConnection c $ \pg -> pgClose pg n
     return stmt
-  clone c = withPG c $ \pg -> do
+  clone c = withPGConnection c $ \pg -> do
     pg' <- pgConnect $ pgConnectionDatabase pg
     pgv <- takePGConnection pg'
     return c{ connectionPG = pgv }
@@ -219,41 +221,34 @@
   proxiedClientVer = HDBC.hdbcClientVer
   dbServerVer = connectionServerVer
   dbTransactionSupport _ = True
-  getTables c = withPG c $ \pg ->
+  getTables c = withPGConnection c $ \pg ->
     map (pgDecodeRep . head) . snd <$> pgSimpleQuery pg (BSLC.fromChunks
-      [ "SELECT relname "
-      ,   "FROM pg_class "
-      ,   "JOIN pg_namespace "
-      ,     "ON relnamespace = pg_namespace.oid "
-      ,  "WHERE nspname = ANY (current_schemas(false)) "
-      ,    "AND relkind IN ('r','v','m','f')"
-      ])
-  describeTable c t = withPG c $ \pg -> do
-    let makecol ~[attname, attrelid, attnum, atttypid, attlen, atttypmod, attnotnull] =
-          colDescName &&& colDesc $ getType c pg (Just $ not $ pgDecodeRep attnotnull) PGColDescription
-            { colName = pgDecodeRep attname
-            , colTable = pgDecodeRep attrelid
-            , colNumber = pgDecodeRep attnum
-            , colType = pgDecodeRep atttypid
-            , colSize = pgDecodeRep attlen
-            , colModifier = pgDecodeRep atttypmod
-            , colBinary = False
-            }
-    map makecol . snd <$> pgSimpleQuery pg (BSLC.fromChunks
-      [ "SELECT attname, attrelid, attnum, atttypid, attlen, atttypmod, attnotnull "
-      ,   "FROM pg_attribute "
-      ,   "JOIN pg_class "
-      ,     "ON attrelid = pg_class.oid "
-      ,   "JOIN pg_namespace "
-      ,     "ON relnamespace = pg_namespace.oid "
-      ,  "WHERE nspname = ANY (current_schemas(false)) "
-      ,    "AND relkind IN ('r','v','m','f') "
-      ,    "AND relname = ", pgLiteralRep t
-      ,   " AND attnum > 0 AND NOT attisdropped "
-      ,    "ORDER BY attnum"
+      [ "SELECT relname"
+      ,  " FROM pg_catalog.pg_class"
+      ,  " JOIN pg_catalog.pg_namespace ON relnamespace = pg_namespace.oid"
+      , " WHERE nspname = ANY (current_schemas(false))"
+      ,   " AND relkind IN ('r','v','m','f')"
       ])
+  describeTable c t = withPGConnection c $ \pg ->
+    map (\[attname, attrelid, attnum, atttypid, attlen, atttypmod, attnotnull] ->
+      colDescName &&& colDesc $ getType c pg (Just $ not $ pgDecodeRep attnotnull) PGColDescription
+        { colName = pgDecodeRep attname
+        , colTable = pgDecodeRep attrelid
+        , colNumber = pgDecodeRep attnum
+        , colType = pgDecodeRep atttypid
+        , colSize = pgDecodeRep attlen
+        , colModifier = pgDecodeRep atttypmod
+        , colBinary = False
+        })
+      . snd <$> pgSimpleQuery pg (BSLC.fromChunks
+        [ "SELECT attname, attrelid, attnum, atttypid, attlen, atttypmod, attnotnull"
+        ,  " FROM pg_catalog.pg_attribute"
+        , " WHERE attrelid = ", pgLiteralRep t, "::regclass"
+        , "   AND attnum > 0 AND NOT attisdropped"
+        , " ORDER BY attrelid, attnum"
+        ])
 
-encodeRep :: (PGParameter t a, PGRep t a) => a -> PGValue
+encodeRep :: PGRep a => a -> PGValue
 encodeRep x = PGTextValue $ pgEncode (pgTypeOf x) x
 
 encode :: HDBC.SqlValue -> PGValue
@@ -318,13 +313,13 @@
 typeId "uuid"                         = HDBC.SqlGUIDT
 typeId t = HDBC.SqlUnknownT t
 
-decodeRep :: PGColumn t a => PGTypeName t -> PGTypeEnv -> (a -> HDBC.SqlValue) -> PGValue -> HDBC.SqlValue
+decodeRep :: PGColumn t a => PGTypeID t -> PGTypeEnv -> (a -> HDBC.SqlValue) -> PGValue -> HDBC.SqlValue
 decodeRep t e f (PGBinaryValue v) = f $ pgDecodeBinary e t v
 decodeRep t _ f (PGTextValue v) = f $ pgDecode t v
 decodeRep _ _ _ PGNullValue = HDBC.SqlNull
 
 #define DECODE(T) \
-  decode T e = decodeRep (PGTypeProxy :: PGTypeName T) e
+  decode T e = decodeRep (PGTypeProxy :: PGTypeID T) e
 
 decode :: String -> PGTypeEnv -> PGValue -> HDBC.SqlValue
 DECODE("boolean")                     HDBC.SqlBool
diff --git a/Database/PostgreSQL/Typed/Inet.hs b/Database/PostgreSQL/Typed/Inet.hs
--- a/Database/PostgreSQL/Typed/Inet.hs
+++ b/Database/PostgreSQL/Typed/Inet.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies, DataKinds #-}
+{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies, DataKinds, TypeFamilies #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 -- |
 -- Module: Database.PostgreSQL.Typed.Inet
@@ -120,8 +120,10 @@
     jb :: Word8 -> Word8 -> Word16
     jb x y = fromIntegral x `shiftL` 8 .|. fromIntegral y
 
-instance PGType "inet"
-instance PGType "cidr"
+instance PGType "inet" where
+  type PGVal "inet" = PGInet
+instance PGType "cidr" where
+  type PGVal "cidr" = PGInet
 instance PGParameter "inet" PGInet where
   pgEncode _ = BSC.pack . show
 instance PGParameter "cidr" PGInet where
diff --git a/Database/PostgreSQL/Typed/Protocol.hs b/Database/PostgreSQL/Typed/Protocol.hs
--- a/Database/PostgreSQL/Typed/Protocol.hs
+++ b/Database/PostgreSQL/Typed/Protocol.hs
@@ -50,7 +50,7 @@
 import Control.Arrow ((&&&), first, second)
 import Control.Exception (Exception, throwIO, onException)
 import Control.Monad (void, liftM2, replicateM, when, unless)
-#ifdef USE_MD5
+#ifdef VERSION_cryptonite
 import qualified Crypto.Hash as Hash
 import qualified Data.ByteArray.Encoding as BA
 #endif
@@ -257,7 +257,7 @@
 pgServerVersion :: PGConnection -> Maybe BS.ByteString
 pgServerVersion PGConnection{ connParameters = p } = Map.lookup (BSC.pack "server_version") p
 
-#ifdef USE_MD5
+#ifdef VERSION_cryptonite
 md5 :: BS.ByteString -> BS.ByteString
 md5 = BA.convertToBase BA.Base16 . (Hash.hash :: BS.ByteString -> Hash.Digest Hash.MD5)
 #endif
@@ -411,22 +411,26 @@
 pgRecv block c@PGConnection{ connHandle = h, connInput = dr, connState = sr } =
   go =<< readIORef dr where
   next = writeIORef dr
-  state s d = writeIORef sr s >> next d
   new = G.pushChunk getMessage
   go (G.Done b _ m) = do
     when (connDebug c) $ putStrLn $ "< " ++ show m
-    got (new b) m =<< readIORef sr
+    got (new b) m
   go (G.Fail _ _ r) = next (new BS.empty) >> fail r -- not clear how can recover
   go d@(G.Partial r) = do
     b <- (if block then BS.hGetSome else BS.hGetNonBlocking) h smallChunkSize
     if BS.null b
       then Nothing <$ next d
       else go $ r (Just b)
-  got :: G.Decoder PGBackendMessage -> PGBackendMessage -> PGState -> IO (Maybe PGBackendMessage)
-  got d (NoticeResponse m) _ = connLogMessage c m >> go d
-  got d m@(ReadyForQuery s) _ = Just m <$ state s d
-  got d m@(ErrorResponse _) _ = Just m <$ state StateUnsync d
-  got d m _ = Just m <$ next d
+  got :: G.Decoder PGBackendMessage -> PGBackendMessage -> IO (Maybe PGBackendMessage)
+  got d (NoticeResponse m) = connLogMessage c m >> go d
+  got d m@(ReadyForQuery s) = do
+    s' <- atomicModifyIORef' sr ((,) s)
+    if s == s'
+      then go d
+      else done d m
+  got d m@(ErrorResponse _) = writeIORef sr StateUnsync >> done d m
+  got d m = done d m
+  done d m = Just m <$ next d
 
 -- |Receive the next message from PostgreSQL (low-level). Note that this will
 -- block until it gets a message.
@@ -488,7 +492,7 @@
     pgSend c $ PasswordMessage $ pgDBPass db
     pgFlush c
     conn c
-#ifdef USE_MD5
+#ifdef VERSION_cryptonite
   msg c (AuthenticationMD5Password salt) = do
     pgSend c $ PasswordMessage $ BSC.pack "md5" `BS.append` md5 (md5 (pgDBPass db <> pgDBUser db) `BS.append` salt)
     pgFlush c
@@ -534,10 +538,14 @@
   wait s = do
     r <- pgRecv s c
     case r of
-      Nothing -> do
-        pgSend c Sync
-        pgFlush c
-        wait True
+      Nothing
+        | s -> do
+          writeIORef sr StateClosed
+          fail $ "pgReceive: connection closed"
+        | otherwise -> do
+          pgSend c Sync
+          pgFlush c
+          wait True
       (Just (ErrorResponse{ messageFields = m })) -> do
         connLogMessage c m
         wait s
diff --git a/Database/PostgreSQL/Typed/Query.hs b/Database/PostgreSQL/Typed/Query.hs
--- a/Database/PostgreSQL/Typed/Query.hs
+++ b/Database/PostgreSQL/Typed/Query.hs
@@ -170,6 +170,13 @@
 newName pre = TH.newName . ('_':) . (pre:) . filter (\c -> isAlphaNum c || c == '_') . BSC.unpack
 
 -- |Construct a 'PGQuery' from a SQL string.
+-- This is the underlying template function for 'pgSQL' which you can use in largely the same way when you want to construct query strings from other variables.
+-- For example:
+--
+-- > selectQuery = "SELECT * FROM"
+-- > selectFoo = $(makePGQuery simpleQueryFlags (selectQuery ++ " foo"))
+--
+-- The only caveat is that variables or functions like @selectQuery@ need to be defined in a different module (due to TH stage restrictions).
 makePGQuery :: QueryFlags -> String -> TH.ExpQ
 makePGQuery QueryFlags{ flagQuery = False } sqle = pgSubstituteLiterals sqle
 makePGQuery QueryFlags{ flagNullable = nulls, flagPrepare = prep } sqle = do
@@ -199,7 +206,7 @@
         `TH.AppE` TH.ListE (map (TH.LitE . TH.IntegerL . toInteger . tpgValueTypeOID . snd) $ zip p pt)
         `TH.AppE` TH.ListE vals 
         `TH.AppE` TH.ListE 
-#ifdef USE_BINARY
+#ifdef VERSION_postgresql_binary
           bins
 #else
           []
@@ -257,6 +264,8 @@
 -- 
 -- 'pgSQL' can also be used at the top-level to execute SQL statements at compile-time (without any parameters and ignoring results).
 -- Here the query can only be prefixed with @!@ to make errors non-fatal.
+--
+-- If you want to construct queries out of string variables rather than quasi-quoted strings, you can use the lower-level 'makePGQuery' instead.
 pgSQL :: QuasiQuoter
 pgSQL = QuasiQuoter
   { quoteExp = qqQuery
diff --git a/Database/PostgreSQL/Typed/Range.hs b/Database/PostgreSQL/Typed/Range.hs
--- a/Database/PostgreSQL/Typed/Range.hs
+++ b/Database/PostgreSQL/Typed/Range.hs
@@ -1,4 +1,7 @@
-{-# LANGUAGE CPP, MultiParamTypeClasses, FlexibleInstances, UndecidableInstances, FunctionalDependencies, DataKinds, GeneralizedNewtypeDeriving, PatternGuards, OverloadedStrings #-}
+{-# LANGUAGE CPP, MultiParamTypeClasses, FlexibleContexts, FlexibleInstances, UndecidableInstances, DataKinds, GeneralizedNewtypeDeriving, PatternGuards, OverloadedStrings, TypeFamilies #-}
+#if __GLASGOW_HASKELL__ >= 800
+{-# LANGUAGE UndecidableSuperClasses #-}
+#endif
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 -- |
 -- Module: Database.PostgreSQL.Typed.Range
@@ -21,6 +24,7 @@
 #if !MIN_VERSION_base(4,8,0)
 import Data.Monoid (Monoid(..))
 #endif
+import GHC.TypeLits (Symbol)
 
 import Database.PostgreSQL.Typed.Types
 
@@ -216,11 +220,12 @@
 
 -- |Class indicating that the first PostgreSQL type is a range of the second.
 -- This implies 'PGParameter' and 'PGColumn' instances that will work for any type.
-class (PGType tr, PGType t) => PGRangeType tr t | tr -> t where
-  pgRangeElementType :: PGTypeName tr -> PGTypeName t
+class (PGType t, PGType (PGSubType t)) => PGRangeType t where
+  type PGSubType t :: Symbol
+  pgRangeElementType :: PGTypeID t -> PGTypeID (PGSubType t)
   pgRangeElementType PGTypeProxy = PGTypeProxy
 
-instance (PGRangeType tr t, PGParameter t a) => PGParameter tr (Range a) where
+instance (PGRangeType t, PGParameter (PGSubType t) a) => PGParameter t (Range a) where
   pgEncode _ Empty = BSC.pack "empty"
   pgEncode tr (Range (Lower l) (Upper u)) = buildPGValue $
     pc '[' '(' l
@@ -232,7 +237,7 @@
     pb Nothing = mempty
     pb (Just b) = pgDQuote "(),[]" $ pgEncode (pgRangeElementType tr) b
     pc c o b = BSB.char7 $ if boundClosed b then c else o
-instance (PGRangeType tr t, PGColumn t a) => PGColumn tr (Range a) where
+instance (PGRangeType t, PGColumn (PGSubType t) a) => PGColumn t (Range a) where
   pgDecode tr a = either (error . ("pgDecode range (" ++) . (++ ("): " ++ BSC.unpack a))) id $ P.parseOnly per a where
     per = (Empty <$ pe) <> pr
     pe = P.stringCI "empty"
@@ -247,16 +252,28 @@
       uc <- pc ']' ')'
       return $ Range (Lower (mb lc lb)) (Upper (mb uc ub))
 
-instance PGType "int4range"
-instance PGRangeType "int4range" "integer"
-instance PGType "numrange"
-instance PGRangeType "numrange" "numeric"
-instance PGType "tsrange"
-instance PGRangeType "tsrange" "timestamp without time zone"
-instance PGType "tstzrange"
-instance PGRangeType "tstzrange" "timestamp with time zone"
-instance PGType "daterange"
-instance PGRangeType "daterange" "date"
-instance PGType "int8range"
-instance PGRangeType "int8range" "bigint"
+instance PGType "int4range" where
+  type PGVal "int4range" = Range (PGVal (PGSubType "int4range"))
+instance PGRangeType "int4range" where
+  type PGSubType "int4range" = "integer"
+instance PGType "numrange" where
+  type PGVal "numrange" = Range (PGVal (PGSubType "numrange"))
+instance PGRangeType "numrange" where
+  type PGSubType "numrange" = "numeric"
+instance PGType "tsrange" where
+  type PGVal "tsrange" = Range (PGVal (PGSubType "tsrange"))
+instance PGRangeType "tsrange" where
+  type PGSubType "tsrange" = "timestamp without time zone"
+instance PGType "tstzrange" where
+  type PGVal "tstzrange" = Range (PGVal (PGSubType "tstzrange"))
+instance PGRangeType "tstzrange" where
+  type PGSubType "tstzrange" = "timestamp with time zone"
+instance PGType "daterange" where
+  type PGVal "daterange" = Range (PGVal (PGSubType "daterange"))
+instance PGRangeType "daterange" where
+  type PGSubType "daterange" = "date"
+instance PGType "int8range" where
+  type PGVal "int8range" = Range (PGVal (PGSubType "int8range"))
+instance PGRangeType "int8range" where
+  type PGSubType "int8range" = "bigint"
 
diff --git a/Database/PostgreSQL/Typed/Relation.hs b/Database/PostgreSQL/Typed/Relation.hs
new file mode 100644
--- /dev/null
+++ b/Database/PostgreSQL/Typed/Relation.hs
@@ -0,0 +1,205 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TupleSections #-}
+#if __GLASGOW_HASKELL__ >= 800
+{-# LANGUAGE UndecidableSuperClasses #-}
+#endif
+-- |
+-- Module: Database.PostgreSQL.Typed.Relation
+-- Copyright: 2016 Dylan Simon
+-- 
+-- Automatically create data types based on tables and other relations.
+
+module Database.PostgreSQL.Typed.Relation
+  ( dataPGRelation
+  ) where
+
+import qualified Data.ByteString.Lazy as BSL
+import           Data.Proxy (Proxy(..))
+import qualified Language.Haskell.TH as TH
+
+import           Database.PostgreSQL.Typed.Types
+import           Database.PostgreSQL.Typed.Dynamic
+import           Database.PostgreSQL.Typed.Protocol
+import           Database.PostgreSQL.Typed.TypeCache
+import           Database.PostgreSQL.Typed.TH
+
+-- |Data types that are based on database relations.
+-- Normally these instances are created using 'dataPGRelation'.
+class (PGRep a, PGRecordType (PGRepType a)) => PGRelation a where
+  -- |Database name of table/relation (i.e., second argument to 'dataPGRelation').  Normally this is the same as @'pgTypeID' . 'pgTypeOfProxy'@, but this preserves any specified schema qualification.
+  pgRelationName :: Proxy a -> PGName
+  pgRelationName = pgTypeName . pgTypeOfProxy
+  -- |Database names of columns.
+  pgColumnNames :: Proxy a -> [PGName]
+
+-- |Create a new data type corresponding to the given PostgreSQL relation.
+-- For example, if you have @CREATE TABLE foo (abc integer NOT NULL, def text)@, then
+-- @dataPGRelation \"Foo\" \"foo\" (\"foo_\"++)@ will be equivalent to:
+-- 
+-- > data Foo = Foo{ foo_abc :: PGVal "integer", foo_def :: Maybe (PGVal "text") }
+-- > instance PGType "foo" where PGVal "foo" = Foo
+-- > instance PGParameter "foo" Foo where ...
+-- > instance PGColumn "foo" Foo where ...
+-- > instance PGColumn "foo" (Maybe Foo) where ... -- to handle NULL in not null columns
+-- > instance PGRep Foo where PGRepType = "foo"
+-- > instance PGRecordType "foo"
+-- > instance PGRelation Foo where pgColumnNames _ = ["abc", "def"]
+-- > uncurryFoo :: (PGVal "integer", Maybe (PGVal "text")) -> Foo
+--
+-- (Note that @PGVal "integer" = Int32@ and @PGVal "text" = Text@ by default.)
+-- This provides instances for marshalling the corresponding composite/record types, e.g., using @SELECT foo.*::foo FROM foo@.
+-- If you want any derived instances, you'll need to create them yourself using StandaloneDeriving.
+--
+-- Requires language extensions: TemplateHaskell, FlexibleInstances, MultiParamTypeClasses, DataKinds, TypeFamilies, PatternGuards
+dataPGRelation :: String -- ^ Haskell type and constructor to create
+  -> PGName -- ^ PostgreSQL table/relation name
+  -> (String -> String) -- ^ How to generate field names from column names, e.g. @("table_"++)@ (input is 'pgNameString')
+  -> TH.DecsQ
+dataPGRelation typs pgtab colf = do
+  (pgid, cold) <- TH.runIO $ withTPGTypeConnection $ \tpg -> do
+    cl <- mapM (\[to, cn, ct, cnn] -> do
+      let c = pgDecodeRep cn
+          n = TH.mkName $ colf $ pgNameString c
+          o = pgDecodeRep ct
+      t <- maybe (fail $ "dataPGRelation " ++ typs ++ " = " ++ show pgtab ++ ": column '" ++ show c ++ "' has unknown type " ++ show o) return
+        =<< lookupPGType tpg o
+      return (pgDecodeRep to, (c, n, TH.LitT (TH.StrTyLit $ pgNameString t), not $ pgDecodeRep cnn)))
+      . snd =<< pgSimpleQuery (pgConnection tpg) (BSL.fromChunks
+        [ "SELECT reltype, attname, atttypid, attnotnull"
+        ,  " FROM pg_catalog.pg_attribute"
+        ,  " JOIN pg_catalog.pg_class ON attrelid = pg_class.oid"
+        , " WHERE attrelid = ", pgLiteralRep pgtab, "::regclass"
+        ,   " AND attnum > 0 AND NOT attisdropped"
+        , " ORDER BY attnum"
+        ])
+    case cl of
+      [] -> fail $ "dataPGRelation " ++ typs ++ " = " ++ show pgtab ++ ": no columns found"
+      (to, _):_ -> do
+        tt <- maybe (fail $ "dataPGRelation " ++ typs ++ " = " ++ show pgtab ++ ": table type not found (you may need to use reloadTPGTypes or adjust search_path)") return
+          =<< lookupPGType tpg to
+        return (tt, map snd cl)
+  cols <- mapM (\(c, _, t, nn) -> do
+      v <- TH.newName $ pgNameString c
+      return (v, t, nn))
+    cold
+  let typl = TH.LitT (TH.StrTyLit $ pgNameString pgid)
+      encfun f = TH.FunD f [TH.Clause [TH.WildP, TH.ConP typn (map (\(v, _, _) -> TH.VarP v) cols)]
+        (TH.NormalB $ pgcall f rect `TH.AppE`
+          (TH.ConE 'PGRecord `TH.AppE` TH.ListE (map (colenc f) cols)))
+        [] ]
+  dv <- TH.newName "x"
+  tv <- TH.newName "t"
+  ev <- TH.newName "e"
+  return $
+    [ TH.DataD
+      []
+      typn
+      []
+#if MIN_VERSION_template_haskell(2,11,0)
+      Nothing
+#endif
+      [ TH.RecC typn $ map (\(_, n, t, nn) ->
+        ( n
+#if MIN_VERSION_template_haskell(2,11,0)
+        , TH.Bang TH.NoSourceUnpackedness TH.NoSourceStrictness
+#else
+        , TH.NotStrict
+#endif
+        , (if nn then (TH.ConT ''Maybe `TH.AppT`) else id)
+          (TH.ConT ''PGVal `TH.AppT` t)))
+        cold
+      ]
+      []
+    , instanceD [] (TH.ConT ''PGType `TH.AppT` typl)
+      [ TH.TySynInstD ''PGVal $ TH.TySynEqn [typl] typt
+      ]
+    , instanceD [] (TH.ConT ''PGParameter `TH.AppT` typl `TH.AppT` typt)
+      [ encfun 'pgEncode
+      , encfun 'pgLiteral
+      ]
+    , instanceD [] (TH.ConT ''PGColumn `TH.AppT` typl `TH.AppT` typt)
+      [ TH.FunD 'pgDecode [TH.Clause [TH.WildP, TH.VarP dv]
+        (TH.GuardedB
+          [ (TH.PatG [TH.BindS
+              (TH.ConP 'PGRecord [TH.ListP $ map colpat cols])
+              (pgcall 'pgDecode rect `TH.AppE` TH.VarE dv)]
+            , foldl (\f -> TH.AppE f . coldec) (TH.ConE typn) cols)
+          , (TH.NormalG (TH.ConE 'True)
+            , TH.VarE 'error `TH.AppE` TH.LitE (TH.StringL $ "pgDecode " ++ typs ++ ": NULL in not null record column"))
+          ])
+        [] ]
+      ]
+#if MIN_VERSION_template_haskell(2,11,0)
+    , TH.InstanceD (Just TH.Overlapping) [] (TH.ConT ''PGColumn `TH.AppT` typl `TH.AppT` (TH.ConT ''Maybe `TH.AppT` typt))
+      [ TH.FunD 'pgDecode [TH.Clause [TH.WildP, TH.VarP dv]
+        (TH.GuardedB
+          [ (TH.PatG [TH.BindS
+              (TH.ConP 'PGRecord [TH.ListP $ map colpat cols])
+              (pgcall 'pgDecode rect `TH.AppE` TH.VarE dv)]
+            , TH.ConE 'Just `TH.AppE` foldl (\f -> TH.AppE f . coldec) (TH.ConE typn) cols)
+          , (TH.NormalG (TH.ConE 'True)
+            , TH.ConE 'Nothing)
+          ])
+        [] ]
+      , TH.FunD 'pgDecodeValue
+        [ TH.Clause [TH.WildP, TH.WildP, TH.ConP 'PGNullValue []]
+          (TH.NormalB $ TH.ConE 'Nothing)
+          []
+        , TH.Clause [TH.WildP, TH.VarP tv, TH.ConP 'PGTextValue [TH.VarP dv]]
+          (TH.NormalB $ TH.VarE 'pgDecode `TH.AppE` TH.VarE tv `TH.AppE` TH.VarE dv)
+          []
+        , TH.Clause [TH.VarP ev, TH.VarP tv, TH.ConP 'PGBinaryValue [TH.VarP dv]]
+          (TH.NormalB $ TH.VarE 'pgDecodeBinary `TH.AppE` TH.VarE ev `TH.AppE` TH.VarE tv `TH.AppE` TH.VarE dv)
+          []
+        ]
+      ]
+#endif
+    , instanceD [] (TH.ConT ''PGRep `TH.AppT` typt)
+      [ TH.TySynInstD ''PGRepType $ TH.TySynEqn [typt] typl
+      ]
+    , instanceD [] (TH.ConT ''PGRecordType `TH.AppT` typl) []
+    , instanceD [] (TH.ConT ''PGRelation `TH.AppT` typt)
+      [ TH.FunD 'pgRelationName [TH.Clause [TH.WildP]
+        (TH.NormalB $ namelit pgtab)
+        [] ]
+      , TH.FunD 'pgColumnNames [TH.Clause [TH.WildP]
+        (TH.NormalB $ TH.ListE $ map (\(c, _, _, _) -> namelit c) cold)
+        [] ]
+      ]
+    , TH.SigD (TH.mkName ("uncurry" ++ typs)) $ TH.ArrowT `TH.AppT`
+      foldl (\f (_, t, n) -> f `TH.AppT`
+          (if n then (TH.ConT ''Maybe `TH.AppT`) else id)
+          (TH.ConT ''PGVal `TH.AppT` t))
+        (TH.ConT (TH.tupleTypeName (length cols)))
+        cols `TH.AppT` typt
+    , TH.FunD (TH.mkName ("uncurry" ++ typs))
+      [ TH.Clause [TH.ConP (TH.tupleDataName (length cols)) (map (\(v, _, _) -> TH.VarP v) cols)]
+        (TH.NormalB $ foldl (\f (v, _, _) -> f `TH.AppE` TH.VarE v) (TH.ConE typn) cols)
+        []
+      ]
+    , TH.PragmaD $ TH.AnnP (TH.TypeAnnotation typn) $ namelit pgid
+    , TH.PragmaD $ TH.AnnP (TH.ValueAnnotation typn) $ namelit pgid
+    ] ++ map (\(c, n, _, _) ->
+      TH.PragmaD $ TH.AnnP (TH.ValueAnnotation n) $ namelit c) cold
+  where
+  typn = TH.mkName typs
+  typt = TH.ConT typn
+  instanceD = TH.InstanceD
+#if MIN_VERSION_template_haskell(2,11,0)
+      Nothing
+#endif
+  pgcall f t = TH.VarE f `TH.AppE`
+    (TH.ConE 'PGTypeProxy `TH.SigE`
+      (TH.ConT ''PGTypeID `TH.AppT` t))
+  colenc f (v, t, False) = TH.ConE 'Just `TH.AppE` (pgcall f t `TH.AppE` TH.VarE v)
+  colenc f (v, t, True) = TH.VarE 'fmap `TH.AppE` pgcall f t `TH.AppE` TH.VarE v
+  colpat (v, _, False) = TH.ConP 'Just [TH.VarP v]
+  colpat (v, _, True) = TH.VarP v
+  coldec (v, t, False) = pgcall 'pgDecode t `TH.AppE` TH.VarE v
+  coldec (v, t, True) = TH.VarE 'fmap `TH.AppE` pgcall 'pgDecode t `TH.AppE` TH.VarE v
+  rect = TH.LitT $ TH.StrTyLit "record"
+  namelit n = TH.ConE 'PGName `TH.AppE`
+    TH.ListE (map (TH.LitE . TH.IntegerL . fromIntegral) $ pgNameBytes n)
diff --git a/Database/PostgreSQL/Typed/TH.hs b/Database/PostgreSQL/Typed/TH.hs
--- a/Database/PostgreSQL/Typed/TH.hs
+++ b/Database/PostgreSQL/Typed/TH.hs
@@ -8,6 +8,7 @@
 
 module Database.PostgreSQL.Typed.TH
   ( getTPGDatabase
+  , withTPGTypeConnection
   , withTPGConnection
   , useTPGDatabase
   , reloadTPGTypes
@@ -16,26 +17,21 @@
   , tpgTypeEncoder
   , tpgTypeDecoder
   , tpgTypeBinary
-  -- * HDBC support
-  , PGTypes
-  , pgLoadTypes
   ) where
 
 #if !MIN_VERSION_base(4,8,0)
 import Control.Applicative ((<$>), (<$))
 #endif
 import Control.Applicative ((<|>))
-import Control.Concurrent.MVar (MVar, newMVar, takeMVar, putMVar, modifyMVar_)
+import Control.Concurrent.MVar (MVar, newMVar, takeMVar, putMVar, withMVar)
 import Control.Exception (onException, finally)
 import Control.Monad (liftM2)
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Lazy as BSL
-import qualified Data.ByteString.Lazy.Char8 as BSLC
 import qualified Data.ByteString.UTF8 as BSU
 import qualified Data.Foldable as Fold
-import qualified Data.IntMap.Lazy as IntMap
-import Data.List (find)
 import Data.Maybe (isJust, fromMaybe)
+import Data.String (fromString)
 import qualified Data.Traversable as Tv
 import qualified Language.Haskell.TH as TH
 import Network (PortID(UnixSocket, PortNumber), PortNumber)
@@ -43,11 +39,8 @@
 import System.IO.Unsafe (unsafePerformIO, unsafeInterleaveIO)
 
 import Database.PostgreSQL.Typed.Types
-import Database.PostgreSQL.Typed.Dynamic
 import Database.PostgreSQL.Typed.Protocol
-
--- |A particular PostgreSQL type, identified by full formatted name (from @format_type@ or @\\dT@).
-type TPGType = String
+import Database.PostgreSQL.Typed.TypeCache
 
 -- |Generate a 'PGDatabase' based on the environment variables:
 -- @TPG_HOST@ (localhost); @TPG_SOCK@ or @TPG_PORT@ (5432); @TPG_DB@ or user; @TPG_USER@ or @USER@ (postgres); @TPG_PASS@ ()
@@ -70,44 +63,22 @@
     }
 
 {-# NOINLINE tpgState #-}
-tpgState :: MVar (PGDatabase, Maybe TPGState)
+tpgState :: MVar (PGDatabase, Maybe PGTypeConnection)
 tpgState = unsafePerformIO $ do
   db <- unsafeInterleaveIO getTPGDatabase
   newMVar (db, Nothing)
 
-data TPGState = TPGState
-  { tpgConnection :: PGConnection
-  , tpgTypes :: PGTypes
-  }
-
--- |Map keyed on fromIntegral OID.
-type PGTypes = IntMap.IntMap TPGType
-
--- |Load a map of types from the database.
-pgLoadTypes :: PGConnection -> IO PGTypes
-pgLoadTypes c =
-  IntMap.fromAscList . map (\[to, tn] -> (fromIntegral (pgDecodeRep to :: OID), pgDecodeRep tn)) .
-    snd <$> pgSimpleQuery c (BSLC.pack "SELECT typ.oid, format_type(CASE WHEN typtype = 'd' THEN typbasetype ELSE typ.oid END, -1) FROM pg_catalog.pg_type typ JOIN pg_catalog.pg_namespace nsp ON typnamespace = nsp.oid WHERE nspname <> 'pg_toast' AND nspname <> 'information_schema' ORDER BY typ.oid")
-
-tpgLoadTypes :: TPGState -> IO TPGState
-tpgLoadTypes tpg = do
-  t <- pgLoadTypes (tpgConnection tpg)
-  return tpg{ tpgTypes = t }
-
-tpgInit :: PGConnection -> IO TPGState
-tpgInit c = tpgLoadTypes TPGState{ tpgConnection = c, tpgTypes = undefined }
-
 -- |Run an action using the Template Haskell state.
-withTPGState :: (TPGState -> IO a) -> IO a
-withTPGState f = do
+withTPGTypeConnection :: (PGTypeConnection -> IO a) -> IO a
+withTPGTypeConnection f = do
   (db, tpg') <- takeMVar tpgState
-  tpg <- maybe (tpgInit =<< pgConnect db) return tpg'
+  tpg <- maybe (newPGTypeConnection =<< pgConnect db) return tpg'
     `onException` putMVar tpgState (db, Nothing) -- might leave connection open
   f tpg `finally` putMVar tpgState (db, Just tpg)
 
 -- |Run an action using the Template Haskell PostgreSQL connection.
 withTPGConnection :: (PGConnection -> IO a) -> IO a
-withTPGConnection f = withTPGState (f . tpgConnection)
+withTPGConnection f = withTPGTypeConnection (f . pgConnection)
 
 -- |Specify an alternative database to use during compilation.
 -- This lets you override the default connection parameters that are based on TPG environment variables.
@@ -119,62 +90,64 @@
   putMVar tpgState . (,) db =<<
     (if db == db'
       then Tv.mapM (\t -> do
-        c <- pgReconnect (tpgConnection t) db
-        return t{ tpgConnection = c }) tpg'
-      else Nothing <$ Fold.mapM_ (pgDisconnect . tpgConnection) tpg')
+        c <- pgReconnect (pgConnection t) db
+        return t{ pgConnection = c }) tpg'
+      else Nothing <$ Fold.mapM_ (pgDisconnect . pgConnection) tpg')
     `onException` putMVar tpgState (db, Nothing)
   return []
 
 -- |Force reloading of all types from the database.
 -- This may be needed if you make structural changes to the database during compile-time.
 reloadTPGTypes :: TH.DecsQ
-reloadTPGTypes = TH.runIO $ [] <$ modifyMVar_ tpgState (\(d, c) -> (,) d <$> Tv.mapM tpgLoadTypes c)
+reloadTPGTypes = TH.runIO $ [] <$ withMVar tpgState (mapM_ flushPGTypeConnection . snd)
 
 -- |Lookup a type name by OID.
 -- Error if not found.
-tpgType :: TPGState -> OID -> TPGType
-tpgType TPGState{ tpgTypes = types } t =
-  IntMap.findWithDefault (error $ "Unknown PostgreSQL type: " ++ show t ++ "\nYour postgresql-typed application may need to be rebuilt.") (fromIntegral t) types
+tpgType :: PGTypeConnection -> OID -> IO PGName
+tpgType c o =
+  maybe (fail $ "Unknown PostgreSQL type: " ++ show o ++ "\nYou may need to use reloadTPGTypes or adjust search_path, or your postgresql-typed application may need to be rebuilt.") return =<< lookupPGType c o
 
 -- |Lookup a type OID by type name.
 -- This is less common and thus less efficient than going the other way.
 -- Fail if not found.
-getTPGTypeOID :: Monad m => TPGState -> String -> m OID
-getTPGTypeOID TPGState{ tpgTypes = types } t =
-  maybe (fail $ "Unknown PostgreSQL type: " ++ t ++ "; be sure to use the exact type name from \\dTS") (return . fromIntegral . fst)
-    $ find ((==) t . snd) $ IntMap.toList types
+getTPGTypeOID :: PGTypeConnection -> PGName -> IO OID
+getTPGTypeOID c t =
+  maybe (fail $ "Unknown PostgreSQL type: " ++ show t ++ "; be sure to use the exact type name from \\dTS") return =<< findPGType c t
 
 data TPGValueInfo = TPGValueInfo
   { tpgValueName :: BS.ByteString
   , tpgValueTypeOID :: !OID
-  , tpgValueType :: TPGType
+  , tpgValueType :: PGName
   , tpgValueNullable :: Bool
   }
 
 -- |A type-aware wrapper to 'pgDescribe'
 tpgDescribe :: BS.ByteString -> [String] -> Bool -> IO ([TPGValueInfo], [TPGValueInfo])
-tpgDescribe sql types nulls = withTPGState $ \tpg -> do
-  at <- mapM (getTPGTypeOID tpg) types
-  (pt, rt) <- pgDescribe (tpgConnection tpg) (BSL.fromStrict sql) at nulls
-  return
-    ( map (\o -> TPGValueInfo
-      { tpgValueName = BS.empty
-      , tpgValueTypeOID = o
-      , tpgValueType = tpgType tpg o
-      , tpgValueNullable = True
-      }) pt
-    , map (\(c, o, n) -> TPGValueInfo
-      { tpgValueName = c
-      , tpgValueTypeOID = o
-      , tpgValueType = tpgType tpg o
-      , tpgValueNullable = n && o /= 2278 -- "void"
-      }) rt
-    )
+tpgDescribe sql types nulls = withTPGTypeConnection $ \tpg -> do
+  at <- mapM (getTPGTypeOID tpg . fromString) types
+  (pt, rt) <- pgDescribe (pgConnection tpg) (BSL.fromStrict sql) at nulls
+  (,)
+    <$> mapM (\o -> do
+      ot <- tpgType tpg o
+      return TPGValueInfo
+        { tpgValueName = BS.empty
+        , tpgValueTypeOID = o
+        , tpgValueType = ot
+        , tpgValueNullable = True
+        }) pt
+    <*> mapM (\(c, o, n) -> do
+      ot <- tpgType tpg o
+      return TPGValueInfo
+        { tpgValueName = c
+        , tpgValueTypeOID = o
+        , tpgValueType = ot
+        , tpgValueNullable = n && o /= 2278 -- "void"
+        }) rt
 
-typeApply :: TPGType -> TH.Name -> TH.Name -> TH.Exp
+typeApply :: PGName -> TH.Name -> TH.Name -> TH.Exp
 typeApply t f e =
   TH.VarE f `TH.AppE` TH.VarE e
-    `TH.AppE` (TH.ConE 'PGTypeProxy `TH.SigE` (TH.ConT ''PGTypeName `TH.AppT` TH.LitT (TH.StrTyLit t)))
+    `TH.AppE` (TH.ConE 'PGTypeProxy `TH.SigE` (TH.ConT ''PGTypeID `TH.AppT` TH.LitT (TH.StrTyLit $ pgNameString $ t)))
 
 
 -- |TH expression to encode a 'PGParameter' value to a 'Maybe' 'L.ByteString'.
diff --git a/Database/PostgreSQL/Typed/TypeCache.hs b/Database/PostgreSQL/Typed/TypeCache.hs
new file mode 100644
--- /dev/null
+++ b/Database/PostgreSQL/Typed/TypeCache.hs
@@ -0,0 +1,67 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Database.PostgreSQL.Typed.TypeCache
+  ( PGTypes
+  , pgGetTypes
+  , PGTypeConnection
+  , pgConnection
+  , newPGTypeConnection
+  , flushPGTypeConnection
+  , lookupPGType
+  , findPGType
+  ) where
+
+import Data.IORef (IORef, newIORef, readIORef, writeIORef)
+import qualified Data.IntMap as IntMap
+import Data.List (find)
+
+import Database.PostgreSQL.Typed.Types (PGName, OID)
+import Database.PostgreSQL.Typed.Dynamic
+import Database.PostgreSQL.Typed.Protocol
+
+-- |Map keyed on fromIntegral OID.
+type PGTypes = IntMap.IntMap PGName
+
+-- |A 'PGConnection' along with cached information about types.
+data PGTypeConnection = PGTypeConnection
+  { pgConnection :: !PGConnection
+  , pgTypes :: IORef (Maybe PGTypes)
+  }
+
+-- |Create a 'PGTypeConnection'.
+newPGTypeConnection :: PGConnection -> IO PGTypeConnection
+newPGTypeConnection c = do
+  t <- newIORef Nothing
+  return $ PGTypeConnection c t
+
+-- |Flush the cached type list, forcing it to be reloaded.
+flushPGTypeConnection :: PGTypeConnection -> IO ()
+flushPGTypeConnection c =
+  writeIORef (pgTypes c) Nothing
+
+-- |Get a map of types from the database.
+pgGetTypes :: PGConnection -> IO PGTypes
+pgGetTypes c =
+  IntMap.fromAscList . map (\[to, tn] -> (fromIntegral (pgDecodeRep to :: OID), pgDecodeRep tn)) .
+    snd <$> pgSimpleQuery c "SELECT oid, format_type(CASE WHEN typtype = 'd' THEN typbasetype ELSE oid END, -1) FROM pg_catalog.pg_type ORDER BY oid"
+
+-- |Get a cached map of types.
+getPGTypes :: PGTypeConnection -> IO PGTypes
+getPGTypes (PGTypeConnection c tr) =
+  maybe (do
+      t <- pgGetTypes c
+      writeIORef tr $ Just t
+      return t)
+    return
+    =<< readIORef tr
+
+-- |Lookup a type name by OID.
+-- This is an efficient, often pure operation.
+lookupPGType :: PGTypeConnection -> OID -> IO (Maybe PGName)
+lookupPGType c o =
+  IntMap.lookup (fromIntegral o) <$> getPGTypes c
+
+-- |Lookup a type OID by type name.
+-- This is less common and thus less efficient than going the other way.
+findPGType :: PGTypeConnection -> PGName -> IO (Maybe OID)
+findPGType c t =
+  fmap (fromIntegral . fst) . find ((==) t . snd) . IntMap.toList <$> getPGTypes c
diff --git a/Database/PostgreSQL/Typed/Types.hs b/Database/PostgreSQL/Typed/Types.hs
--- a/Database/PostgreSQL/Typed/Types.hs
+++ b/Database/PostgreSQL/Typed/Types.hs
@@ -1,7 +1,10 @@
-{-# LANGUAGE CPP, FlexibleInstances, ScopedTypeVariables, MultiParamTypeClasses, FunctionalDependencies, FlexibleContexts, DataKinds, KindSignatures, TypeFamilies, UndecidableInstances #-}
+{-# LANGUAGE CPP, FlexibleInstances, ScopedTypeVariables, MultiParamTypeClasses, FlexibleContexts, DataKinds, KindSignatures, TypeFamilies, DeriveDataTypeable #-}
 #if __GLASGOW_HASKELL__ < 710
 {-# LANGUAGE OverlappingInstances #-}
 #endif
+#if __GLASGOW_HASKELL__ >= 800
+{-# LANGUAGE UndecidableSuperClasses #-}
+#endif
 -- |
 -- Module: Database.PostgreSQL.Typed.Types
 -- Copyright: 2015 Dylan Simon
@@ -14,15 +17,17 @@
     OID
   , PGValue(..)
   , PGValues
-  , PGTypeName(..)
-  , PGTypeEnv(..)
-  , unknownPGTypeEnv
+  , PGTypeID(..)
+  , PGTypeEnv(..), unknownPGTypeEnv
+  , PGName(..), pgNameBS, pgNameString
   , PGRecord(..)
 
   -- * Marshalling classes
   , PGType(..)
   , PGParameter(..)
   , PGColumn(..)
+  , PGStringType
+  , PGRecordType
 
   -- * Marshalling interface
   , pgEncodeParameter
@@ -37,25 +42,26 @@
   , buildPGValue
   ) where
 
+import qualified Codec.Binary.UTF8.String as UTF8
 #if !MIN_VERSION_base(4,8,0)
 import Control.Applicative ((<$>), (<$), (<*), (*>))
 #endif
 import Control.Arrow ((&&&))
-#ifdef USE_AESON
+#ifdef VERSION_aeson
 import qualified Data.Aeson as JSON
 #endif
 import qualified Data.Attoparsec.ByteString as P (anyWord8)
 import qualified Data.Attoparsec.ByteString.Char8 as P
 import Data.Bits (shiftL, (.|.))
-import Data.ByteString.Internal (w2c)
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Builder as BSB
 import qualified Data.ByteString.Builder.Prim as BSBP
 import qualified Data.ByteString.Char8 as BSC
-import Data.ByteString.Internal (c2w)
+import Data.ByteString.Internal (c2w, w2c)
 import qualified Data.ByteString.Lazy as BSL
 import qualified Data.ByteString.UTF8 as BSU
 import Data.Char (isSpace, isDigit, digitToInt, intToDigit, toLower)
+import Data.Data (Data)
 import Data.Int
 import Data.List (intersperse)
 import Data.Maybe (fromMaybe)
@@ -64,10 +70,11 @@
 import Data.Monoid (mempty, mconcat)
 #endif
 import Data.Ratio ((%), numerator, denominator)
-#ifdef USE_SCIENTIFIC
+#ifdef VERSION_scientific
 import Data.Scientific (Scientific)
 #endif
-#ifdef USE_TEXT
+import Data.String (IsString(..))
+#ifdef VERSION_text
 import qualified Data.Text as T
 import qualified Data.Text.Encoding as TE
 import qualified Data.Text.Lazy as TL
@@ -79,13 +86,14 @@
 #else
 import System.Locale (defaultTimeLocale)
 #endif
-#ifdef USE_UUID
+import Data.Typeable (Typeable)
+#ifdef VERSION_uuid
 import qualified Data.UUID as UUID
 #endif
 import Data.Word (Word8, Word32)
 import GHC.TypeLits (Symbol, symbolVal, KnownSymbol)
 import Numeric (readFloat)
-#ifdef USE_BINARY
+#ifdef VERSION_postgresql_binary
 import qualified PostgreSQL.Binary.Decoder as BinD
 import qualified PostgreSQL.Binary.Encoder as BinE
 #endif
@@ -113,48 +121,78 @@
   { pgIntegerDatetimes = Nothing
   }
 
--- |A proxy type for PostgreSQL types.  The type argument should be an (internal) name of a database type (see @\\dT+@).
-data PGTypeName (t :: Symbol) = PGTypeProxy
+-- |A PostgreSQL literal identifier, generally corresponding to the \"name\" type (63-byte strings), but as it would be entered in a query, so may include double-quoting for special characters or schema-qualification.
+newtype PGName = PGName
+  { pgNameBytes :: [Word8] -- ^Raw bytes of the identifier (should really be a 'BS.ByteString', but we need a working 'Data' instance for annotations).
+  }
+  deriving (Eq, Ord, Typeable, Data)
 
--- |A valid PostgreSQL type.
--- This is just an indicator class: no implementation is needed.
--- Unfortunately this will generate orphan instances wherever used.
-class KnownSymbol t => PGType t where
-  pgTypeName :: PGTypeName t -> String
-  pgTypeName = symbolVal
+-- |The literal identifier as used in a query.
+pgNameBS :: PGName -> BS.ByteString
+pgNameBS = BS.pack . pgNameBytes
+
+-- |Applies utf-8 encoding.
+instance IsString PGName where
+  fromString = PGName . UTF8.encode
+-- |Unquoted 'pgNameString'.
+instance Show PGName where
+  show = pgNameString
+
+-- |Reverses the 'IsString' instantce.
+pgNameString :: PGName -> String
+pgNameString = UTF8.decode . pgNameBytes
+
+-- |A proxy type for PostgreSQL types.  The type argument should be an (internal) name of a database type, as per @format_type(OID)@ (usually the same as @\\dT+@).
+-- When the type's namespace (schema) is not in @search_path@, this will be explicitly qualified, so you should be sure to have a consistent @search_path@ for all database connections.
+-- The underlying 'Symbol' should be considered a lifted 'PGName'.
+data PGTypeID (t :: Symbol) = PGTypeProxy
+
+-- |A valid PostgreSQL type, its metadata, and corresponding Haskell representation.
+-- For conversion the other way (from Haskell type to PostgreSQL), see 'Database.PostgreSQL.Typed.Dynamic.PGRep'.
+-- Unfortunately any instances of this will be orphans.
+class (KnownSymbol t
+#if __GLASGOW_HASKELL__ >= 800
+    , PGParameter t (PGVal t), PGColumn t (PGVal t)
+#endif
+    ) => PGType t where
+  -- |The default, native Haskell representation of this type, which should be as close as possible to the PostgreSQL representation.
+  type PGVal t :: *
+  -- |The string name of this type: specialized version of 'symbolVal'.
+  pgTypeName :: PGTypeID t -> PGName
+  pgTypeName = fromString . symbolVal
   -- |Does this type support binary decoding?
   -- If so, 'pgDecodeBinary' must be implemented for every 'PGColumn' instance of this type.
-  pgBinaryColumn :: PGTypeEnv -> PGTypeName t -> Bool
+  pgBinaryColumn :: PGTypeEnv -> PGTypeID t -> Bool
   pgBinaryColumn _ _ = False
 
 -- |A @PGParameter t a@ instance describes how to encode a PostgreSQL type @t@ from @a@.
 class PGType t => PGParameter t a where
   -- |Encode a value to a PostgreSQL text representation.
-  pgEncode :: PGTypeName t -> a -> PGTextValue
+  pgEncode :: PGTypeID t -> a -> PGTextValue
   -- |Encode a value to a (quoted) literal value for use in SQL statements.
   -- Defaults to a quoted version of 'pgEncode'
-  pgLiteral :: PGTypeName t -> a -> BS.ByteString
+  pgLiteral :: PGTypeID t -> a -> BS.ByteString
   pgLiteral t = pgQuote . pgEncode t
   -- |Encode a value to a PostgreSQL representation.
   -- Defaults to the text representation by pgEncode
-  pgEncodeValue :: PGTypeEnv -> PGTypeName t -> a -> PGValue
+  pgEncodeValue :: PGTypeEnv -> PGTypeID t -> a -> PGValue
   pgEncodeValue _ t = PGTextValue . pgEncode t
 
 -- |A @PGColumn t a@ instance describes how te decode a PostgreSQL type @t@ to @a@.
 class PGType t => PGColumn t a where
   -- |Decode the PostgreSQL text representation into a value.
-  pgDecode :: PGTypeName t -> PGTextValue -> a
+  pgDecode :: PGTypeID t -> PGTextValue -> a
   -- |Decode the PostgreSQL binary representation into a value.
   -- Only needs to be implemented if 'pgBinaryColumn' is true.
-  pgDecodeBinary :: PGTypeEnv -> PGTypeName t -> PGBinaryValue -> a
-  pgDecodeBinary _ t _ = error $ "pgDecodeBinary " ++ pgTypeName t ++ ": not supported"
-  pgDecodeValue :: PGTypeEnv -> PGTypeName t -> PGValue -> a
+  pgDecodeBinary :: PGTypeEnv -> PGTypeID t -> PGBinaryValue -> a
+  pgDecodeBinary _ t _ = error $ "pgDecodeBinary " ++ show (pgTypeName t) ++ ": not supported"
+  pgDecodeValue :: PGTypeEnv -> PGTypeID t -> PGValue -> a
   pgDecodeValue _ t (PGTextValue v) = pgDecode t v
   pgDecodeValue e t (PGBinaryValue v) = pgDecodeBinary e t v
-  pgDecodeValue _ t PGNullValue = error $ "NULL in " ++ pgTypeName t ++ " column (use Maybe or COALESCE)"
+  pgDecodeValue _ t PGNullValue = error $ "NULL in " ++ show (pgTypeName t) ++ " column (use Maybe or COALESCE)"
 
 instance PGParameter t a => PGParameter t (Maybe a) where
-  pgEncode t = maybe (error $ "pgEncode " ++ pgTypeName t ++ ": Nothing") (pgEncode t)
+  pgEncode t = maybe (error $ "pgEncode " ++ show (pgTypeName t) ++ ": Nothing") (pgEncode t)
   pgLiteral = maybe (BSC.pack "NULL") . pgLiteral
   pgEncodeValue e = maybe PGNullValue . pgEncodeValue e
 
@@ -165,19 +203,19 @@
   pgDecodeValue e t v = Just $ pgDecodeValue e t v
 
 -- |Final parameter encoding function used when a (nullable) parameter is passed to a prepared query.
-pgEncodeParameter :: PGParameter t a => PGTypeEnv -> PGTypeName t -> a -> PGValue
+pgEncodeParameter :: PGParameter t a => PGTypeEnv -> PGTypeID t -> a -> PGValue
 pgEncodeParameter = pgEncodeValue
 
 -- |Final parameter escaping function used when a (nullable) parameter is passed to be substituted into a simple query.
-pgEscapeParameter :: PGParameter t a => PGTypeEnv -> PGTypeName t -> a -> BS.ByteString
+pgEscapeParameter :: PGParameter t a => PGTypeEnv -> PGTypeID t -> a -> BS.ByteString
 pgEscapeParameter _ = pgLiteral
 
 -- |Final column decoding function used for a nullable result value.
-pgDecodeColumn :: PGColumn t (Maybe a) => PGTypeEnv -> PGTypeName t -> PGValue -> Maybe a
+pgDecodeColumn :: PGColumn t (Maybe a) => PGTypeEnv -> PGTypeID t -> PGValue -> Maybe a
 pgDecodeColumn = pgDecodeValue
 
 -- |Final column decoding function used for a non-nullable result value.
-pgDecodeColumnNotNull :: PGColumn t a => PGTypeEnv -> PGTypeName t -> PGValue -> a
+pgDecodeColumnNotNull :: PGColumn t a => PGTypeEnv -> PGTypeID t -> PGValue -> a
 pgDecodeColumnNotNull = pgDecodeValue
 
 
@@ -220,20 +258,21 @@
     | isnul s = Nothing
     | otherwise = Just s
 
-#ifdef USE_BINARY
-binDec :: PGType t => BinD.Decoder a -> PGTypeName t -> PGBinaryValue -> a
-binDec d t = either (\e -> error $ "pgDecodeBinary " ++ pgTypeName t ++ ": " ++ show e) id . BinD.run d
+#ifdef VERSION_postgresql_binary
+binDec :: PGType t => BinD.Decoder a -> PGTypeID t -> PGBinaryValue -> a
+binDec d t = either (\e -> error $ "pgDecodeBinary " ++ show (pgTypeName t) ++ ": " ++ show e) id . BinD.run d
 
 #define BIN_COL pgBinaryColumn _ _ = True
-#define BIN_ENC(F) pgEncodeValue _ _ = PGBinaryValue . buildPGValue . F
-#define BIN_DEC(F) pgDecodeBinary _ = F
+#define BIN_ENC(F) pgEncodeValue _ _ = PGBinaryValue . buildPGValue . (F)
+#define BIN_DEC(F) pgDecodeBinary _ = binDec (F)
 #else
 #define BIN_COL
 #define BIN_ENC(F)
 #define BIN_DEC(F)
 #endif
 
-instance PGType "any"
+instance PGType "any" where
+  type PGVal "any" = PGValue
 instance PGType t => PGColumn t PGValue where
   pgDecode _ = PGTextValue
   pgDecodeBinary _ _ = PGBinaryValue
@@ -244,13 +283,18 @@
   pgEncode _ (PGBinaryValue _) = error "pgEncode any: binary"
   pgEncodeValue _ _ = id
 
-instance PGType "void"
+instance PGType "void" where
+  type PGVal "void" = ()
+instance PGParameter "void" () where
+  pgEncode _ _ = BSC.empty
 instance PGColumn "void" () where
   pgDecode _ _ = ()
   pgDecodeBinary _ _ _ = ()
   pgDecodeValue _ _ _ = ()
 
-instance PGType "boolean" where BIN_COL
+instance PGType "boolean" where
+  type PGVal "boolean" = Bool
+  BIN_COL
 instance PGParameter "boolean" Bool where
   pgEncode _ False = BSC.singleton 'f'
   pgEncode _ True = BSC.singleton 't'
@@ -262,58 +306,70 @@
     'f' -> False
     't' -> True
     c -> error $ "pgDecode boolean: " ++ [c]
-  BIN_DEC(binDec BinD.bool)
+  BIN_DEC(BinD.bool)
 
 type OID = Word32
-instance PGType "oid" where BIN_COL
+instance PGType "oid" where
+  type PGVal "oid" = OID
+  BIN_COL
 instance PGParameter "oid" OID where
   pgEncode _ = BSC.pack . show
   pgLiteral = pgEncode
   BIN_ENC(BinE.int4_word32)
 instance PGColumn "oid" OID where
   pgDecode _ = read . BSC.unpack
-  BIN_DEC(binDec BinD.int)
+  BIN_DEC(BinD.int)
 
-instance PGType "smallint" where BIN_COL
+instance PGType "smallint" where
+  type PGVal "smallint" = Int16
+  BIN_COL
 instance PGParameter "smallint" Int16 where
   pgEncode _ = BSC.pack . show
   pgLiteral = pgEncode
   BIN_ENC(BinE.int2_int16)
 instance PGColumn "smallint" Int16 where
   pgDecode _ = read . BSC.unpack
-  BIN_DEC(binDec BinD.int)
+  BIN_DEC(BinD.int)
 
-instance PGType "integer" where BIN_COL
+instance PGType "integer" where 
+  type PGVal "integer" = Int32
+  BIN_COL
 instance PGParameter "integer" Int32 where
   pgEncode _ = BSC.pack . show
   pgLiteral = pgEncode
   BIN_ENC(BinE.int4_int32)
 instance PGColumn "integer" Int32 where
   pgDecode _ = read . BSC.unpack
-  BIN_DEC(binDec BinD.int)
+  BIN_DEC(BinD.int)
 
-instance PGType "bigint" where BIN_COL
+instance PGType "bigint" where
+  type PGVal "bigint" = Int64
+  BIN_COL
 instance PGParameter "bigint" Int64 where
   pgEncode _ = BSC.pack . show
   pgLiteral = pgEncode
   BIN_ENC(BinE.int8_int64)
 instance PGColumn "bigint" Int64 where
   pgDecode _ = read . BSC.unpack
-  BIN_DEC(binDec BinD.int)
+  BIN_DEC(BinD.int)
 
-instance PGType "real" where BIN_COL
+instance PGType "real" where
+  type PGVal "real" = Float
+  BIN_COL
 instance PGParameter "real" Float where
   pgEncode _ = BSC.pack . show
   pgLiteral = pgEncode
   BIN_ENC(BinE.float4)
 instance PGColumn "real" Float where
   pgDecode _ = read . BSC.unpack
-  BIN_DEC(binDec BinD.float4)
+  BIN_DEC(BinD.float4)
 instance PGColumn "real" Double where
   pgDecode _ = read . BSC.unpack
-  BIN_DEC((realToFrac .) . binDec BinD.float4)
+  BIN_DEC(realToFrac <$> BinD.float4)
 
-instance PGType "double precision" where BIN_COL
+instance PGType "double precision" where
+  type PGVal "double precision" = Double
+  BIN_COL
 instance PGParameter "double precision" Double where
   pgEncode _ = BSC.pack . show
   pgLiteral = pgEncode
@@ -324,15 +380,23 @@
   BIN_ENC(BinE.float8 . realToFrac)
 instance PGColumn "double precision" Double where
   pgDecode _ = read . BSC.unpack
-  BIN_DEC(binDec BinD.float8)
+  BIN_DEC(BinD.float8)
 
-instance PGType "\"char\"" where BIN_COL
+instance PGType "\"char\"" where
+  type PGVal "\"char\"" = Word8
+  BIN_COL
+instance PGParameter "\"char\"" Word8 where
+  pgEncode _ = BS.singleton
+  BIN_ENC(BinE.char . w2c)
+instance PGColumn "\"char\"" Word8 where
+  pgDecode _ = BS.head
+  BIN_DEC(c2w <$> BinD.char)
 instance PGParameter "\"char\"" Char where
   pgEncode _ = BSC.singleton
   BIN_ENC(BinE.char)
 instance PGColumn "\"char\"" Char where
   pgDecode _ = BSC.head
-  BIN_DEC(binDec BinD.char)
+  BIN_DEC(BinD.char)
 
 
 class PGType t => PGStringType t
@@ -342,7 +406,7 @@
   BIN_ENC(BinE.text_strict . T.pack)
 instance PGStringType t => PGColumn t String where
   pgDecode _ = BSU.toString
-  BIN_DEC((T.unpack .) . binDec BinD.text_strict)
+  BIN_DEC(T.unpack <$> BinD.text_strict)
 
 instance
 #if __GLASGOW_HASKELL__ >= 710
@@ -357,12 +421,27 @@
 #endif
     PGStringType t => PGColumn t BS.ByteString where
   pgDecode _ = id
-  BIN_DEC((TE.encodeUtf8 .) . binDec BinD.text_strict)
+  BIN_DEC(TE.encodeUtf8 <$> BinD.text_strict)
 
 instance
 #if __GLASGOW_HASKELL__ >= 710
     {-# OVERLAPPABLE #-}
 #endif
+    PGStringType t => PGParameter t PGName where
+  pgEncode _ = pgNameBS
+  BIN_ENC(BinE.text_strict . TE.decodeUtf8 . pgNameBS)
+instance
+#if __GLASGOW_HASKELL__ >= 710
+    {-# OVERLAPPABLE #-}
+#endif
+    PGStringType t => PGColumn t PGName where
+  pgDecode _ = PGName . BS.unpack
+  BIN_DEC(PGName . BS.unpack . TE.encodeUtf8 <$> BinD.text_strict)
+
+instance
+#if __GLASGOW_HASKELL__ >= 710
+    {-# OVERLAPPABLE #-}
+#endif
     PGStringType t => PGParameter t BSL.ByteString where
   pgEncode _ = BSL.toStrict
   BIN_ENC(BinE.text_lazy . TLE.decodeUtf8)
@@ -372,28 +451,39 @@
 #endif
     PGStringType t => PGColumn t BSL.ByteString where
   pgDecode _ = BSL.fromStrict
-  BIN_DEC((TLE.encodeUtf8 .) . binDec BinD.text_lazy)
+  BIN_DEC(TLE.encodeUtf8 <$> BinD.text_lazy)
 
-#ifdef USE_TEXT
+#ifdef VERSION_text
 instance PGStringType t => PGParameter t T.Text where
   pgEncode _ = TE.encodeUtf8
   BIN_ENC(BinE.text_strict)
 instance PGStringType t => PGColumn t T.Text where
   pgDecode _ = TE.decodeUtf8
-  BIN_DEC(binDec BinD.text_strict)
+  BIN_DEC(BinD.text_strict)
 
 instance PGStringType t => PGParameter t TL.Text where
   pgEncode _ = BSL.toStrict . TLE.encodeUtf8
   BIN_ENC(BinE.text_lazy)
 instance PGStringType t => PGColumn t TL.Text where
   pgDecode _ = TL.fromStrict . TE.decodeUtf8
-  BIN_DEC(binDec BinD.text_lazy)
+  BIN_DEC(BinD.text_lazy)
+#define PGVALSTRING T.Text
+#else
+#define PGVALSTRING String
 #endif
 
-instance PGType "text" where BIN_COL
-instance PGType "character varying" where BIN_COL
-instance PGType "name" where BIN_COL
-instance PGType "bpchar" where BIN_COL
+instance PGType "text" where
+  type PGVal "text" = PGVALSTRING
+  BIN_COL
+instance PGType "character varying" where
+  type PGVal "character varying" = PGVALSTRING
+  BIN_COL
+instance PGType "name" where
+  type PGVal "name" = PGVALSTRING
+  BIN_COL
+instance PGType "bpchar" where
+  type PGVal "bpchar" = PGVALSTRING
+  BIN_COL
 instance PGStringType "text"
 instance PGStringType "character varying"
 instance PGStringType "name" -- limit 63 characters; not strictly textsend but essentially the same
@@ -414,7 +504,9 @@
   pd [x] = error $ "pgDecode bytea: " ++ show x
   unhex = fromIntegral . digitToInt . w2c
 
-instance PGType "bytea" where BIN_COL
+instance PGType "bytea" where
+  type PGVal "bytea" = BS.ByteString
+  BIN_COL
 instance
 #if __GLASGOW_HASKELL__ >= 710
     {-# OVERLAPPING #-}
@@ -429,7 +521,7 @@
 #endif
     PGColumn "bytea" BSL.ByteString where
   pgDecode _ = BSL.pack . decodeBytea
-  BIN_DEC(binDec BinD.bytea_lazy)
+  BIN_DEC(BinD.bytea_lazy)
 instance
 #if __GLASGOW_HASKELL__ >= 710
     {-# OVERLAPPING #-}
@@ -444,7 +536,7 @@
 #endif
     PGColumn "bytea" BS.ByteString where
   pgDecode _ = BS.pack . decodeBytea
-  BIN_DEC(binDec BinD.bytea_strict)
+  BIN_DEC(BinD.bytea_strict)
 
 readTime :: Time.ParseTime t => String -> String -> t
 readTime =
@@ -455,28 +547,30 @@
 #endif
     defaultTimeLocale
 
-instance PGType "date" where BIN_COL
+instance PGType "date" where
+  type PGVal "date" = Time.Day
+  BIN_COL
 instance PGParameter "date" Time.Day where
   pgEncode _ = BSC.pack . Time.showGregorian
   pgLiteral t = pgQuoteUnsafe . pgEncode t
   BIN_ENC(BinE.date)
 instance PGColumn "date" Time.Day where
   pgDecode _ = readTime "%F" . BSC.unpack
-  BIN_DEC(binDec BinD.date)
+  BIN_DEC(BinD.date)
 
-binColDatetime :: PGTypeEnv -> PGTypeName t -> Bool
-#ifdef USE_BINARY
+binColDatetime :: PGTypeEnv -> PGTypeID t -> Bool
+#ifdef VERSION_postgresql_binary
 binColDatetime PGTypeEnv{ pgIntegerDatetimes = Just _ } _ = True
 #endif
 binColDatetime _ _ = False
 
-#ifdef USE_BINARY
-binEncDatetime :: PGParameter t a => BinE.Encoder a -> BinE.Encoder a -> PGTypeEnv -> PGTypeName t -> a -> PGValue
+#ifdef VERSION_postgresql_binary
+binEncDatetime :: PGParameter t a => BinE.Encoder a -> BinE.Encoder a -> PGTypeEnv -> PGTypeID t -> a -> PGValue
 binEncDatetime _ ff PGTypeEnv{ pgIntegerDatetimes = Just False } _ = PGBinaryValue . buildPGValue . ff
 binEncDatetime fi _ PGTypeEnv{ pgIntegerDatetimes = Just True } _ = PGBinaryValue . buildPGValue . fi
 binEncDatetime _ _ PGTypeEnv{ pgIntegerDatetimes = Nothing } t = PGTextValue . pgEncode t
 
-binDecDatetime :: PGColumn t a => BinD.Decoder a -> BinD.Decoder a -> PGTypeEnv -> PGTypeName t -> PGBinaryValue -> a
+binDecDatetime :: PGColumn t a => BinD.Decoder a -> BinD.Decoder a -> PGTypeEnv -> PGTypeID t -> PGBinaryValue -> a
 binDecDatetime _ ff PGTypeEnv{ pgIntegerDatetimes = Just False } = binDec ff
 binDecDatetime fi _ PGTypeEnv{ pgIntegerDatetimes = Just True } = binDec fi
 binDecDatetime _ _ PGTypeEnv{ pgIntegerDatetimes = Nothing } = error "pgDecodeBinary: unknown integer_datetimes value"
@@ -493,67 +587,72 @@
 fixTZ (c:s) = c:fixTZ s
 
 instance PGType "time without time zone" where
+  type PGVal "time without time zone" = Time.TimeOfDay
   pgBinaryColumn = binColDatetime
 instance PGParameter "time without time zone" Time.TimeOfDay where
   pgEncode _ = BSC.pack . Time.formatTime defaultTimeLocale "%T%Q"
   pgLiteral t = pgQuoteUnsafe . pgEncode t
-#ifdef USE_BINARY
+#ifdef VERSION_postgresql_binary
   pgEncodeValue = binEncDatetime BinE.time_int BinE.time_float
 #endif
 instance PGColumn "time without time zone" Time.TimeOfDay where
   pgDecode _ = readTime "%T%Q" . BSC.unpack
-#ifdef USE_BINARY
+#ifdef VERSION_postgresql_binary
   pgDecodeBinary = binDecDatetime BinD.time_int BinD.time_float
 #endif
 
 instance PGType "time with time zone" where
+  type PGVal "time with time zone" = (Time.TimeOfDay, Time.TimeZone)
   pgBinaryColumn = binColDatetime
 instance PGParameter "time with time zone" (Time.TimeOfDay, Time.TimeZone) where
   pgEncode _ (t, z) = BSC.pack $ Time.formatTime defaultTimeLocale "%T%Q" t ++ fixTZ (Time.formatTime defaultTimeLocale "%z" z)
   pgLiteral t = pgQuoteUnsafe . pgEncode t
-#ifdef USE_BINARY
+#ifdef VERSION_postgresql_binary
   pgEncodeValue = binEncDatetime BinE.timetz_int BinE.timetz_float
 #endif
 instance PGColumn "time with time zone" (Time.TimeOfDay, Time.TimeZone) where
   pgDecode _ = (Time.localTimeOfDay . Time.zonedTimeToLocalTime &&& Time.zonedTimeZone) . readTime "%T%Q%z" . fixTZ . BSC.unpack
-#ifdef USE_BINARY
+#ifdef VERSION_postgresql_binary
   pgDecodeBinary = binDecDatetime BinD.timetz_int BinD.timetz_float
 #endif
 
 instance PGType "timestamp without time zone" where
+  type PGVal "timestamp without time zone" = Time.LocalTime
   pgBinaryColumn = binColDatetime
 instance PGParameter "timestamp without time zone" Time.LocalTime where
   pgEncode _ = BSC.pack . Time.formatTime defaultTimeLocale "%F %T%Q"
   pgLiteral t = pgQuoteUnsafe . pgEncode t
-#ifdef USE_BINARY
+#ifdef VERSION_postgresql_binary
   pgEncodeValue = binEncDatetime BinE.timestamp_int BinE.timestamp_float
 #endif
 instance PGColumn "timestamp without time zone" Time.LocalTime where
   pgDecode _ = readTime "%F %T%Q" . BSC.unpack
-#ifdef USE_BINARY
+#ifdef VERSION_postgresql_binary
   pgDecodeBinary = binDecDatetime BinD.timestamp_int BinD.timestamp_float
 #endif
 
 instance PGType "timestamp with time zone" where
+  type PGVal "timestamp with time zone" = Time.UTCTime
   pgBinaryColumn = binColDatetime
 instance PGParameter "timestamp with time zone" Time.UTCTime where
   pgEncode _ = BSC.pack . fixTZ . Time.formatTime defaultTimeLocale "%F %T%Q%z"
   -- pgLiteral t = pgQuoteUnsafe . pgEncode t
-#ifdef USE_BINARY
+#ifdef VERSION_postgresql_binary
   pgEncodeValue = binEncDatetime BinE.timestamptz_int BinE.timestamptz_float
 #endif
 instance PGColumn "timestamp with time zone" Time.UTCTime where
   pgDecode _ = readTime "%F %T%Q%z" . fixTZ . BSC.unpack
-#ifdef USE_BINARY
+#ifdef VERSION_postgresql_binary
   pgDecodeBinary = binDecDatetime BinD.timestamptz_int BinD.timestamptz_float
 #endif
 
 instance PGType "interval" where
+  type PGVal "interval" = Time.DiffTime
   pgBinaryColumn = binColDatetime
 instance PGParameter "interval" Time.DiffTime where
   pgEncode _ = BSC.pack . show
   pgLiteral t = pgQuoteUnsafe . pgEncode t
-#ifdef USE_BINARY
+#ifdef VERSION_postgresql_binary
   pgEncodeValue = binEncDatetime BinE.interval_int BinE.interval_float
 #endif
 -- |Representation of DiffTime as interval.
@@ -576,11 +675,18 @@
       return $ x * u
     day = 86400
     month = 2629746
-#ifdef USE_BINARY
+#ifdef VERSION_postgresql_binary
   pgDecodeBinary = binDecDatetime BinD.interval_int BinD.interval_float
 #endif
 
-instance PGType "numeric" where BIN_COL
+instance PGType "numeric" where
+  type PGVal "numeric" = 
+#ifdef VERSION_scientific
+    Scientific
+#else
+    Rational
+#endif
+  BIN_COL
 instance PGParameter "numeric" Rational where
   pgEncode _ r
     | denominator r == 0 = BSC.pack "NaN" -- this can't happen
@@ -600,7 +706,7 @@
     ur [(x,"")] = x
     ur _ = error $ "pgDecode numeric: " ++ s
     s = BSC.unpack bs
-  BIN_DEC((realToFrac .) . binDec BinD.numeric)
+  BIN_DEC(realToFrac <$> BinD.numeric)
 
 -- This will produce infinite(-precision) strings
 showRational :: Rational -> String
@@ -609,25 +715,27 @@
   frac 0 = ""
   frac f = intToDigit i : frac f' where (i, f') = properFraction (10 * f)
 
-#ifdef USE_SCIENTIFIC
+#ifdef VERSION_scientific
 instance PGParameter "numeric" Scientific where
   pgEncode _ = BSC.pack . show
   pgLiteral = pgEncode
   BIN_ENC(BinE.numeric)
 instance PGColumn "numeric" Scientific where
   pgDecode _ = read . BSC.unpack
-  BIN_DEC(binDec BinD.numeric)
+  BIN_DEC(BinD.numeric)
 #endif
 
-#ifdef USE_UUID
-instance PGType "uuid" where BIN_COL
+#ifdef VERSION_uuid
+instance PGType "uuid" where
+  type PGVal "uuid" = UUID.UUID
+  BIN_COL
 instance PGParameter "uuid" UUID.UUID where
   pgEncode _ = UUID.toASCIIBytes
   pgLiteral t = pgQuoteUnsafe . pgEncode t
   BIN_ENC(BinE.uuid)
 instance PGColumn "uuid" UUID.UUID where
   pgDecode _ u = fromMaybe (error $ "pgDecode uuid: " ++ BSC.unpack u) $ UUID.fromASCIIBytes u
-  BIN_DEC(binDec BinD.uuid)
+  BIN_DEC(BinD.uuid)
 #endif
 
 -- |Generic class of composite (row or record) types.
@@ -643,23 +751,32 @@
     pa = P.char '(' *> P.sepBy el (P.char ',') <* P.char ')' <* P.endOfInput
     el = parsePGDQuote True "()," BS.null
 
-instance PGType "record"
+instance PGType "record" where
+  type PGVal "record" = PGRecord
 -- |The generic anonymous record type, as created by @ROW@.
 -- In this case we can not know the types, and in fact, PostgreSQL does not accept values of this type regardless (except as literals).
 instance PGRecordType "record"
 
-#ifdef USE_AESON
-instance PGType "json"
+#ifdef VERSION_aeson
+instance PGType "json" where
+  type PGVal "json" = JSON.Value
+  BIN_COL
 instance PGParameter "json" JSON.Value where
   pgEncode _ = BSL.toStrict . JSON.encode
+  BIN_ENC(BinE.json_ast)
 instance PGColumn "json" JSON.Value where
   pgDecode _ j = either (error . ("pgDecode json (" ++) . (++ ("): " ++ BSC.unpack j))) id $ P.parseOnly JSON.json j
+  BIN_DEC(BinD.json_ast)
 
-instance PGType "jsonb"
+instance PGType "jsonb" where
+  type PGVal "jsonb" = JSON.Value
+  BIN_COL
 instance PGParameter "jsonb" JSON.Value where
   pgEncode _ = BSL.toStrict . JSON.encode
+  BIN_ENC(BinE.jsonb_ast)
 instance PGColumn "jsonb" JSON.Value where
   pgDecode _ j = either (error . ("pgDecode jsonb (" ++) . (++ ("): " ++ BSC.unpack j))) id $ P.parseOnly JSON.json j
+  BIN_DEC(BinD.jsonb_ast)
 #endif
 
 {-
diff --git a/postgresql-typed.cabal b/postgresql-typed.cabal
--- a/postgresql-typed.cabal
+++ b/postgresql-typed.cabal
@@ -1,9 +1,9 @@
 Name:          postgresql-typed
-Version:       0.4.5
+Version:       0.5.0
 Cabal-Version: >= 1.8
 License:       BSD3
 License-File:  COPYING
-Copyright:     2010-2013 Chris Forno, 2014-2016 Dylan Simon
+Copyright:     2010-2013 Chris Forno, 2014-2017 Dylan Simon
 Author:        Dylan Simon
 Maintainer:    Dylan Simon <dylan-pgtyped@dylex.net>
 Stability:     provisional
@@ -81,28 +81,24 @@
     Database.PostgreSQL.Typed.TemplatePG
     Database.PostgreSQL.Typed.SQLToken
     Database.PostgreSQL.Typed.ErrCodes
+    Database.PostgreSQL.Typed.Relation
   Other-Modules:
     Paths_postgresql_typed
+    Database.PostgreSQL.Typed.TypeCache
   GHC-Options: -Wall
   if flag(md5)
     Build-Depends: cryptonite >= 0.5, memory >= 0.5
-    CPP-options: -DUSE_MD5
   if flag(binary)
-    Build-Depends: postgresql-binary >= 0.7, text >= 1, uuid >= 1.3, scientific >= 0.3
-    CPP-options: -DUSE_BINARY -DUSE_TEXT -DUSE_UUID -DUSE_SCIENTIFIC
+    Build-Depends: postgresql-binary >= 0.8, text >= 1, uuid >= 1.3, scientific >= 0.3
   else
     if flag(text)
       Build-Depends: text >= 1
-      CPP-options: -DUSE_TEXT
     if flag(uuid)
       Build-Depends: uuid >= 1.3
-      CPP-options: -DUSE_UUID
     if flag(scientific)
       Build-Depends: scientific >= 0.3
-      CPP-options: -DUSE_SCIENTIFIC
   if flag(aeson)
     Build-Depends: aeson >= 0.7
-    CPP-options: -DUSE_AESON
   if flag(HDBC)
     Build-Depends: HDBC >= 2.2
     Exposed-Modules:
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -1,11 +1,11 @@
-{-# LANGUAGE OverloadedStrings, FlexibleInstances, MultiParamTypeClasses, DataKinds, DeriveDataTypeable #-}
+{-# LANGUAGE OverloadedStrings, FlexibleInstances, MultiParamTypeClasses, DataKinds, DeriveDataTypeable, TypeFamilies, PatternGuards, StandaloneDeriving #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
--- {-# OPTIONS_GHC -ddump-splices #-}
+--{-# OPTIONS_GHC -ddump-splices #-}
 module Main (main) where
 
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Char8 as BSC
-import Data.Char (isDigit)
+import Data.Char (isDigit, toUpper)
 import Data.Int (Int32)
 import qualified Data.Time as Time
 import System.Exit (exitSuccess, exitFailure)
@@ -19,6 +19,7 @@
 import Database.PostgreSQL.Typed.Enum
 import Database.PostgreSQL.Typed.Inet
 import Database.PostgreSQL.Typed.SQLToken
+import Database.PostgreSQL.Typed.Relation
 
 import Connect
 
@@ -31,7 +32,16 @@
 -- This runs at compile-time:
 [pgSQL|!CREATE TYPE myenum AS enum ('abc', 'DEF', 'XX_ye')|]
 
-makePGEnum "myenum" "MyEnum" ("MyEnum_" ++)
+[pgSQL|!CREATE TABLE myfoo (id serial primary key, adx myenum, bar char(4))|]
+
+dataPGEnum "MyEnum" "myenum" ("MyEnum_" ++)
+
+deriving instance Show MyEnum
+
+dataPGRelation "MyFoo" "myfoo" (\(c:s) -> "foo" ++ toUpper c : s)
+
+_fooRow :: MyFoo
+_fooRow = MyFoo{ fooId = 1, fooAdx = Just MyEnum_DEF, fooBar = Just "abcd" }
 
 instance Q.Arbitrary MyEnum where
   arbitrary = Q.arbitraryBoundedEnum
