diff --git a/exe/Example.hs b/exe/Example.hs
--- a/exe/Example.hs
+++ b/exe/Example.hs
@@ -29,7 +29,7 @@
        '[ "pk_users" ::: 'PrimaryKey '["id"] ] :=>
        '[ "id" ::: 'Def :=> 'NotNull 'PGint4
         , "name" ::: 'NoDef :=> 'NotNull 'PGtext
-        , "vec" ::: 'NoDef :=> 'NotNull ('PGvararray 'PGint2)
+        , "vec" ::: 'NoDef :=> 'NotNull ('PGvararray ('Null 'PGint2))
         ])
    , "emails" ::: 'Table (
        '[  "pk_emails" ::: 'PrimaryKey '["id"]
@@ -60,7 +60,7 @@
 teardown :: Definition Schema '[]
 teardown = dropTable #emails >>> dropTable #users
 
-insertUser :: Manipulation Schema '[ 'NotNull 'PGtext, 'NotNull ('PGvararray 'PGint2)]
+insertUser :: Manipulation Schema '[ 'NotNull 'PGtext, 'NotNull ('PGvararray ('Null 'PGint2))]
   '[ "fromOnly" ::: 'NotNull 'PGint4 ]
 insertUser = insertRows #users
   (Default `as` #id :* Set (param @1) `as` #name :* Set (param @2) `as` #vec) []
@@ -76,7 +76,7 @@
 getUsers :: Query Schema '[]
   '[ "userName" ::: 'NotNull 'PGtext
    , "userEmail" ::: 'Null 'PGtext
-   , "userVec" ::: 'NotNull ('PGvararray 'PGint2)]
+   , "userVec" ::: 'NotNull ('PGvararray ('Null 'PGint2))]
 getUsers = select
   (#u ! #name `as` #userName :* #e ! #email `as` #userEmail :* #u ! #vec `as` #userVec)
   ( from (table (#users `as` #u)
diff --git a/squeal-postgresql.cabal b/squeal-postgresql.cabal
--- a/squeal-postgresql.cabal
+++ b/squeal-postgresql.cabal
@@ -1,5 +1,5 @@
 name: squeal-postgresql
-version: 0.3.2.0
+version: 0.4.0.0
 synopsis: Squeal PostgreSQL Library
 description: Squeal is a type-safe embedding of PostgreSQL in Haskell
 homepage: https://github.com/morphismtech/squeal
@@ -37,7 +37,7 @@
   ghc-options: -Wall
   build-depends:
       aeson >= 1.2.4.0
-    , base >= 4.10.1.0 && < 5.0
+    , base >= 4.11.1.0 && < 5.0
     , binary-parser >= 0.5.5
     , bytestring >= 0.10.8.2
     , bytestring-strict-builder >= 0.4.5
@@ -50,7 +50,7 @@
     , network-ip >= 0.3.0.2
     , postgresql-binary >= 0.12.1
     , postgresql-libpq >= 0.9.4.1
-    , profunctors >= 5.2.2
+    , records-sop >= 0.1.0.0
     , resource-pool >= 0.2.3.2
     , scientific >= 0.3.5.3
     , text >= 1.2.3.0
@@ -69,6 +69,24 @@
   build-depends:
       base >= 4.10.0.0
     , doctest >= 0.11.4
+
+test-suite squeal-postgresql-specs
+  default-language: Haskell2010
+  type: exitcode-stdio-1.0
+  hs-source-dirs: test/Specs
+  ghc-options: -Wall  -main-is Specs
+  main-is: Specs.hs
+  other-modules: ExceptionHandling
+  build-depends:
+      base >= 4.10.0.0
+    , bytestring >= 0.10.8.2
+    , generics-sop >= 0.3.1.0
+    , hspec >= 2.4.8
+    , squeal-postgresql
+    , text >= 1.2.2.2
+    , transformers >= 0.5.2.0
+    , transformers-base >= 0.4.4
+    , vector >= 0.12.0.1
 
 executable squeal-postgresql-example
   default-language: Haskell2010
diff --git a/src/Squeal/PostgreSQL/Binary.hs b/src/Squeal/PostgreSQL/Binary.hs
--- a/src/Squeal/PostgreSQL/Binary.hs
+++ b/src/Squeal/PostgreSQL/Binary.hs
@@ -11,65 +11,104 @@
 do not need to define your own instances to decode retrieved rows into Haskell values or
 to encode Haskell values into statement parameters.
 
+Let's see some examples. We'll need some imports
+
 >>> import Data.Int (Int16)
 >>> import Data.Text (Text)
-
->>> data Row = Row { col1 :: Int16, col2 :: Text } deriving (Eq, GHC.Generic)
->>> instance Generic Row
->>> instance HasDatatypeInfo Row
-
 >>> import Control.Monad (void)
 >>> import Control.Monad.Base (liftBase)
 >>> import Squeal.PostgreSQL
 
+Define a Haskell datatype `Row` that will serve as both the input and output of a simple
+round trip query.
+
+>>> data Row = Row { col1 :: Int16, col2 :: Text, col3 :: Maybe Bool } deriving (Eq, GHC.Generic)
+>>> instance Generic Row
+>>> instance HasDatatypeInfo Row
 >>> :{
 let
-  query :: Query '[]
-    '[ 'NotNull 'PGint2, 'NotNull 'PGtext]
-    '["col1" ::: 'NotNull 'PGint2, "col2" ::: 'NotNull 'PGtext]
-  query = values_ (param @1 `as` #col1 :* param @2 `as` #col2)
+  roundTrip :: Query '[] (TuplePG Row) (RowPG Row)
+  roundTrip = values_ $
+    parameter @1 int2 `as` #col1 :*
+    parameter @2 text `as` #col2 :*
+    parameter @3 bool `as` #col3
 :}
 
+So long as we can encode the parameters and then decode the result of the query,
+the input and output should be equal.
+
+>>> let input = Row 2 "hi" (Just True)
 >>> :{
+void . withConnection "host=localhost port=5432 dbname=exampledb" $ do
+  result <- runQueryParams roundTrip input
+  Just output <- firstRow result
+  liftBase . print $ input == output
+:}
+True
+
+In addition to being able to encode and decode basic Haskell types
+like `Int16` and `Text`, Squeal permits you to encode and decode Haskell types to
+Postgres array, enumerated and composite types and json. Let's see another example,
+this time using the `Vector` type which corresponds to variable length arrays
+and homogeneous tuples which correspond to fixed length arrays. We can even
+create multi-dimensional fixed length arrays.
+
+>>> :{
+data Row = Row
+  { col1 :: Vector Int16
+  , col2 :: (Maybe Int16,Maybe Int16)
+  , col3 :: ((Int16,Int16),(Int16,Int16),(Int16,Int16))
+  } deriving (Eq, GHC.Generic)
+:}
+
+>>> instance Generic Row
+>>> instance HasDatatypeInfo Row
+
+Once again, we define a simple round trip query.
+
+>>> :{
 let
-  roundtrip :: IO ()
-  roundtrip = void . withConnection "host=localhost port=5432 dbname=exampledb" $ do
-    result <- runQueryParams query (2 :: Int16, "hi" :: Text)
-    Just row <- firstRow result
-    liftBase . print $ row == Row 2 "hi"
+  roundTrip :: Query '[] (TuplePG Row) (RowPG Row)
+  roundTrip = values_ $
+    parameter @1 (int2 & vararray)                  `as` #col1 :*
+    parameter @2 (int2 & fixarray @2)               `as` #col2 :*
+    parameter @3 (int2 & fixarray @2 & fixarray @3) `as` #col3
 :}
 
->>> roundtrip
+>>> :set -XOverloadedLists
+>>> let input = Row [1,2] (Just 1,Nothing) ((1,2),(3,4),(5,6))
+>>> :{
+void . withConnection "host=localhost port=5432 dbname=exampledb" $ do
+  result <- runQueryParams roundTrip input
+  Just output <- firstRow result
+  liftBase . print $ input == output
+:}
 True
 
-In addition to being able to encode and decode basic Haskell types like `Int16` and `Text`,
-Squeal permits you to encode and decode Haskell types which are equivalent to
-Postgres enumerated and composite types.
-
 Enumerated (enum) types are data types that comprise a static, ordered set of values.
 They are equivalent to Haskell algebraic data types whose constructors are nullary.
 An example of an enum type might be the days of the week,
 or a set of status values for a piece of data.
 
->>> data Schwarma = Beef | Lamb | Chicken deriving (Show, GHC.Generic)
+>>> data Schwarma = Beef | Lamb | Chicken deriving (Eq, Show, GHC.Generic)
 >>> instance Generic Schwarma
 >>> instance HasDatatypeInfo Schwarma
 
 A composite type represents the structure of a row or record;
-it is essentially just a list of field names and their data types. They are almost
-equivalent to Haskell record types. However, because of the potential presence of @NULL@
-all the record fields must be `Maybe`s of basic types.
+it is essentially just a list of field names and their data types.
 
->>> data Person = Person {name :: Maybe Text, age :: Maybe Int32} deriving (Show, GHC.Generic)
+>>> data Person = Person {name :: Text, age :: Int32} deriving (Eq, Show, GHC.Generic)
 >>> instance Generic Person
 >>> instance HasDatatypeInfo Person
+>>> instance Aeson.FromJSON Person
+>>> instance Aeson.ToJSON Person
 
 We can create the equivalent Postgres types directly from their Haskell types.
 
 >>> :{
 type Schema =
-  '[ "schwarma" ::: 'Typedef (EnumFrom Schwarma)
-   , "person" ::: 'Typedef (CompositeFrom Person)
+  '[ "schwarma" ::: 'Typedef (PG (Enumerated Schwarma))
+   , "person" ::: 'Typedef (PG (Composite Person))
    ]
 :}
 
@@ -81,25 +120,40 @@
     createTypeCompositeFrom @Person #person
 :}
 
-Then we can perform roundtrip queries;
+Let's demonstrate how to associate our Haskell types `Schwarma` and `Person`
+with enumerated, composite or json types in Postgres. First create a Haskell
+`Row` type using the `Enumerated`, `Composite` and `Json` newtypes as fields.
 
 >>> :{
+data Row = Row
+  { schwarma :: Enumerated Schwarma
+  , person1 :: Composite Person
+  , person2 :: Json Person
+  } deriving (Eq, GHC.Generic)
+:}
+
+>>> instance Generic Row
+>>> instance HasDatatypeInfo Row
+>>> :{
 let
-  querySchwarma :: Query Schema
-    '[ 'NotNull (EnumFrom Schwarma)]
-    '["fromOnly" ::: 'NotNull (EnumFrom Schwarma)]
-  querySchwarma = values_ (parameter @1 #schwarma `as` #fromOnly)
+  input = Row
+    (Enumerated Chicken)
+    (Composite (Person "Faisal" 24))
+    (Json (Person "Ahmad" 48))
 :}
 
+Once again, define a round trip query.
+
 >>> :{
 let
-  queryPerson :: Query Schema
-    '[ 'NotNull (CompositeFrom Person)]
-    '["fromOnly" ::: 'NotNull (CompositeFrom Person)]
-  queryPerson = values_ (parameter @1 #person `as` #fromOnly)
+  roundTrip :: Query Schema (TuplePG Row) (RowPG Row)
+  roundTrip = values_ $
+    parameter @1 (typedef #schwarma) `as` #schwarma :*
+    parameter @2 (typedef #person)   `as` #person1  :*
+    parameter @3 json                `as` #person2
 :}
 
-And finally drop the types.
+Finally, we can drop our type definitions.
 
 >>> :{
 let
@@ -112,20 +166,16 @@
 >>> :{
 let
   session = do
-    result1 <- runQueryParams querySchwarma (Only Chicken)
-    Just (Only schwarma) <- firstRow result1
-    liftBase $ print (schwarma :: Schwarma)
-    result2 <- runQueryParams queryPerson (Only (Person (Just "Faisal") (Just 24)))
-    Just (Only person) <- firstRow result2
-    liftBase $ print (person :: Person)
+    result <- runQueryParams roundTrip input
+    Just output <- firstRow result
+    liftBase . print $ input == output
 in
   void . withConnection "host=localhost port=5432 dbname=exampledb" $
     define setup
     & pqThen session
     & pqThen (define teardown)
 :}
-Chicken
-Person {name = Just "Faisal", age = Just 24}
+True
 -}
 
 {-# LANGUAGE
@@ -134,10 +184,13 @@
   , DeriveFunctor
   , DeriveGeneric
   , DeriveTraversable
+  , DefaultSignatures
   , FlexibleContexts
   , FlexibleInstances
+  , FunctionalDependencies
   , GADTs
   , LambdaCase
+  , OverloadedStrings
   , MultiParamTypeClasses
   , ScopedTypeVariables
   , TypeApplications
@@ -149,35 +202,37 @@
 module Squeal.PostgreSQL.Binary
   ( -- * Encoding
     ToParam (..)
-  , ToColumnParam (..)
   , ToParams (..)
     -- * Decoding
   , FromValue (..)
-  , FromColumnValue (..)
   , FromRow (..)
     -- * Only
   , Only (..)
   ) where
 
 import BinaryParser
-import ByteString.StrictBuilder
-import Data.Aeson hiding (Null)
+import ByteString.StrictBuilder (builderLength, int32BE, int64BE, word32BE)
+import Control.Arrow (left)
+import Control.Monad
 import Data.Int
 import Data.Kind
-import Data.Monoid hiding (All)
 import Data.Scientific
 import Data.Time
 import Data.UUID.Types
 import Data.Vector (Vector)
 import Data.Word
 import Generics.SOP
+import Generics.SOP.Record
 import GHC.TypeLits
 import Network.IP.Addr
 
-import qualified Data.ByteString.Lazy as Lazy
-import qualified Data.ByteString as Strict hiding (pack, unpack)
-import qualified Data.Text.Lazy as Lazy
-import qualified Data.Text as Strict
+import qualified Data.Aeson as Aeson
+import qualified Data.ByteString.Lazy as Lazy (ByteString)
+import qualified Data.ByteString.Lazy as Lazy.ByteString
+import qualified Data.ByteString as Strict (ByteString)
+import qualified Data.Text.Lazy as Lazy (Text)
+import qualified Data.Text as Strict (Text)
+import qualified Data.Text as Strict.Text
 import qualified Data.Vector as Vector
 import qualified GHC.Generics as GHC
 import qualified PostgreSQL.Binary.Decoding as Decoding
@@ -219,6 +274,8 @@
 instance ToParam Char ('PGchar 1) where toParam = K . Encoding.char_utf8
 instance ToParam Strict.Text 'PGtext where toParam = K . Encoding.text_strict
 instance ToParam Lazy.Text 'PGtext where toParam = K . Encoding.text_lazy
+instance ToParam String 'PGtext where
+  toParam = K . Encoding.text_strict . Strict.Text.pack
 instance ToParam Strict.ByteString 'PGbytea where
   toParam = K . Encoding.bytea_strict
 instance ToParam Lazy.ByteString 'PGbytea where
@@ -232,17 +289,29 @@
 instance ToParam UTCTime 'PGtimestamptz where
   toParam = K . Encoding.timestamptz_int
 instance ToParam DiffTime 'PGinterval where toParam = K . Encoding.interval_int
-instance ToParam Value 'PGjson where toParam = K . Encoding.json_ast
-instance ToParam Value 'PGjsonb where toParam = K . Encoding.jsonb_ast
-instance (HasOid pg, ToParam x pg)
-  => ToParam (Vector (Maybe x)) ('PGvararray pg) where
-    toParam = K . Encoding.nullableArray_vector
-      (oid @pg) (unK . toParam @x @pg)
+instance ToParam Aeson.Value 'PGjson where toParam = K . Encoding.json_ast
+instance ToParam Aeson.Value 'PGjsonb where toParam = K . Encoding.jsonb_ast
+instance Aeson.ToJSON x => ToParam (Json x) 'PGjson where
+  toParam = K . Encoding.json_bytes
+    . Lazy.ByteString.toStrict . Aeson.encode . getJson
+instance Aeson.ToJSON x => ToParam (Jsonb x) 'PGjsonb where
+  toParam = K . Encoding.jsonb_bytes
+    . Lazy.ByteString.toStrict . Aeson.encode . getJsonb
+instance ToArray x ('NotNull ('PGvararray ty))
+  => ToParam x ('PGvararray ty) where
+    toParam
+      = K . Encoding.array (baseOid @x @('NotNull ('PGvararray ty)))
+      . unK . toArray @x @('NotNull ('PGvararray ty))
+instance ToArray x ('NotNull ('PGfixarray n ty))
+  => ToParam x ('PGfixarray n ty) where
+    toParam
+      = K . Encoding.array (baseOid @x @('NotNull ('PGfixarray n ty)))
+      . unK . toArray @x @('NotNull ('PGfixarray n ty))
 instance
   ( IsEnumType x
   , HasDatatypeInfo x
-  , LabelsFrom x ~ labels
-  ) => ToParam x ('PGenum labels) where
+  , LabelsPG x ~ labels
+  ) => ToParam (Enumerated x) ('PGenum labels) where
     toParam =
       let
         gshowConstructor :: NP ConstructorInfo xss -> SOP I xss -> String
@@ -253,21 +322,20 @@
           gshowConstructor constructors (SOP xs)
       in
         K . Encoding.text_strict
-        . Strict.pack
+        . Strict.Text.pack
         . gshowConstructor (constructorInfo (datatypeInfo (Proxy @x)))
         . from
+        . getEnumerated
 instance
   ( SListI fields
-  , MapMaybes xs
-  , IsProductType x (Maybes xs)
-  , AllZip ToAliasedParam xs fields
-  , FieldNamesFrom x ~ AliasesOf fields
+  , IsRecord x xs
+  , AllZip ToField xs fields
   , All HasAliasedOid fields
-  ) => ToParam x ('PGcomposite fields) where
+  ) => ToParam (Composite x) ('PGcomposite fields) where
     toParam =
       let
 
-        encoders = htrans (Proxy @ToAliasedParam) toAliasedParam
+        encoders = htrans (Proxy @ToField) toField
 
         composite
           :: All HasAliasedOid row
@@ -301,36 +369,66 @@
               hcfoldMap (Proxy @HasAliasedOid) each fields
 
       in
-        composite . encoders . unMaybes . unZ . unSOP . from
+        composite . encoders . toRecord . getComposite
 
-class HasAliasedOid (field :: (Symbol, PGType)) where aliasedOid :: Word32
-instance HasOid ty => HasAliasedOid (alias ::: ty) where aliasedOid = oid @ty
+class HasAliasedOid (field :: (Symbol, NullityType)) where
+  aliasedOid :: Word32
+instance HasOid ty => HasAliasedOid (alias ::: nullity ty) where
+  aliasedOid = oid @ty
 
-class ToAliasedParam (x :: Type) (field :: (Symbol, PGType)) where
-  toAliasedParam :: Maybe x -> K (Maybe Encoding.Encoding) field
-instance ToParam x ty => ToAliasedParam x (alias ::: ty) where
-  toAliasedParam = \case
-    Nothing -> K Nothing
-    Just x -> K . Just . unK $ toParam @x @ty x
+class ToNullityParam (x :: Type) (ty :: NullityType) where
+  toNullityParam :: x -> K (Maybe Encoding.Encoding) ty
+instance ToParam x pg => ToNullityParam x ('NotNull pg) where
+  toNullityParam = K . Just . unK . toParam @x @pg
+instance ToParam x pg => ToNullityParam (Maybe x) ('Null pg) where
+  toNullityParam = K . fmap (unK . toParam @x @pg)
 
--- | A `ToColumnParam` constraint lifts the `ToParam` encoding
--- of a `Type` to a `NullityType`, encoding `Maybe`s to `Null`s. You should
--- not define instances of `ToColumnParam`, just use the provided instances.
-class ToColumnParam (x :: Type) (ty :: NullityType) where
-  -- | >>> toColumnParam @Int16 @('NotNull 'PGint2) 0
-  -- K (Just "\NUL\NUL")
-  --
-  -- >>> toColumnParam @(Maybe Int16) @('Null 'PGint2) (Just 0)
-  -- K (Just "\NUL\NUL")
-  --
-  -- >>> toColumnParam @(Maybe Int16) @('Null 'PGint2) Nothing
-  -- K Nothing
-  toColumnParam :: x -> K (Maybe Strict.ByteString) ty
-instance ToParam x pg => ToColumnParam x ('NotNull pg) where
-  toColumnParam = K . Just . Encoding.encodingBytes . unK . toParam @x @pg
-instance ToParam x pg => ToColumnParam (Maybe x) ('Null pg) where
-  toColumnParam = K . fmap (Encoding.encodingBytes . unK . toParam @x @pg)
+class ToField (x :: (Symbol, Type)) (field :: (Symbol, NullityType)) where
+  toField :: P x -> K (Maybe Encoding.Encoding) field
+instance ToNullityParam x ty => ToField (alias ::: x) (alias ::: ty) where
+  toField (P x) = K . unK $ toNullityParam @x @ty x
 
+class ToArray (x :: Type) (array :: NullityType) where
+  toArray :: x -> K Encoding.Array array
+  baseOid :: Word32
+  default baseOid :: HasOid (PGTypeOf array) => Word32
+  baseOid = oid @(PGTypeOf array)
+instance {-# OVERLAPPABLE #-} (HasOid pg, ToParam x pg)
+  => ToArray x ('NotNull pg) where
+    toArray = K . Encoding.encodingArray . unK . toParam @x @pg
+instance {-# OVERLAPPABLE #-} (HasOid pg, ToParam x pg)
+  => ToArray (Maybe x) ('Null pg) where
+    toArray = K . maybe Encoding.nullArray
+      (Encoding.encodingArray . unK . toParam @x @pg)
+instance {-# OVERLAPPING #-} ToArray x array
+  => ToArray (Vector x) ('NotNull ('PGvararray array)) where
+    toArray = K . Encoding.dimensionArray Vector.foldl'
+      (unK . toArray @x @array)
+    baseOid = baseOid @x @array
+instance {-# OVERLAPPING #-} ToArray x array
+  => ToArray (Maybe (Vector x)) ('Null ('PGvararray array)) where
+    toArray = K . maybe Encoding.nullArray
+      (Encoding.dimensionArray Vector.foldl' (unK . toArray @x @array))
+    baseOid = baseOid @x @array
+instance {-# OVERLAPPING #-}
+  ( IsProductType product xs
+  , Length xs ~ n
+  , All ((~) x) xs
+  , ToArray x array )
+  => ToArray product ('NotNull ('PGfixarray n array)) where
+    toArray = K . Encoding.dimensionArray foldlN
+      (unK . toArray @x @array) . unZ . unSOP . from
+    baseOid = baseOid @x @array
+instance {-# OVERLAPPING #-}
+  ( IsProductType product xs
+  , Length xs ~ n
+  , All ((~) x) xs
+  , ToArray x array )
+  => ToArray (Maybe product) ('Null ('PGfixarray n array)) where
+    toArray = K . maybe Encoding.nullArray
+      (Encoding.dimensionArray foldlN (unK . toArray @x @array) . unZ . unSOP . from)
+    baseOid = baseOid @x @array
+
 -- | A `ToParams` constraint generically sequences the encodings of `Type`s
 -- of the fields of a tuple or record to a row of `ColumnType`s. You should
 -- not define instances of `ToParams`. Instead define `Generic` instances
@@ -345,61 +443,67 @@
   -- >>> instance Generic Tuple
   -- >>> toParams @Tuple @Params (Tuple False (Just 0))
   -- K (Just "\NUL") :* K (Just "\NUL\NUL") :* Nil
-  toParams :: x -> NP (K (Maybe Strict.ByteString)) tys
-instance (SListI tys, IsProductType x xs, AllZip ToColumnParam xs tys)
+  toParams :: x -> NP (K (Maybe Encoding.Encoding)) tys
+instance (SListI tys, IsProductType x xs, AllZip ToNullityParam xs tys)
   => ToParams x tys where
       toParams
-        = htrans (Proxy @ToColumnParam) (toColumnParam . unI)
+        = htrans (Proxy @ToNullityParam) (toNullityParam . unI)
         . unZ . unSOP . from
 
 -- | A `FromValue` constraint gives a parser from the binary format of
 -- a PostgreSQL `PGType` into a Haskell `Type`.
 class FromValue (pg :: PGType) (y :: Type) where
   -- | >>> newtype Id = Id { getId :: Int16 } deriving Show
-  -- >>> instance FromValue 'PGint2 Id where fromValue = fmap Id . fromValue
-  fromValue :: proxy pg -> Decoding.Value y
-instance FromValue 'PGbool Bool where fromValue _ = Decoding.bool
-instance FromValue 'PGint2 Int16 where fromValue _ = Decoding.int
-instance FromValue 'PGint4 Int32 where fromValue _ = Decoding.int
-instance FromValue 'PGint8 Int64 where fromValue _ = Decoding.int
-instance FromValue 'PGfloat4 Float where fromValue _ = Decoding.float4
-instance FromValue 'PGfloat8 Double where fromValue _ = Decoding.float8
-instance FromValue 'PGnumeric Scientific where fromValue _ = Decoding.numeric
-instance FromValue 'PGuuid UUID where fromValue _ = Decoding.uuid
-instance FromValue 'PGinet (NetAddr IP) where fromValue _ = Decoding.inet
-instance FromValue ('PGchar 1) Char where fromValue _ = Decoding.char
-instance FromValue 'PGtext Strict.Text where fromValue _ = Decoding.text_strict
-instance FromValue 'PGtext Lazy.Text where fromValue _ = Decoding.text_lazy
+  -- >>> instance FromValue 'PGint2 Id where fromValue = Id <$> fromValue @'PGint2
+  fromValue :: Decoding.Value y
+instance FromValue 'PGbool Bool where fromValue = Decoding.bool
+instance FromValue 'PGint2 Int16 where fromValue = Decoding.int
+instance FromValue 'PGint4 Int32 where fromValue = Decoding.int
+instance FromValue 'PGint8 Int64 where fromValue = Decoding.int
+instance FromValue 'PGfloat4 Float where fromValue = Decoding.float4
+instance FromValue 'PGfloat8 Double where fromValue = Decoding.float8
+instance FromValue 'PGnumeric Scientific where fromValue = Decoding.numeric
+instance FromValue 'PGuuid UUID where fromValue = Decoding.uuid
+instance FromValue 'PGinet (NetAddr IP) where fromValue = Decoding.inet
+instance FromValue ('PGchar 1) Char where fromValue = Decoding.char
+instance FromValue 'PGtext Strict.Text where fromValue = Decoding.text_strict
+instance FromValue 'PGtext Lazy.Text where fromValue = Decoding.text_lazy
+instance FromValue 'PGtext String where
+  fromValue = Strict.Text.unpack <$> Decoding.text_strict
 instance FromValue 'PGbytea Strict.ByteString where
-  fromValue _ = Decoding.bytea_strict
+  fromValue = Decoding.bytea_strict
 instance FromValue 'PGbytea Lazy.ByteString where
-  fromValue _ = Decoding.bytea_lazy
-instance FromValue 'PGdate Day where fromValue _ = Decoding.date
-instance FromValue 'PGtime TimeOfDay where fromValue _ = Decoding.time_int
+  fromValue = Decoding.bytea_lazy
+instance FromValue 'PGdate Day where fromValue = Decoding.date
+instance FromValue 'PGtime TimeOfDay where fromValue = Decoding.time_int
 instance FromValue 'PGtimetz (TimeOfDay, TimeZone) where
-  fromValue _ = Decoding.timetz_int
+  fromValue = Decoding.timetz_int
 instance FromValue 'PGtimestamp LocalTime where
-  fromValue _ = Decoding.timestamp_int
+  fromValue = Decoding.timestamp_int
 instance FromValue 'PGtimestamptz UTCTime where
-  fromValue _ = Decoding.timestamptz_int
+  fromValue = Decoding.timestamptz_int
 instance FromValue 'PGinterval DiffTime where
-  fromValue _ = Decoding.interval_int
-instance FromValue 'PGjson Value where fromValue _ = Decoding.json_ast
-instance FromValue 'PGjsonb Value where fromValue _ = Decoding.jsonb_ast
-instance FromValue pg y => FromValue ('PGvararray pg) (Vector (Maybe y)) where
-  fromValue _ = Decoding.array
-    (Decoding.dimensionArray Vector.replicateM
-      (Decoding.nullableValueArray (fromValue (Proxy @pg))))
-instance FromValue pg y => FromValue ('PGfixarray n pg) (Vector (Maybe y)) where
-  fromValue _ = Decoding.array
-    (Decoding.dimensionArray Vector.replicateM
-      (Decoding.nullableValueArray (fromValue (Proxy @pg))))
+  fromValue = Decoding.interval_int
+instance FromValue 'PGjson Aeson.Value where fromValue = Decoding.json_ast
+instance FromValue 'PGjsonb Aeson.Value where fromValue = Decoding.jsonb_ast
+instance Aeson.FromJSON x => FromValue 'PGjson (Json x) where
+  fromValue = Json <$>
+    Decoding.json_bytes (left Strict.Text.pack . Aeson.eitherDecodeStrict)
+instance Aeson.FromJSON x => FromValue 'PGjsonb (Jsonb x) where
+  fromValue = Jsonb <$>
+    Decoding.jsonb_bytes (left Strict.Text.pack . Aeson.eitherDecodeStrict)
+instance FromArray ('NotNull ('PGvararray ty)) y
+  => FromValue ('PGvararray ty) y where
+    fromValue = Decoding.array (fromArray @('NotNull ('PGvararray ty)) @y)
+instance FromArray ('NotNull ('PGfixarray n ty)) y
+  => FromValue ('PGfixarray n ty) y where
+    fromValue = Decoding.array (fromArray @('NotNull ('PGfixarray n ty)) @y)
 instance
   ( IsEnumType y
   , HasDatatypeInfo y
-  , LabelsFrom y ~ labels
-  ) => FromValue ('PGenum labels) y where
-    fromValue _ =
+  , LabelsPG y ~ labels
+  ) => FromValue ('PGenum labels) (Enumerated y) where
+    fromValue =
       let
         greadConstructor
           :: All ((~) '[]) xss
@@ -412,29 +516,16 @@
             then Just (SOP (Z Nil))
             else SOP . S . unSOP <$> greadConstructor constructors name
       in
-        Decoding.enum
+        fmap Enumerated
+        . Decoding.enum
         $ fmap to
         . greadConstructor (constructorInfo (datatypeInfo (Proxy @y)))
-        . Strict.unpack
-
+        . Strict.Text.unpack
 instance
-  ( SListI fields
-  , MapMaybes ys
-  , IsProductType y (Maybes ys)
-  , AllZip FromAliasedValue fields ys
-  , FieldNamesFrom y ~ AliasesOf fields
-  ) => FromValue ('PGcomposite fields) y where
+  ( FromRow fields y
+  ) => FromValue ('PGcomposite fields) (Composite y) where
     fromValue =
       let
-        decoders
-          :: forall pgs zs proxy
-          . AllZip FromAliasedValue pgs zs
-          => proxy ('PGcomposite pgs)
-          -> NP Decoding.Value zs
-        decoders _ = htrans (Proxy @FromAliasedValue) fromAliasedValue
-          (hpure Proxy :: NP Proxy pgs)
-
-        composite fields = do
         -- <number of fields: 4 bytes>
         -- [for each field]
         --  <OID of field's type: sizeof(Oid) bytes>
@@ -445,80 +536,103 @@
         --    <value: <length> bytes>
         --  [end if]
         -- [end for]
+        composite = Decoding.valueParser $ do
           unitOfSize 4
-          let
-            each field = do
-              unitOfSize 4
-              len <- sized 4 Decoding.int
-              if len == -1 then return Nothing else Just <$> sized len field
-          htraverse' each fields
-
-      in fmap (to . SOP . Z . maybes) . composite . decoders
-
-class FromAliasedValue (pg :: (Symbol,PGType)) (y :: Type) where
-  fromAliasedValue :: proxy pg -> Decoding.Value y
-instance FromValue pg y => FromAliasedValue (alias ::: pg) y where
-  fromAliasedValue _ = fromValue (Proxy @pg)
+          hsequence' $ hpure $ Comp $ do
+            unitOfSize 4
+            len <- sized 4 Decoding.int
+            if len == -1
+              then return (K Nothing)
+              else K . Just <$> bytesOfSize len
+      in
+        fmap Composite (Decoding.fn (fromRow @fields <=< composite))
 
--- | A `FromColumnValue` constraint lifts the `FromValue` parser
+-- | A `FromField` constraint lifts the `FromValue` parser
 -- to a decoding of a @(Symbol, NullityType)@ to a `Type`,
 -- decoding `Null`s to `Maybe`s. You should not define instances for
--- `FromColumnValue`, just use the provided instances.
-class FromColumnValue (colty :: (Symbol,NullityType)) (y :: Type) where
-  -- | >>> :set -XTypeOperators -XOverloadedStrings
-  -- >>> newtype Id = Id { getId :: Int16 } deriving Show
-  -- >>> instance FromValue 'PGint2 Id where fromValue = fmap Id . fromValue
-  -- >>> fromColumnValue @("col" ::: 'NotNull 'PGint2) @Id (K (Just "\NUL\SOH"))
-  -- Id {getId = 1}
-  --
-  -- >>> fromColumnValue @("col" ::: 'Null 'PGint2) @(Maybe Id) (K (Just "\NUL\SOH"))
-  -- Just (Id {getId = 1})
-  fromColumnValue :: K (Maybe Strict.ByteString) colty -> y
+-- `FromField`, just use the provided instances.
+class FromField (pg :: (Symbol, NullityType)) (y :: (Symbol, Type)) where
+  fromField
+    :: K (Maybe Strict.ByteString) pg
+    -> (Either Strict.Text :.: P) y
 instance FromValue pg y
-  => FromColumnValue (column ::: ('NotNull pg)) y where
-    fromColumnValue = \case
-      K Nothing -> error "fromColumnValue: saw NULL when expecting NOT NULL"
-      K (Just bs) ->
-        let
-          errOrValue =
-            Decoding.valueParser (fromValue @pg @y Proxy) bs
-          err str = error $ "fromColumnValue: " ++ Strict.unpack str
-        in
-          either err id errOrValue
+  => FromField (column ::: ('NotNull pg)) (column ::: y) where
+    fromField = Comp . \case
+      K Nothing -> Left "fromField: saw NULL when expecting NOT NULL"
+      K (Just bytestring) -> P <$>
+        Decoding.valueParser (fromValue @pg) bytestring
 instance FromValue pg y
-  => FromColumnValue (column ::: ('Null pg)) (Maybe y) where
-    fromColumnValue (K nullOrBytes)
-      = either err id
-      . Decoding.valueParser (fromValue @pg @y Proxy)
-      <$> nullOrBytes
-      where
-        err str = error $ "fromColumnValue: " ++ Strict.unpack str
+  => FromField (column ::: 'Null pg) (column ::: Maybe y) where
+    fromField = Comp . \case
+      K Nothing -> Right $ P Nothing
+      K (Just bytestring) -> P . Just <$>
+        Decoding.valueParser (fromValue @pg) bytestring
 
+class FromArray (ty :: NullityType) (y :: Type) where
+  fromArray :: Decoding.Array y
+instance {-# OVERLAPPABLE #-} FromValue pg y
+  => FromArray ('NotNull pg) y where
+    fromArray = Decoding.valueArray (fromValue @pg @y)
+instance {-# OVERLAPPABLE #-} FromValue pg y
+  => FromArray ('Null pg) (Maybe y) where
+    fromArray = Decoding.nullableValueArray (fromValue @pg @y)
+instance {-# OVERLAPPING #-} FromArray array y
+  => FromArray ('NotNull ('PGvararray array)) (Vector y) where
+    fromArray =
+      Decoding.dimensionArray Vector.replicateM (fromArray @array @y)
+instance {-# OVERLAPPING #-} FromArray array y
+  => FromArray ('Null ('PGvararray array)) (Maybe (Vector y)) where
+    fromArray = Just <$> 
+      Decoding.dimensionArray Vector.replicateM (fromArray @array @y)
+instance {-# OVERLAPPING #-}
+  ( FromArray array y
+  , All ((~) y) ys
+  , SListI ys
+  , IsProductType product ys )
+  => FromArray ('NotNull ('PGfixarray n array)) product where
+    fromArray =
+      let
+        rep _ = fmap (to . SOP . Z) . replicateMN
+      in
+        Decoding.dimensionArray rep (fromArray @array @y)
+instance {-# OVERLAPPING #-}
+  ( FromArray array y
+  , All ((~) y) ys
+  , SListI ys
+  , IsProductType product ys )
+  => FromArray ('Null ('PGfixarray n array)) (Maybe product) where
+    fromArray =
+      let
+        rep _ = fmap (to . SOP . Z) . replicateMN
+      in
+        Just <$> Decoding.dimensionArray rep (fromArray @array @y)
+
 -- | A `FromRow` constraint generically sequences the parsings of the columns
--- of a `RelationType` into the fields of a record `Type` provided they have
+-- of a `RowType` into the fields of a record `Type` provided they have
 -- the same field names. You should not define instances of `FromRow`.
 -- Instead define `Generic` and `HasDatatypeInfo` instances which in turn
 -- provide `FromRow` instances.
-class SListI results => FromRow (results :: RelationType) y where
+class SListI result => FromRow (result :: RowType) y where
   -- | >>> :set -XOverloadedStrings
   -- >>> import Data.Text
   -- >>> newtype UserId = UserId { getUserId :: Int16 } deriving Show
-  -- >>> instance FromValue 'PGint2 UserId where fromValue = fmap UserId . fromValue
+  -- >>> instance FromValue 'PGint2 UserId where fromValue = UserId <$> fromValue @'PGint2
   -- >>> data UserRow = UserRow { userId :: UserId, userName :: Maybe Text } deriving (Show, GHC.Generic)
   -- >>> instance Generic UserRow
   -- >>> instance HasDatatypeInfo UserRow
   -- >>> type User = '["userId" ::: 'NotNull 'PGint2, "userName" ::: 'Null 'PGtext]
   -- >>> fromRow @User @UserRow (K (Just "\NUL\SOH") :* K (Just "bloodninja") :* Nil)
-  -- UserRow {userId = UserId {getUserId = 1}, userName = Just "bloodninja"}
-  fromRow :: NP (K (Maybe Strict.ByteString)) results -> y
+  -- Right (UserRow {userId = UserId {getUserId = 1}, userName = Just "bloodninja"})
+  fromRow :: NP (K (Maybe Strict.ByteString)) result -> Either Strict.Text y
 instance
-  ( SListI results
-  , IsProductType y ys
-  , AllZip FromColumnValue results ys
-  , FieldNamesFrom y ~ AliasesOf results
-  ) => FromRow results y where
+  ( SListI result
+  , IsRecord y ys
+  , AllZip FromField result ys
+  ) => FromRow result y where
     fromRow
-      = to . SOP . Z . htrans (Proxy @FromColumnValue) (I . fromColumnValue)
+      = fmap fromRecord
+      . hsequence'
+      . htrans (Proxy @FromField) fromField
 
 -- | `Only` is a 1-tuple type, useful for encoding a single parameter with
 -- `toParams` or decoding a single value with `fromRow`.
@@ -528,8 +642,21 @@
 -- K (Just "foo") :* Nil
 --
 -- >>> fromRow @'["fromOnly" ::: 'Null 'PGtext] @(Only (Maybe Text)) (K (Just "bar") :* Nil)
--- Only {fromOnly = Just "bar"}
+-- Right (Only {fromOnly = Just "bar"})
 newtype Only x = Only { fromOnly :: x }
   deriving (Functor,Foldable,Traversable,Eq,Ord,Read,Show,GHC.Generic)
 instance Generic (Only x)
 instance HasDatatypeInfo (Only x)
+
+foldlN
+  :: All ((~) x) xs
+  => (z -> x -> z) -> z -> NP I xs -> z
+foldlN f z = \case
+  Nil -> z
+  I x :* xs -> let z' = f z x in seq z' $ foldlN f z' xs
+
+replicateMN
+  :: forall x xs m. (All ((~) x) xs, Monad m, SListI xs)
+  => m x -> m (NP I xs)
+replicateMN mx = hsequence' $
+  hcpure (Proxy :: Proxy ((~) x)) (Comp (I <$> mx)) 
diff --git a/src/Squeal/PostgreSQL/Definition.hs b/src/Squeal/PostgreSQL/Definition.hs
--- a/src/Squeal/PostgreSQL/Definition.hs
+++ b/src/Squeal/PostgreSQL/Definition.hs
@@ -270,7 +270,7 @@
 -}
 check
   :: ( Has alias schema ('Table table)
-     , HasAll aliases (TableToRelation table) subcolumns )
+     , HasAll aliases (TableToRow table) subcolumns )
   => NP Alias aliases
   -- ^ specify the subcolumns which are getting checked
   -> (forall tab. Condition schema '[tab ::: subcolumns] 'Ungrouped '[])
@@ -304,7 +304,7 @@
 -}
 unique
   :: ( Has alias schema ('Table table)
-     , HasAll aliases (TableToRelation table) subcolumns )
+     , HasAll aliases (TableToRow table) subcolumns )
   => NP Alias aliases
   -- ^ specify subcolumns which together are unique for each row
   -> TableConstraintExpression schema alias ('Unique aliases)
@@ -807,9 +807,9 @@
 createView
   :: KnownSymbol view
   => Alias view -- ^ the name of the view to add
-  -> Query schema '[] relation
+  -> Query schema '[] row
     -- ^ query
-  -> Definition schema (Create view ('View relation) schema)
+  -> Definition schema (Create view ('View row) schema)
 createView alias query = UnsafeDefinition $
   "CREATE" <+> "VIEW" <+> renderAlias alias <+> "AS"
   <+> renderQuery query <> ";"
@@ -857,19 +857,23 @@
 createTypeEnumFrom
   :: forall hask enum schema.
   ( SOP.Generic hask
-  , SOP.All KnownSymbol (LabelsFrom hask)
+  , SOP.All KnownSymbol (LabelsPG hask)
   , KnownSymbol enum
   )
   => Alias enum
   -- ^ name of the user defined enumerated type
-  -> Definition schema (Create enum ('Typedef (EnumFrom hask)) schema)
+  -> Definition schema (Create enum ('Typedef (PG (Enumerated hask))) schema)
 createTypeEnumFrom enum = createTypeEnum enum
-  (SOP.hpure label :: NP PGlabel (LabelsFrom hask))
+  (SOP.hpure label :: NP PGlabel (LabelsPG hask))
 
 {- | `createTypeComposite` creates a composite type. The composite type is
 specified by a list of attribute names and data types.
 
->>> type PGcomplex = 'PGcomposite '["real" ::: 'PGfloat8, "imaginary" ::: 'PGfloat8]
+>>> :{
+type PGcomplex = 'PGcomposite
+  '[ "real"      ::: 'NotNull 'PGfloat8
+   , "imaginary" ::: 'NotNull 'PGfloat8 ]
+:}
 
 >>> :{
 let
@@ -892,36 +896,39 @@
   (renderCommaSeparated renderField fields) <> ";"
   where
     renderField :: Aliased (TypeExpression schema) x -> ByteString
-    renderField (typ `As` field) =
-      renderAlias field <+> renderTypeExpression typ
+    renderField (typ `As` alias) =
+      renderAlias alias <+> renderTypeExpression typ
 
 -- | Composite types can also be generated from a Haskell type, for example
 --
--- >>> data Complex = Complex {real :: Maybe Double, imaginary :: Maybe Double} deriving GHC.Generic
+-- >>> data Complex = Complex {real :: Double, imaginary :: Double} deriving GHC.Generic
 -- >>> instance SOP.Generic Complex
 -- >>> instance SOP.HasDatatypeInfo Complex
 -- >>> printSQL $ createTypeCompositeFrom @Complex #complex
 -- CREATE TYPE "complex" AS ("real" float8, "imaginary" float8);
 createTypeCompositeFrom
   :: forall hask ty schema.
-  ( ZipAliased (FieldNamesFrom hask) (FieldTypesFrom hask)
-  , SOP.All (PGTyped schema) (FieldTypesFrom hask)
-  , KnownSymbol ty
-  )
+  ( SOP.All (FieldTyped schema) (RowPG hask)
+  , KnownSymbol ty )
   => Alias ty
   -- ^ name of the user defined composite type
-  -> Definition schema (Create ty ( 'Typedef (CompositeFrom hask)) schema)
-createTypeCompositeFrom ty = createTypeComposite ty $ zipAs
-  (SOP.hpure Alias :: NP Alias (FieldNamesFrom hask))
-  (SOP.hcpure (SOP.Proxy :: SOP.Proxy (PGTyped schema)) pgtype
-    :: NP (TypeExpression schema) (FieldTypesFrom hask))
+  -> Definition schema (Create ty ( 'Typedef (PG (Composite hask))) schema)
+createTypeCompositeFrom ty = createTypeComposite ty
+  (SOP.hcpure (SOP.Proxy :: SOP.Proxy (FieldTyped schema)) fieldtype
+    :: NP (Aliased (TypeExpression schema)) (RowPG hask))
 
+class FieldTyped schema ty where
+  fieldtype :: Aliased (TypeExpression schema) ty
+instance (KnownSymbol alias, PGTyped schema ty)
+  => FieldTyped schema (alias ::: ty) where
+    fieldtype = pgtype `As` Alias
+
 -- | Drop a type.
 --
 -- >>> data Schwarma = Beef | Lamb | Chicken deriving GHC.Generic
 -- >>> instance SOP.Generic Schwarma
 -- >>> instance SOP.HasDatatypeInfo Schwarma
--- >>> printSQL (dropType #schwarma :: Definition '["schwarma" ::: 'Typedef (EnumFrom Schwarma)] '[])
+-- >>> printSQL (dropType #schwarma :: Definition '["schwarma" ::: 'Typedef (PG (Enumerated Schwarma))] '[])
 -- DROP TYPE "schwarma";
 dropType
   :: Has tydef schema ('Typedef ty)
@@ -938,15 +945,15 @@
 -- | used in `createTable` commands as a column constraint to note that
 -- @NULL@ may be present in a column
 nullable
-  :: TypeExpression schema ty
+  :: TypeExpression schema (nullity ty)
   -> ColumnTypeExpression schema ('NoDef :=> 'Null ty)
 nullable ty = UnsafeColumnTypeExpression $ renderTypeExpression ty <+> "NULL"
 
 -- | used in `createTable` commands as a column constraint to ensure
 -- @NULL@ is not present in a column
 notNullable
-  :: TypeExpression schema ty
-  -> ColumnTypeExpression schema (def :=> 'NotNull ty)
+  :: TypeExpression schema (nullity ty)
+  -> ColumnTypeExpression schema ('NoDef :=> 'NotNull ty)
 notNullable ty = UnsafeColumnTypeExpression $ renderTypeExpression ty <+> "NOT NULL"
 
 -- | used in `createTable` commands as a column constraint to give a default
diff --git a/src/Squeal/PostgreSQL/Expression.hs b/src/Squeal/PostgreSQL/Expression.hs
--- a/src/Squeal/PostgreSQL/Expression.hs
+++ b/src/Squeal/PostgreSQL/Expression.hs
@@ -45,7 +45,9 @@
   , nullIf
     -- ** Collections
   , array
+  , index
   , row
+  , field
     -- ** Functions
   , unsafeBinaryOp
   , unsafeUnaryOp
@@ -61,12 +63,12 @@
   , greatest
   , least
     -- ** Conditions
-  , Condition
   , true
   , false
   , not_
   , (.&&)
   , (.||)
+  , Condition
   , caseWhenThenElse
   , ifThenElse
   , (.==)
@@ -86,16 +88,13 @@
   , upper
   , charLength
   , like
-    -- ** json or jsonb operators
-  , PGarray
-  , PGarrayOf
-  , PGjsonKey
-  , PGjson_
+    -- ** Json
+    -- *** Json and Jsonb operators
   , (.->)
   , (.->>)
   , (.#>)
   , (.#>>)
-    -- *** jsonb only operators
+    -- *** Jsonb operators
   , (.@>)
   , (.<@)
   , (.?)
@@ -124,8 +123,6 @@
   , jsonbExtractPath
   , jsonExtractPathAsText
   , jsonbExtractPathAsText
-  , jsonObjectKeys
-  , jsonbObjectKeys
   , jsonTypeof
   , jsonbTypeof
   , jsonStripNulls
@@ -146,6 +143,9 @@
     -- * Types
   , TypeExpression (UnsafeTypeExpression, renderTypeExpression)
   , PGTyped (pgtype)
+  , typedef
+  , typetable
+  , typeview
   , bool
   , int2
   , smallint
@@ -191,6 +191,7 @@
 import qualified Data.Aeson as JSON
 import Data.Ratio
 import Data.String
+import Data.Word
 import Generics.SOP hiding (from)
 import GHC.OverloadedLabels
 import GHC.TypeLits
@@ -217,14 +218,14 @@
 -}
 newtype Expression
   (schema :: SchemaType)
-  (relations :: RelationsType)
+  (from :: FromType)
   (grouping :: Grouping)
   (params :: [NullityType])
   (ty :: NullityType)
     = UnsafeExpression { renderExpression :: ByteString }
     deriving (GHC.Generic,Show,Eq,Ord,NFData)
 
-instance RenderSQL (Expression schema relations grouping params ty) where
+instance RenderSQL (Expression schema from grouping params ty) where
   renderSQL = renderExpression
 
 {- | A `HasParameter` constraint is used to indicate a value that is
@@ -247,10 +248,10 @@
     -- >>> printSQL expr
     -- ($1 :: int4)
     parameter
-      :: TypeExpression schema (PGTypeOf ty)
-      -> Expression schema relations grouping params ty
+      :: TypeExpression schema ty
+      -> Expression schema from grouping params ty
     parameter ty = UnsafeExpression $ parenthesized $
-      "$" <> renderNat (Proxy @n) <+> "::"
+      "$" <> renderNat @n <+> "::"
         <+> renderTypeExpression ty
 instance {-# OVERLAPPING #-} HasParameter 1 schema (ty1:tys) ty1
 instance {-# OVERLAPPABLE #-} (KnownNat n, HasParameter (n-1) schema params ty)
@@ -263,84 +264,84 @@
 -- >>> printSQL expr
 -- ($1 :: int4)
 param
-  :: forall n schema params relations grouping ty
-   . (PGTyped schema (PGTypeOf ty), HasParameter n schema params ty)
-  => Expression schema relations grouping params ty -- ^ param
+  :: forall n schema params from grouping ty
+   . (PGTyped schema ty, HasParameter n schema params ty)
+  => Expression schema from grouping params ty -- ^ param
 param = parameter @n pgtype
 
-instance (HasUnique relation relations columns, Has column columns ty)
-  => IsLabel column (Expression schema relations 'Ungrouped params ty) where
+instance (HasUnique table from columns, Has column columns ty)
+  => IsLabel column (Expression schema from 'Ungrouped params ty) where
     fromLabel = UnsafeExpression $ renderAlias (Alias @column)
-instance (HasUnique relation relations columns, Has column columns ty)
+instance (HasUnique table from columns, Has column columns ty)
   => IsLabel column
-    (Aliased (Expression schema relations 'Ungrouped params) (column ::: ty)) where
+    (Aliased (Expression schema from 'Ungrouped params) (column ::: ty)) where
     fromLabel = fromLabel @column `As` Alias @column
-instance (HasUnique relation relations columns, Has column columns ty)
+instance (HasUnique table from columns, Has column columns ty)
   => IsLabel column
-    (NP (Aliased (Expression schema relations 'Ungrouped params)) '[column ::: ty]) where
+    (NP (Aliased (Expression schema from 'Ungrouped params)) '[column ::: ty]) where
     fromLabel = fromLabel @column :* Nil
 
-instance (Has relation relations columns, Has column columns ty)
-  => IsQualified relation column (Expression schema relations 'Ungrouped params ty) where
-    relation ! column = UnsafeExpression $
-      renderAlias relation <> "." <> renderAlias column
-instance (Has relation relations columns, Has column columns ty)
-  => IsQualified relation column
-    (Aliased (Expression schema relations 'Ungrouped params) (column ::: ty)) where
-    relation ! column = relation ! column `As` column
-instance (Has relation relations columns, Has column columns ty)
-  => IsQualified relation column
-    (NP (Aliased (Expression schema relations 'Ungrouped params)) '[column ::: ty]) where
-    relation ! column = relation ! column :* Nil
+instance (Has table from columns, Has column columns ty)
+  => IsQualified table column (Expression schema from 'Ungrouped params ty) where
+    table ! column = UnsafeExpression $
+      renderAlias table <> "." <> renderAlias column
+instance (Has table from columns, Has column columns ty)
+  => IsQualified table column
+    (Aliased (Expression schema from 'Ungrouped params) (column ::: ty)) where
+    table ! column = table ! column `As` column
+instance (Has table from columns, Has column columns ty)
+  => IsQualified table column
+    (NP (Aliased (Expression schema from 'Ungrouped params)) '[column ::: ty]) where
+    table ! column = table ! column :* Nil
 
 instance
-  ( HasUnique relation relations columns
+  ( HasUnique table from columns
   , Has column columns ty
-  , GroupedBy relation column bys
+  , GroupedBy table column bys
   ) => IsLabel column
-    (Expression schema relations ('Grouped bys) params ty) where
+    (Expression schema from ('Grouped bys) params ty) where
       fromLabel = UnsafeExpression $ renderAlias (Alias @column)
 instance
-  ( HasUnique relation relations columns
+  ( HasUnique table from columns
   , Has column columns ty
-  , GroupedBy relation column bys
+  , GroupedBy table column bys
   ) => IsLabel column
-    ( Aliased (Expression schema relations ('Grouped bys) params)
+    ( Aliased (Expression schema from ('Grouped bys) params)
       (column ::: ty) ) where
       fromLabel = fromLabel @column `As` Alias @column
 instance
-  ( HasUnique relation relations columns
+  ( HasUnique table from columns
   , Has column columns ty
-  , GroupedBy relation column bys
+  , GroupedBy table column bys
   ) => IsLabel column
-    ( NP (Aliased (Expression schema relations ('Grouped bys) params))
+    ( NP (Aliased (Expression schema from ('Grouped bys) params))
       '[column ::: ty] ) where
       fromLabel = fromLabel @column :* Nil
 
 instance
-  ( Has relation relations columns
+  ( Has table from columns
   , Has column columns ty
-  , GroupedBy relation column bys
-  ) => IsQualified relation column
-    (Expression schema relations ('Grouped bys) params ty) where
-      relation ! column = UnsafeExpression $
-        renderAlias relation <> "." <> renderAlias column
+  , GroupedBy table column bys
+  ) => IsQualified table column
+    (Expression schema from ('Grouped bys) params ty) where
+      table ! column = UnsafeExpression $
+        renderAlias table <> "." <> renderAlias column
 instance
-  ( Has relation relations columns
+  ( Has table from columns
   , Has column columns ty
-  , GroupedBy relation column bys
-  ) => IsQualified relation column
-    (Aliased (Expression schema relations ('Grouped bys) params)
+  , GroupedBy table column bys
+  ) => IsQualified table column
+    (Aliased (Expression schema from ('Grouped bys) params)
       (column ::: ty)) where
-        relation ! column = relation ! column `As` column
+        table ! column = table ! column `As` column
 instance
-  ( Has relation relations columns
+  ( Has table from columns
   , Has column columns ty
-  , GroupedBy relation column bys
-  ) => IsQualified relation column
-    ( NP (Aliased (Expression schema relations ('Grouped bys) params))
+  , GroupedBy table column bys
+  ) => IsQualified table column
+    ( NP (Aliased (Expression schema from ('Grouped bys) params))
       '[column ::: ty]) where
-        relation ! column = relation ! column :* Nil
+        table ! column = table ! column :* Nil
 
 -- | analagous to `Nothing`
 --
@@ -360,14 +361,14 @@
 
 -- | return the leftmost value which is not NULL
 --
--- >>> printSQL $ coalesce [null_, notNull true] false
+-- >>> printSQL $ coalesce [null_, true] false
 -- COALESCE(NULL, TRUE, FALSE)
 coalesce
-  :: [Expression schema relations grouping params ('Null ty)]
+  :: [Expression schema from grouping params ('Null ty)]
   -- ^ @NULL@s may be present
-  -> Expression schema relations grouping params ('NotNull ty)
+  -> Expression schema from grouping params ('NotNull ty)
   -- ^ @NULL@ is absent
-  -> Expression schema relations grouping params ('NotNull ty)
+  -> Expression schema from grouping params ('NotNull ty)
 coalesce nullxs notNullx = UnsafeExpression $
   "COALESCE" <> parenthesized (commaSeparated
     ((renderExpression <$> nullxs) <> [renderExpression notNullx]))
@@ -377,26 +378,26 @@
 -- >>> printSQL $ fromNull true null_
 -- COALESCE(NULL, TRUE)
 fromNull
-  :: Expression schema relations grouping params ('NotNull ty)
+  :: Expression schema from grouping params ('NotNull ty)
   -- ^ what to convert @NULL@ to
-  -> Expression schema relations grouping params ('Null ty)
-  -> Expression schema relations grouping params ('NotNull ty)
+  -> Expression schema from grouping params ('Null ty)
+  -> Expression schema from grouping params ('NotNull ty)
 fromNull notNullx nullx = coalesce [nullx] notNullx
 
 -- | >>> printSQL $ null_ & isNull
 -- NULL IS NULL
 isNull
-  :: Expression schema relations grouping params ('Null ty)
+  :: Expression schema from grouping params ('Null ty)
   -- ^ possibly @NULL@
-  -> Condition schema relations grouping params
+  -> Condition schema from grouping params
 isNull x = UnsafeExpression $ renderExpression x <+> "IS NULL"
 
 -- | >>> printSQL $ null_ & isNotNull
 -- NULL IS NOT NULL
 isNotNull
-  :: Expression schema relations grouping params ('Null ty)
+  :: Expression schema from grouping params ('Null ty)
   -- ^ possibly @NULL@
-  -> Condition schema relations grouping params
+  -> Condition schema from grouping params
 isNotNull x = UnsafeExpression $ renderExpression x <+> "IS NOT NULL"
 
 -- | analagous to `maybe` using @IS NULL@
@@ -404,13 +405,13 @@
 -- >>> printSQL $ matchNull true not_ null_
 -- CASE WHEN NULL IS NULL THEN TRUE ELSE (NOT NULL) END
 matchNull
-  :: Expression schema relations grouping params (nullty)
+  :: Expression schema from grouping params (nullty)
   -- ^ what to convert @NULL@ to
-  -> ( Expression schema relations grouping params ('NotNull ty)
-       -> Expression schema relations grouping params (nullty) )
+  -> ( Expression schema from grouping params ('NotNull ty)
+       -> Expression schema from grouping params (nullty) )
   -- ^ function to perform when @NULL@ is absent
-  -> Expression schema relations grouping params ('Null ty)
-  -> Expression schema relations grouping params (nullty)
+  -> Expression schema from grouping params ('Null ty)
+  -> Expression schema from grouping params (nullty)
 matchNull y f x = ifThenElse (isNull x) y
   (f (UnsafeExpression (renderExpression x)))
 
@@ -423,55 +424,81 @@
 NULL IF (FALSE, ($1 :: bool))
 -}
 nullIf
-  :: Expression schema relations grouping params ('NotNull ty)
+  :: Expression schema from grouping params ('NotNull ty)
   -- ^ @NULL@ is absent
-  -> Expression schema relations grouping params ('NotNull ty)
+  -> Expression schema from grouping params ('NotNull ty)
   -- ^ @NULL@ is absent
-  -> Expression schema relations grouping params ('Null ty)
+  -> Expression schema from grouping params ('Null ty)
 nullIf x y = UnsafeExpression $ "NULL IF" <+> parenthesized
   (renderExpression x <> ", " <> renderExpression y)
 
--- | >>> printSQL $ array [null_, notNull false, notNull true]
+-- | >>> printSQL $ array [null_, false, true]
 -- ARRAY[NULL, FALSE, TRUE]
 array
-  :: [Expression schema relations grouping params ('Null ty)]
+  :: [Expression schema from grouping params ty]
   -- ^ array elements
-  -> Expression schema relations grouping params (nullity ('PGvararray ty))
+  -> Expression schema from grouping params (nullity ('PGvararray ty))
 array xs = UnsafeExpression $
   "ARRAY[" <> commaSeparated (renderExpression <$> xs) <> "]"
 
+-- | >>> printSQL $ array [null_, false, true] & index 2
+-- (ARRAY[NULL, FALSE, TRUE])[2]
+index
+  :: Word64 -- ^ index
+  -> Expression schema from grouping params (nullity ('PGvararray ty)) -- ^ array
+  -> Expression schema from grouping params (NullifyType ty)
+index n expr = UnsafeExpression $
+  parenthesized (renderExpression expr) <> "[" <> fromString (show n) <> "]"
+
 instance (KnownSymbol label, label `In` labels) => IsPGlabel label
-  (Expression schema relations grouping params (nullity ('PGenum labels))) where
+  (Expression schema from grouping params (nullity ('PGenum labels))) where
   label = UnsafeExpression $ renderLabel (PGlabel @label)
 
 -- | A row constructor is an expression that builds a row value
 -- (also called a composite value) using values for its member fields.
 --
--- >>> type Complex = PGcomposite '["real" ::: 'PGfloat8, "imaginary" ::: 'PGfloat8]
+-- >>> :{
+-- type Complex = 'PGcomposite
+--   '[ "real"      ::: 'NotNull 'PGfloat8
+--    , "imaginary" ::: 'NotNull 'PGfloat8 ]
+-- :}
+--
 -- >>> let i = row (0 `as` #real :* 1 `as` #imaginary) :: Expression '[] '[] 'Ungrouped '[] ('NotNull Complex)
 -- >>> printSQL i
 -- ROW(0, 1)
 row
-  :: SListI (Nulls fields)
-  => NP (Aliased (Expression schema relations grouping params)) (Nulls fields)
+  :: SListI row
+  => NP (Aliased (Expression schema from grouping params)) row
   -- ^ zero or more expressions for the row field values
-  -> Expression schema relations grouping params (nullity ('PGcomposite fields))
+  -> Expression schema from grouping params (nullity ('PGcomposite row))
 row exprs = UnsafeExpression $ "ROW" <> parenthesized
   (renderCommaSeparated (\ (expr `As` _) -> renderExpression expr) exprs)
 
-instance Has field fields ty => IsLabel field
-  (   Expression schema relation grouping params (nullity ('PGcomposite fields))
-   -> Expression schema relation grouping params ('Null ty) ) where
-    fromLabel expr = UnsafeExpression $
-      parenthesized (renderExpression expr) <> "." <>
-        fromString (symbolVal (Proxy @field))
+-- | >>> :{
+-- type Complex = 'PGcomposite
+--   '[ "real"      ::: 'NotNull 'PGfloat8
+--    , "imaginary" ::: 'NotNull 'PGfloat8 ]
+-- :}
+--
+-- >>> let i = row (0 `as` #real :* 1 `as` #imaginary) :: Expression '["complex" ::: 'Typedef Complex] '[] 'Ungrouped '[] ('NotNull Complex)
+-- >>> printSQL $ i & field #complex #imaginary
+-- (ROW(0, 1)::"complex")."imaginary"
+field
+  :: (Has tydef schema ('Typedef ('PGcomposite row)), Has field row ty)
+  => Alias tydef -- ^ row type
+  -> Alias field -- ^ field name
+  -> Expression schema from grouping params ('NotNull ('PGcomposite row))
+  -> Expression schema from grouping params ty
+field td fld expr = UnsafeExpression $
+  parenthesized (renderExpression expr <> "::" <> renderAlias td)
+    <> "." <> renderAlias fld
 
 instance Semigroup
-  (Expression schema relations grouping params (nullity ('PGvararray ty))) where
+  (Expression schema from grouping params (nullity ('PGvararray ty))) where
     (<>) = unsafeBinaryOp "||"
 
 instance Monoid
-  (Expression schema relations grouping params (nullity ('PGvararray ty))) where
+  (Expression schema from grouping params (nullity ('PGvararray ty))) where
     mempty = array []
     mappend = (<>)
 
@@ -479,22 +506,22 @@
 -- >>> printSQL expr
 -- GREATEST(CURRENT_TIMESTAMP, ($1 :: timestamp with time zone))
 greatest
-  :: Expression schema relations grouping params (nullty)
+  :: Expression schema from grouping params (nullty)
   -- ^ needs at least 1 argument
-  -> [Expression schema relations grouping params (nullty)]
+  -> [Expression schema from grouping params (nullty)]
   -- ^ or more
-  -> Expression schema relations grouping params (nullty)
+  -> Expression schema from grouping params (nullty)
 greatest x xs = UnsafeExpression $ "GREATEST("
   <> commaSeparated (renderExpression <$> (x:xs)) <> ")"
 
 -- | >>> printSQL $ least currentTimestamp [null_]
 -- LEAST(CURRENT_TIMESTAMP, NULL)
 least
-  :: Expression schema relations grouping params (nullty)
+  :: Expression schema from grouping params (nullty)
   -- ^ needs at least 1 argument
-  -> [Expression schema relations grouping params (nullty)]
+  -> [Expression schema from grouping params (nullty)]
   -- ^ or more
-  -> Expression schema relations grouping params (nullty)
+  -> Expression schema from grouping params (nullty)
 least x xs = UnsafeExpression $ "LEAST("
   <> commaSeparated (renderExpression <$> (x:xs)) <> ")"
 
@@ -503,9 +530,9 @@
 unsafeBinaryOp
   :: ByteString
   -- ^ operator
-  -> Expression schema relations grouping params (ty0)
-  -> Expression schema relations grouping params (ty1)
-  -> Expression schema relations grouping params (ty2)
+  -> Expression schema from grouping params (ty0)
+  -> Expression schema from grouping params (ty1)
+  -> Expression schema from grouping params (ty2)
 unsafeBinaryOp op x y = UnsafeExpression $ parenthesized $
   renderExpression x <+> op <+> renderExpression y
 
@@ -514,8 +541,8 @@
 unsafeUnaryOp
   :: ByteString
   -- ^ operator
-  -> Expression schema relations grouping params (ty0)
-  -> Expression schema relations grouping params (ty1)
+  -> Expression schema from grouping params (ty0)
+  -> Expression schema from grouping params (ty1)
 unsafeUnaryOp op x = UnsafeExpression $ parenthesized $
   op <+> renderExpression x
 
@@ -524,8 +551,8 @@
 unsafeFunction
   :: ByteString
   -- ^ function
-  -> Expression schema relations grouping params (xty)
-  -> Expression schema relations grouping params (yty)
+  -> Expression schema from grouping params (xty)
+  -> Expression schema from grouping params (yty)
 unsafeFunction fun x = UnsafeExpression $
   fun <> parenthesized (renderExpression x)
 
@@ -534,13 +561,13 @@
   :: SListI elems
   => ByteString
   -- ^ function
-  -> NP (Expression schema relations grouping params) elems
-  -> Expression schema relations grouping params ret
+  -> NP (Expression schema from grouping params) elems
+  -> Expression schema from grouping params ret
 unsafeVariadicFunction fun x = UnsafeExpression $
   fun <> parenthesized (commaSeparated (hcollapse (hmap (K . renderExpression) x)))
 
-instance PGNum ty
-  => Num (Expression schema relations grouping params (nullity ty)) where
+instance ty `In` PGNum
+  => Num (Expression schema from grouping params (nullity ty)) where
     (+) = unsafeBinaryOp "+"
     (-) = unsafeBinaryOp "-"
     (*) = unsafeBinaryOp "*"
@@ -551,13 +578,13 @@
       . fromString
       . show
 
-instance (PGNum ty, PGFloating ty) => Fractional
-  (Expression schema relations grouping params (nullity ty)) where
+instance (ty `In` PGNum, ty `In` PGFloating) => Fractional
+  (Expression schema from grouping params (nullity ty)) where
     (/) = unsafeBinaryOp "/"
     fromRational x = fromInteger (numerator x) / fromInteger (denominator x)
 
-instance (PGNum ty, PGFloating ty) => Floating
-  (Expression schema relations grouping params (nullity ty)) where
+instance (ty `In` PGNum, ty `In` PGFloating) => Floating
+  (Expression schema from grouping params (nullity ty)) where
     pi = UnsafeExpression "pi()"
     exp = unsafeFunction "exp"
     log = unsafeFunction "ln"
@@ -580,18 +607,18 @@
 
 -- | >>> :{
 -- let
---   expression :: Expression schema relations grouping params (nullity 'PGfloat4)
+--   expression :: Expression schema from grouping params (nullity 'PGfloat4)
 --   expression = atan2_ pi 2
 -- in printSQL expression
 -- :}
 -- atan2(pi(), 2)
 atan2_
-  :: PGFloating float
-  => Expression schema relations grouping params (nullity float)
+  :: float `In` PGFloating
+  => Expression schema from grouping params (nullity float)
   -- ^ numerator
-  -> Expression schema relations grouping params (nullity float)
+  -> Expression schema from grouping params (nullity float)
   -- ^ denominator
-  -> Expression schema relations grouping params (nullity float)
+  -> Expression schema from grouping params (nullity float)
 atan2_ y x = UnsafeExpression $
   "atan2(" <> renderExpression y <> ", " <> renderExpression x <> ")"
 
@@ -604,9 +631,9 @@
 cast
   :: TypeExpression schema ty1
   -- ^ type to cast as
-  -> Expression schema relations grouping params (nullity ty0)
+  -> Expression schema from grouping params ty0
   -- ^ value to convert
-  -> Expression schema relations grouping params (nullity ty1)
+  -> Expression schema from grouping params ty1
 cast ty x = UnsafeExpression $ parenthesized $
   renderExpression x <+> "::" <+> renderTypeExpression ty
 
@@ -614,135 +641,136 @@
 --
 -- >>> :{
 -- let
---   expression :: Expression schema relations grouping params (nullity 'PGint2)
+--   expression :: Expression schema from grouping params (nullity 'PGint2)
 --   expression = 5 `quot_` 2
 -- in printSQL expression
 -- :}
 -- (5 / 2)
 quot_
-  :: PGIntegral int
-  => Expression schema relations grouping params (nullity int)
+  :: int `In` PGIntegral
+  => Expression schema from grouping params (nullity int)
   -- ^ numerator
-  -> Expression schema relations grouping params (nullity int)
+  -> Expression schema from grouping params (nullity int)
   -- ^ denominator
-  -> Expression schema relations grouping params (nullity int)
+  -> Expression schema from grouping params (nullity int)
 quot_ = unsafeBinaryOp "/"
 
 -- | remainder upon integer division
 --
 -- >>> :{
 -- let
---   expression :: Expression schema relations grouping params (nullity 'PGint2)
+--   expression :: Expression schema from grouping params (nullity 'PGint2)
 --   expression = 5 `rem_` 2
 -- in printSQL expression
 -- :}
 -- (5 % 2)
 rem_
-  :: PGIntegral int
-  => Expression schema relations grouping params (nullity int)
+  :: int `In` PGIntegral
+  => Expression schema from grouping params (nullity int)
   -- ^ numerator
-  -> Expression schema relations grouping params (nullity int)
+  -> Expression schema from grouping params (nullity int)
   -- ^ denominator
-  -> Expression schema relations grouping params (nullity int)
+  -> Expression schema from grouping params (nullity int)
 rem_ = unsafeBinaryOp "%"
 
 -- | >>> :{
 -- let
---   expression :: Expression schema relations grouping params (nullity 'PGfloat4)
+--   expression :: Expression schema from grouping params (nullity 'PGfloat4)
 --   expression = trunc pi
 -- in printSQL expression
 -- :}
 -- trunc(pi())
 trunc
-  :: PGFloating frac
-  => Expression schema relations grouping params (nullity frac)
+  :: frac `In` PGFloating
+  => Expression schema from grouping params (nullity frac)
   -- ^ fractional number
-  -> Expression schema relations grouping params (nullity frac)
+  -> Expression schema from grouping params (nullity frac)
 trunc = unsafeFunction "trunc"
 
 -- | >>> :{
 -- let
---   expression :: Expression schema relations grouping params (nullity 'PGfloat4)
+--   expression :: Expression schema from grouping params (nullity 'PGfloat4)
 --   expression = round_ pi
 -- in printSQL expression
 -- :}
 -- round(pi())
 round_
-  :: PGFloating frac
-  => Expression schema relations grouping params (nullity frac)
+  :: frac `In` PGFloating
+  => Expression schema from grouping params (nullity frac)
   -- ^ fractional number
-  -> Expression schema relations grouping params (nullity frac)
+  -> Expression schema from grouping params (nullity frac)
 round_ = unsafeFunction "round"
 
 -- | >>> :{
 -- let
---   expression :: Expression schema relations grouping params (nullity 'PGfloat4)
+--   expression :: Expression schema from grouping params (nullity 'PGfloat4)
 --   expression = ceiling_ pi
 -- in printSQL expression
 -- :}
 -- ceiling(pi())
 ceiling_
-  :: PGFloating frac
-  => Expression schema relations grouping params (nullity frac)
+  :: frac `In` PGFloating
+  => Expression schema from grouping params (nullity frac)
   -- ^ fractional number
-  -> Expression schema relations grouping params (nullity frac)
+  -> Expression schema from grouping params (nullity frac)
 ceiling_ = unsafeFunction "ceiling"
 
--- | A `Condition` is a boolean valued `Expression`. While SQL allows
--- conditions to have @NULL@, Squeal instead chooses to disallow @NULL@,
--- forcing one to handle the case of @NULL@ explicitly to produce
--- a `Condition`.
-type Condition schema relations grouping params =
-  Expression schema relations grouping params ('NotNull 'PGbool)
+-- | A `Condition` is an `Expression`, which can evaluate
+-- to `true`, `false` or `null_`. This is because SQL uses
+-- a three valued logic.
+type Condition schema from grouping params =
+  Expression schema from grouping params ('Null 'PGbool)
 
 -- | >>> printSQL true
 -- TRUE
-true :: Condition schema relations grouping params
+true :: Expression schema from grouping params (nullity 'PGbool)
 true = UnsafeExpression "TRUE"
 
 -- | >>> printSQL false
 -- FALSE
-false :: Condition schema relations grouping params
+false :: Expression schema from grouping params (nullity 'PGbool)
 false = UnsafeExpression "FALSE"
 
 -- | >>> printSQL $ not_ true
 -- (NOT TRUE)
 not_
-  :: Condition schema relations grouping params
-  -> Condition schema relations grouping params
+  :: Expression schema from grouping params (nullity 'PGbool)
+  -> Expression schema from grouping params (nullity 'PGbool)
 not_ = unsafeUnaryOp "NOT"
 
 -- | >>> printSQL $ true .&& false
 -- (TRUE AND FALSE)
 (.&&)
-  :: Condition schema relations grouping params
-  -> Condition schema relations grouping params
-  -> Condition schema relations grouping params
+  :: Expression schema from grouping params (nullity 'PGbool)
+  -> Expression schema from grouping params (nullity 'PGbool)
+  -> Expression schema from grouping params (nullity 'PGbool)
+infixr 3 .&&
 (.&&) = unsafeBinaryOp "AND"
 
 -- | >>> printSQL $ true .|| false
 -- (TRUE OR FALSE)
 (.||)
-  :: Condition schema relations grouping params
-  -> Condition schema relations grouping params
-  -> Condition schema relations grouping params
+  :: Expression schema from grouping params (nullity 'PGbool)
+  -> Expression schema from grouping params (nullity 'PGbool)
+  -> Expression schema from grouping params (nullity 'PGbool)
+infixr 2 .||
 (.||) = unsafeBinaryOp "OR"
 
 -- | >>> :{
 -- let
---   expression :: Expression schema relations grouping params (nullity 'PGint2)
+--   expression :: Expression schema from grouping params (nullity 'PGint2)
 --   expression = caseWhenThenElse [(true, 1), (false, 2)] 3
 -- in printSQL expression
 -- :}
 -- CASE WHEN TRUE THEN 1 WHEN FALSE THEN 2 ELSE 3 END
 caseWhenThenElse
-  :: [ ( Condition schema relations grouping params
-       , Expression schema relations grouping params (ty)
+  :: [ ( Condition schema from grouping params
+       , Expression schema from grouping params ty
      ) ]
   -- ^ whens and thens
-  -> Expression schema relations grouping params (ty)
+  -> Expression schema from grouping params ty
   -- ^ else
-  -> Expression schema relations grouping params (ty)
+  -> Expression schema from grouping params ty
 caseWhenThenElse whenThens else_ = UnsafeExpression $ mconcat
   [ "CASE"
   , mconcat
@@ -758,103 +786,103 @@
 
 -- | >>> :{
 -- let
---   expression :: Expression schema relations grouping params (nullity 'PGint2)
+--   expression :: Expression schema from grouping params (nullity 'PGint2)
 --   expression = ifThenElse true 1 0
 -- in printSQL expression
 -- :}
 -- CASE WHEN TRUE THEN 1 ELSE 0 END
 ifThenElse
-  :: Condition schema relations grouping params
-  -> Expression schema relations grouping params (ty) -- ^ then
-  -> Expression schema relations grouping params (ty) -- ^ else
-  -> Expression schema relations grouping params (ty)
+  :: Condition schema from grouping params
+  -> Expression schema from grouping params ty -- ^ then
+  -> Expression schema from grouping params ty -- ^ else
+  -> Expression schema from grouping params ty
 ifThenElse if_ then_ else_ = caseWhenThenElse [(if_,then_)] else_
 
 -- | Comparison operations like `.==`, `./=`, `.>`, `.>=`, `.<` and `.<=`
 -- will produce @NULL@s if one of their arguments is @NULL@.
 --
--- >>> printSQL $ notNull true .== null_
+-- >>> printSQL $ true .== null_
 -- (TRUE = NULL)
 (.==)
-  :: Expression schema relations grouping params (nullity ty) -- ^ lhs
-  -> Expression schema relations grouping params (nullity ty) -- ^ rhs
-  -> Expression schema relations grouping params (nullity 'PGbool)
+  :: Expression schema from grouping params (nullity0 ty) -- ^ lhs
+  -> Expression schema from grouping params (nullity1 ty) -- ^ rhs
+  -> Condition schema from grouping params
 (.==) = unsafeBinaryOp "="
 infix 4 .==
 
--- | >>> printSQL $ notNull true ./= null_
+-- | >>> printSQL $ true ./= null_
 -- (TRUE <> NULL)
 (./=)
-  :: Expression schema relations grouping params (nullity ty) -- ^ lhs
-  -> Expression schema relations grouping params (nullity ty) -- ^ rhs
-  -> Expression schema relations grouping params (nullity 'PGbool)
+  :: Expression schema from grouping params (nullity0 ty) -- ^ lhs
+  -> Expression schema from grouping params (nullity1 ty) -- ^ rhs
+  -> Condition schema from grouping params
 (./=) = unsafeBinaryOp "<>"
 infix 4 ./=
 
--- | >>> printSQL $ notNull true .>= null_
+-- | >>> printSQL $ true .>= null_
 -- (TRUE >= NULL)
 (.>=)
-  :: Expression schema relations grouping params (nullity ty) -- ^ lhs
-  -> Expression schema relations grouping params (nullity ty) -- ^ rhs
-  -> Expression schema relations grouping params (nullity 'PGbool)
+  :: Expression schema from grouping params (nullity0 ty) -- ^ lhs
+  -> Expression schema from grouping params (nullity1 ty) -- ^ rhs
+  -> Condition schema from grouping params
 (.>=) = unsafeBinaryOp ">="
 infix 4 .>=
 
--- | >>> printSQL $ notNull true .< null_
+-- | >>> printSQL $ true .< null_
 -- (TRUE < NULL)
 (.<)
-  :: Expression schema relations grouping params (nullity ty) -- ^ lhs
-  -> Expression schema relations grouping params (nullity ty) -- ^ rhs
-  -> Expression schema relations grouping params (nullity 'PGbool)
+  :: Expression schema from grouping params (nullity0 ty) -- ^ lhs
+  -> Expression schema from grouping params (nullity1 ty) -- ^ rhs
+  -> Condition schema from grouping params
 (.<) = unsafeBinaryOp "<"
 infix 4 .<
 
--- | >>> printSQL $ notNull true .<= null_
+-- | >>> printSQL $ true .<= null_
 -- (TRUE <= NULL)
 (.<=)
-  :: Expression schema relations grouping params (nullity ty) -- ^ lhs
-  -> Expression schema relations grouping params (nullity ty) -- ^ rhs
-  -> Expression schema relations grouping params (nullity 'PGbool)
+  :: Expression schema from grouping params (nullity0 ty) -- ^ lhs
+  -> Expression schema from grouping params (nullity1 ty) -- ^ rhs
+  -> Condition schema from grouping params
 (.<=) = unsafeBinaryOp "<="
 infix 4 .<=
 
--- | >>> printSQL $ notNull true .> null_
+-- | >>> printSQL $ true .> null_
 -- (TRUE > NULL)
 (.>)
-  :: Expression schema relations grouping params (nullity ty) -- ^ lhs
-  -> Expression schema relations grouping params (nullity ty) -- ^ rhs
-  -> Expression schema relations grouping params (nullity 'PGbool)
+  :: Expression schema from grouping params (nullity0 ty) -- ^ lhs
+  -> Expression schema from grouping params (nullity1 ty) -- ^ rhs
+  -> Condition schema from grouping params
 (.>) = unsafeBinaryOp ">"
 infix 4 .>
 
 -- | >>> printSQL currentDate
 -- CURRENT_DATE
 currentDate
-  :: Expression schema relations grouping params (nullity 'PGdate)
+  :: Expression schema from grouping params (nullity 'PGdate)
 currentDate = UnsafeExpression "CURRENT_DATE"
 
 -- | >>> printSQL currentTime
 -- CURRENT_TIME
 currentTime
-  :: Expression schema relations grouping params (nullity 'PGtimetz)
+  :: Expression schema from grouping params (nullity 'PGtimetz)
 currentTime = UnsafeExpression "CURRENT_TIME"
 
 -- | >>> printSQL currentTimestamp
 -- CURRENT_TIMESTAMP
 currentTimestamp
-  :: Expression schema relations grouping params (nullity 'PGtimestamptz)
+  :: Expression schema from grouping params (nullity 'PGtimestamptz)
 currentTimestamp = UnsafeExpression "CURRENT_TIMESTAMP"
 
 -- | >>> printSQL localTime
 -- LOCALTIME
 localTime
-  :: Expression schema relations grouping params (nullity 'PGtime)
+  :: Expression schema from grouping params (nullity 'PGtime)
 localTime = UnsafeExpression "LOCALTIME"
 
 -- | >>> printSQL localTimestamp
 -- LOCALTIMESTAMP
 localTimestamp
-  :: Expression schema relations grouping params (nullity 'PGtimestamp)
+  :: Expression schema from grouping params (nullity 'PGtimestamp)
 localTimestamp = UnsafeExpression "LOCALTIMESTAMP"
 
 {-----------------------------------------
@@ -862,7 +890,7 @@
 -----------------------------------------}
 
 instance IsString
-  (Expression schema relations grouping params (nullity 'PGtext)) where
+  (Expression schema from grouping params (nullity 'PGtext)) where
     fromString str = UnsafeExpression $
       "E\'" <> fromString (escape =<< str) <> "\'"
       where
@@ -878,36 +906,36 @@
           c -> [c]
 
 instance Semigroup
-  (Expression schema relations grouping params (nullity 'PGtext)) where
+  (Expression schema from grouping params (nullity 'PGtext)) where
     (<>) = unsafeBinaryOp "||"
 
 instance Monoid
-  (Expression schema relations grouping params (nullity 'PGtext)) where
+  (Expression schema from grouping params (nullity 'PGtext)) where
     mempty = fromString ""
     mappend = (<>)
 
 -- | >>> printSQL $ lower "ARRRGGG"
 -- lower(E'ARRRGGG')
 lower
-  :: Expression schema relations grouping params (nullity 'PGtext)
+  :: Expression schema from grouping params (nullity 'PGtext)
   -- ^ string to lower case
-  -> Expression schema relations grouping params (nullity 'PGtext)
+  -> Expression schema from grouping params (nullity 'PGtext)
 lower = unsafeFunction "lower"
 
 -- | >>> printSQL $ upper "eeee"
 -- upper(E'eeee')
 upper
-  :: Expression schema relations grouping params (nullity 'PGtext)
+  :: Expression schema from grouping params (nullity 'PGtext)
   -- ^ string to upper case
-  -> Expression schema relations grouping params (nullity 'PGtext)
+  -> Expression schema from grouping params (nullity 'PGtext)
 upper = unsafeFunction "upper"
 
 -- | >>> printSQL $ charLength "four"
 -- char_length(E'four')
 charLength
-  :: Expression schema relations grouping params (nullity 'PGtext)
+  :: Expression schema from grouping params (nullity 'PGtext)
   -- ^ string to measure
-  -> Expression schema relations grouping params (nullity 'PGint4)
+  -> Expression schema from grouping params (nullity 'PGint4)
 charLength = unsafeFunction "char_length"
 
 -- | The `like` expression returns true if the @string@ matches
@@ -920,11 +948,11 @@
 -- >>> printSQL $ "abc" `like` "a%"
 -- (E'abc' LIKE E'a%')
 like
-  :: Expression schema relations grouping params (nullity 'PGtext)
+  :: Expression schema from grouping params (nullity 'PGtext)
   -- ^ string
-  -> Expression schema relations grouping params (nullity 'PGtext)
+  -> Expression schema from grouping params (nullity 'PGtext)
   -- ^ pattern
-  -> Expression schema relations grouping params (nullity 'PGbool)
+  -> Expression schema from grouping params (nullity 'PGbool)
 like = unsafeBinaryOp "LIKE"
 
 {-----------------------------------------
@@ -938,34 +966,38 @@
 
 -- | Get JSON value (object field or array element) at a key.
 (.->)
-  :: (PGjson_ json, PGjsonKey key)
-  => Expression schema relations grouping params (nullity json)
-  -> Expression schema relations grouping params (nullity key)
-  -> Expression schema relations grouping params ('Null json)
+  :: (json `In` PGJsonType, key `In` PGJsonKey)
+  => Expression schema from grouping params (nullity json)
+  -> Expression schema from grouping params (nullity key)
+  -> Expression schema from grouping params ('Null json)
+infixl 8 .->
 (.->) = unsafeBinaryOp "->"
 
 -- | Get JSON value (object field or array element) at a key, as text.
 (.->>)
-  :: (PGjson_ json, PGjsonKey key)
-  => Expression schema relations grouping params (nullity json)
-  -> Expression schema relations grouping params (nullity key)
-  -> Expression schema relations grouping params ('Null 'PGtext)
+  :: (json `In` PGJsonType, key `In` PGJsonKey)
+  => Expression schema from grouping params (nullity json)
+  -> Expression schema from grouping params (nullity key)
+  -> Expression schema from grouping params ('Null 'PGtext)
+infixl 8 .->>
 (.->>) = unsafeBinaryOp "->>"
 
 -- | Get JSON value at a specified path.
 (.#>)
-  :: (PGjson_ json, PGtextArray "(.#>)" path)
-  => Expression schema relations grouping params (nullity json)
-  -> Expression schema relations grouping params (nullity path)
-  -> Expression schema relations grouping params ('Null json)
+  :: (json `In` PGJsonType, PGTextArray "(.#>)" path)
+  => Expression schema from grouping params (nullity json)
+  -> Expression schema from grouping params (nullity path)
+  -> Expression schema from grouping params ('Null json)
+infixl 8 .#>
 (.#>) = unsafeBinaryOp "#>"
 
 -- | Get JSON value at a specified path as text.
 (.#>>)
-  :: (PGjson_ json, PGtextArray "(.#>>)" path)
-  => Expression schema relations grouping params (nullity json)
-  -> Expression schema relations grouping params (nullity path)
-  -> Expression schema relations grouping params ('Null 'PGtext)
+  :: (json `In` PGJsonType, PGTextArray "(.#>>)" path)
+  => Expression schema from grouping params (nullity json)
+  -> Expression schema from grouping params (nullity path)
+  -> Expression schema from grouping params ('Null 'PGtext)
+infixl 8 .#>>
 (.#>>) = unsafeBinaryOp "#>>"
 
 -- Additional jsonb operators
@@ -973,43 +1005,48 @@
 -- | Does the left JSON value contain the right JSON path/value entries at the
 -- top level?
 (.@>)
-  :: Expression schema relations grouping params (nullity 'PGjsonb)
-  -> Expression schema relations grouping params (nullity 'PGjsonb)
-  -> Condition schema relations grouping params
+  :: Expression schema from grouping params (nullity 'PGjsonb)
+  -> Expression schema from grouping params (nullity 'PGjsonb)
+  -> Condition schema from grouping params
+infixl 9 .@>
 (.@>) = unsafeBinaryOp "@>"
 
 -- | Are the left JSON path/value entries contained at the top level within the
 -- right JSON value?
 (.<@)
-  :: Expression schema relations grouping params (nullity 'PGjsonb)
-  -> Expression schema relations grouping params (nullity 'PGjsonb)
-  -> Condition schema relations grouping params
+  :: Expression schema from grouping params (nullity 'PGjsonb)
+  -> Expression schema from grouping params (nullity 'PGjsonb)
+  -> Condition schema from grouping params
+infixl 9 .<@
 (.<@) = unsafeBinaryOp "<@"
 
 -- | Does the string exist as a top-level key within the JSON value?
 (.?)
-  :: Expression schema relations grouping params (nullity 'PGjsonb)
-  -> Expression schema relations grouping params (nullity 'PGtext)
-  -> Condition schema relations grouping params
+  :: Expression schema from grouping params (nullity 'PGjsonb)
+  -> Expression schema from grouping params (nullity 'PGtext)
+  -> Condition schema from grouping params
+infixl 9 .?
 (.?) = unsafeBinaryOp "?"
 
 -- | Do any of these array strings exist as top-level keys?
 (.?|)
-  :: Expression schema relations grouping params (nullity 'PGjsonb)
-  -> Expression schema relations grouping params (nullity ('PGvararray 'PGtext))
-  -> Condition schema relations grouping params
+  :: Expression schema from grouping params (nullity 'PGjsonb)
+  -> Expression schema from grouping params (nullity ('PGvararray ('NotNull 'PGtext)))
+  -> Condition schema from grouping params
+infixl 9 .?|
 (.?|) = unsafeBinaryOp "?|"
 
 -- | Do all of these array strings exist as top-level keys?
 (.?&)
-  :: Expression schema relations grouping params (nullity 'PGjsonb)
-  -> Expression schema relations grouping params (nullity ('PGvararray 'PGtext))
-  -> Condition schema relations grouping params
+  :: Expression schema from grouping params (nullity 'PGjsonb)
+  -> Expression schema from grouping params (nullity ('PGvararray ('NotNull 'PGtext)))
+  -> Condition schema from grouping params
+infixl 9 .?&
 (.?&) = unsafeBinaryOp "?&"
 
 -- | Concatenate two jsonb values into a new jsonb value.
 instance
-  Semigroup (Expression schema relations grouping param (nullity 'PGjsonb)) where
+  Semigroup (Expression schema from grouping param (nullity 'PGjsonb)) where
   (<>) = unsafeBinaryOp "||"
 
 -- | Delete a key or keys from a JSON object, or remove an array element.
@@ -1025,19 +1062,21 @@
 -- @ integer @: Delete the array element with specified index (Negative integers
 -- count from the end). Throws an error if top level container is not an array.
 (.-.)
-  :: (key `In` '[ 'PGtext, 'PGvararray 'PGtext, 'PGint4, 'PGint2 ]) -- hlint error without parens here
-  => Expression schema relations grouping params (nullity 'PGjsonb)
-  -> Expression schema relations grouping params (nullity key)
-  -> Expression schema relations grouping params (nullity 'PGjsonb)
+  :: (key `In` '[ 'PGtext, 'PGvararray ('NotNull 'PGtext), 'PGint4, 'PGint2 ]) -- hlint error without parens here
+  => Expression schema from grouping params (nullity 'PGjsonb)
+  -> Expression schema from grouping params (nullity key)
+  -> Expression schema from grouping params (nullity 'PGjsonb)
+infixl 6 .-.
 (.-.) = unsafeBinaryOp "-"
 
 -- | Delete the field or element with specified path (for JSON arrays, negative
 -- integers count from the end)
 (#-.)
-  :: PGtextArray "(#-.)" arrayty
-  => Expression schema relations grouping params (nullity 'PGjsonb)
-  -> Expression schema relations grouping params (nullity arrayty)
-  -> Expression schema relations grouping params (nullity 'PGjsonb)
+  :: PGTextArray "(#-.)" arrayty
+  => Expression schema from grouping params (nullity 'PGjsonb)
+  -> Expression schema from grouping params (nullity arrayty)
+  -> Expression schema from grouping params (nullity 'PGjsonb)
+infixl 6 #-.
 (#-.) = unsafeBinaryOp "#-"
 
 {-----------------------------------------
@@ -1045,12 +1084,18 @@
 -----------------------------------------}
 
 -- | Literal binary JSON
-jsonbLit :: JSON.Value -> Expression schema relations grouping params (nullity 'PGjsonb)
-jsonbLit = cast jsonb . UnsafeExpression . singleQuotedUtf8 . toStrict . JSON.encode
+jsonbLit
+  :: JSON.ToJSON x
+  => x -> Expression schema from grouping params (nullity 'PGjsonb)
+jsonbLit = cast jsonb . UnsafeExpression
+  . singleQuotedUtf8 . toStrict . JSON.encode
 
 -- | Literal JSON
-jsonLit :: JSON.Value -> Expression schema relations grouping params (nullity 'PGjson)
-jsonLit = cast json . UnsafeExpression . singleQuotedUtf8 . toStrict . JSON.encode
+jsonLit
+  :: JSON.ToJSON x
+  => x -> Expression schema from grouping params (nullity 'PGjson)
+jsonLit = cast json . UnsafeExpression
+  . singleQuotedUtf8 . toStrict . JSON.encode
 
 -- | Returns the value as json. Arrays and composites are converted
 -- (recursively) to arrays and objects; otherwise, if there is a cast from the
@@ -1059,8 +1104,8 @@
 -- number, a Boolean, or a null value, the text representation will be used, in
 -- such a fashion that it is a valid json value.
 toJson
-  :: Expression schema relations grouping params (nullity ty)
-  -> Expression schema relations grouping params (nullity 'PGjson)
+  :: Expression schema from grouping params (nullity ty)
+  -> Expression schema from grouping params (nullity 'PGjson)
 toJson = unsafeFunction "to_json"
 
 -- | Returns the value as jsonb. Arrays and composites are converted
@@ -1070,43 +1115,43 @@
 -- number, a Boolean, or a null value, the text representation will be used, in
 -- such a fashion that it is a valid jsonb value.
 toJsonb
-  :: Expression schema relations grouping params (nullity ty)
-  -> Expression schema relations grouping params (nullity 'PGjsonb)
+  :: Expression schema from grouping params (nullity ty)
+  -> Expression schema from grouping params (nullity 'PGjsonb)
 toJsonb = unsafeFunction "to_jsonb"
 
 -- | Returns the array as a JSON array. A PostgreSQL multidimensional array
 -- becomes a JSON array of arrays.
 arrayToJson
-  :: PGarray "arrayToJson" arr
-  => Expression schema relations grouping params (nullity arr)
-  -> Expression schema relations grouping params (nullity 'PGjson)
+  :: PGArray "arrayToJson" arr
+  => Expression schema from grouping params (nullity arr)
+  -> Expression schema from grouping params (nullity 'PGjson)
 arrayToJson = unsafeFunction "array_to_json"
 
 -- | Returns the row as a JSON object.
 rowToJson
-  :: Expression schema relations grouping params (nullity ('PGcomposite ty))
-  -> Expression schema relations grouping params (nullity 'PGjson)
+  :: Expression schema from grouping params (nullity ('PGcomposite ty))
+  -> Expression schema from grouping params (nullity 'PGjson)
 rowToJson = unsafeFunction "row_to_json"
 
 -- | Builds a possibly-heterogeneously-typed JSON array out of a variadic
 -- argument list.
 jsonBuildArray
   :: SListI elems
-  => NP (Expression schema relations grouping params) elems
-  -> Expression schema relations grouping params (nullity 'PGjson)
+  => NP (Expression schema from grouping params) elems
+  -> Expression schema from grouping params (nullity 'PGjson)
 jsonBuildArray = unsafeVariadicFunction "json_build_array"
 
 -- | Builds a possibly-heterogeneously-typed (binary) JSON array out of a
 -- variadic argument list.
 jsonbBuildArray
   :: SListI elems
-  => NP (Expression schema relations grouping params) elems
-  -> Expression schema relations grouping params (nullity 'PGjsonb)
+  => NP (Expression schema from grouping params) elems
+  -> Expression schema from grouping params (nullity 'PGjsonb)
 jsonbBuildArray = unsafeVariadicFunction "jsonb_build_array"
 
 unsafeRowFunction
   :: All Top elems
-  => NP (Aliased (Expression schema relations grouping params)) elems
+  => NP (Aliased (Expression schema from grouping params)) elems
   -> [ByteString]
 unsafeRowFunction =
   (`appEndo` []) . hcfoldMap (Proxy :: Proxy Top)
@@ -1118,8 +1163,8 @@
 -- and values.
 jsonBuildObject
   :: All Top elems
-  => NP (Aliased (Expression schema relations grouping params)) elems
-  -> Expression schema relations grouping params (nullity 'PGjson)
+  => NP (Aliased (Expression schema from grouping params)) elems
+  -> Expression schema from grouping params (nullity 'PGjson)
 jsonBuildObject
   = unsafeFunction "json_build_object"
   . UnsafeExpression
@@ -1131,8 +1176,8 @@
 -- between text and values.
 jsonbBuildObject
   :: All Top elems
-  => NP (Aliased (Expression schema relations grouping params)) elems
-  -> Expression schema relations grouping params (nullity 'PGjsonb)
+  => NP (Aliased (Expression schema from grouping params)) elems
+  -> Expression schema from grouping params (nullity 'PGjsonb)
 jsonbBuildObject
   = unsafeFunction "jsonb_build_object"
   . UnsafeExpression
@@ -1144,9 +1189,9 @@
 -- taken as alternating key/value pairs, or two dimensions such that each inner
 -- array has exactly two elements, which are taken as a key/value pair.
 jsonObject
-  :: PGarrayOf "jsonObject" arr 'PGtext
-  => Expression schema relations grouping params (nullity arr)
-  -> Expression schema relations grouping params (nullity 'PGjson)
+  :: PGArrayOf "jsonObject" arr ('NotNull 'PGtext)
+  => Expression schema from grouping params (nullity arr)
+  -> Expression schema from grouping params (nullity 'PGjson)
 jsonObject = unsafeFunction "json_object"
 
 -- | Builds a binary JSON object out of a text array. The array must have either
@@ -1154,19 +1199,19 @@
 -- taken as alternating key/value pairs, or two dimensions such that each inner
 -- array has exactly two elements, which are taken as a key/value pair.
 jsonbObject
-  :: PGarrayOf "jsonbObject" arr 'PGtext
-  => Expression schema relations grouping params (nullity arr)
-  -> Expression schema relations grouping params (nullity 'PGjsonb)
+  :: PGArrayOf "jsonbObject" arr ('NotNull 'PGtext)
+  => Expression schema from grouping params (nullity arr)
+  -> Expression schema from grouping params (nullity 'PGjsonb)
 jsonbObject = unsafeFunction "jsonb_object"
 
 -- | This is an alternate form of 'jsonObject' that takes two arrays; one for
 -- keys and one for values, that are zipped pairwise to create a JSON object.
 jsonZipObject
-  :: ( PGarrayOf "jsonZipObject" keysArray 'PGtext
-     , PGarrayOf "jsonZipObject" valuesArray 'PGtext)
-  => Expression schema relations grouping params (nullity keysArray)
-  -> Expression schema relations grouping params (nullity valuesArray)
-  -> Expression schema relations grouping params (nullity 'PGjson)
+  :: ( PGArrayOf "jsonZipObject" keysArray ('NotNull 'PGtext)
+     , PGArrayOf "jsonZipObject" valuesArray ('NotNull 'PGtext))
+  => Expression schema from grouping params (nullity keysArray)
+  -> Expression schema from grouping params (nullity valuesArray)
+  -> Expression schema from grouping params (nullity 'PGjson)
 jsonZipObject ks vs =
   unsafeVariadicFunction "json_object" (ks :* vs :* Nil)
 
@@ -1174,11 +1219,11 @@
 -- keys and one for values, that are zipped pairwise to create a binary JSON
 -- object.
 jsonbZipObject
-  :: ( PGarrayOf "jsonbZipObject" keysArray 'PGtext
-     , PGarrayOf "jsonbZipObject" valuesArray 'PGtext)
-  => Expression schema relations grouping params (nullity keysArray)
-  -> Expression schema relations grouping params (nullity valuesArray)
-  -> Expression schema relations grouping params (nullity 'PGjsonb)
+  :: ( PGArrayOf "jsonbZipObject" keysArray ('NotNull 'PGtext)
+     , PGArrayOf "jsonbZipObject" valuesArray ('NotNull 'PGtext))
+  => Expression schema from grouping params (nullity keysArray)
+  -> Expression schema from grouping params (nullity valuesArray)
+  -> Expression schema from grouping params (nullity 'PGjsonb)
 jsonbZipObject ks vs =
   unsafeVariadicFunction "jsonb_object" (ks :* vs :* Nil)
 
@@ -1188,23 +1233,23 @@
 
 -- | Returns the number of elements in the outermost JSON array.
 jsonArrayLength
-  :: Expression schema relations grouping params (nullity 'PGjson)
-  -> Expression schema relations grouping params (nullity 'PGint4)
+  :: Expression schema from grouping params (nullity 'PGjson)
+  -> Expression schema from grouping params (nullity 'PGint4)
 jsonArrayLength = unsafeFunction "json_array_length"
 
 -- | Returns the number of elements in the outermost binary JSON array.
 jsonbArrayLength
-  :: Expression schema relations grouping params (nullity 'PGjsonb)
-  -> Expression schema relations grouping params (nullity 'PGint4)
+  :: Expression schema from grouping params (nullity 'PGjsonb)
+  -> Expression schema from grouping params (nullity 'PGint4)
 jsonbArrayLength = unsafeFunction "jsonb_array_length"
 
 -- | Returns JSON value pointed to by the given path (equivalent to #>
 -- operator).
 jsonExtractPath
   :: SListI elems
-  => Expression schema relations grouping params (nullity 'PGjson)
-  -> NP (Expression schema relations grouping params) elems
-  -> Expression schema relations grouping params (nullity 'PGjsonb)
+  => Expression schema from grouping params (nullity 'PGjson)
+  -> NP (Expression schema from grouping params) elems
+  -> Expression schema from grouping params (nullity 'PGjsonb)
 jsonExtractPath x xs =
   unsafeVariadicFunction "json_extract_path" (x :* xs)
 
@@ -1212,9 +1257,9 @@
 -- operator).
 jsonbExtractPath
   :: SListI elems
-  => Expression schema relations grouping params (nullity 'PGjsonb)
-  -> NP (Expression schema relations grouping params) elems
-  -> Expression schema relations grouping params (nullity 'PGjsonb)
+  => Expression schema from grouping params (nullity 'PGjsonb)
+  -> NP (Expression schema from grouping params) elems
+  -> Expression schema from grouping params (nullity 'PGjsonb)
 jsonbExtractPath x xs =
   unsafeVariadicFunction "jsonb_extract_path" (x :* xs)
 
@@ -1222,9 +1267,9 @@
 -- operator), as text.
 jsonExtractPathAsText
   :: SListI elems
-  => Expression schema relations grouping params (nullity 'PGjson)
-  -> NP (Expression schema relations grouping params) elems
-  -> Expression schema relations grouping params (nullity 'PGjson)
+  => Expression schema from grouping params (nullity 'PGjson)
+  -> NP (Expression schema from grouping params) elems
+  -> Expression schema from grouping params (nullity 'PGjson)
 jsonExtractPathAsText x xs =
   unsafeVariadicFunction "json_extract_path_text" (x :* xs)
 
@@ -1232,50 +1277,38 @@
 -- operator), as text.
 jsonbExtractPathAsText
   :: SListI elems
-  => Expression schema relations grouping params (nullity 'PGjsonb)
-  -> NP (Expression schema relations grouping params) elems
-  -> Expression schema relations grouping params (nullity 'PGjsonb)
+  => Expression schema from grouping params (nullity 'PGjsonb)
+  -> NP (Expression schema from grouping params) elems
+  -> Expression schema from grouping params (nullity 'PGjsonb)
 jsonbExtractPathAsText x xs =
   unsafeVariadicFunction "jsonb_extract_path_text" (x :* xs)
 
--- | Returns set of keys in the outermost JSON object.
-jsonObjectKeys
-  :: Expression schema relations grouping params (nullity 'PGjson)
-  -> Expression schema relations grouping params (nullity 'PGtext)
-jsonObjectKeys = unsafeFunction "json_object_keys"
-
--- | Returns set of keys in the outermost JSON object.
-jsonbObjectKeys
-  :: Expression schema relations grouping params (nullity 'PGjsonb)
-  -> Expression schema relations grouping params (nullity 'PGtext)
-jsonbObjectKeys = unsafeFunction "jsonb_object_keys"
-
 -- | Returns the type of the outermost JSON value as a text string. Possible
 -- types are object, array, string, number, boolean, and null.
 jsonTypeof
-  :: Expression schema relations grouping params (nullity 'PGjson)
-  -> Expression schema relations grouping params (nullity 'PGtext)
+  :: Expression schema from grouping params (nullity 'PGjson)
+  -> Expression schema from grouping params (nullity 'PGtext)
 jsonTypeof = unsafeFunction "json_typeof"
 
 -- | Returns the type of the outermost binary JSON value as a text string.
 -- Possible types are object, array, string, number, boolean, and null.
 jsonbTypeof
-  :: Expression schema relations grouping params (nullity 'PGjsonb)
-  -> Expression schema relations grouping params (nullity 'PGtext)
+  :: Expression schema from grouping params (nullity 'PGjsonb)
+  -> Expression schema from grouping params (nullity 'PGtext)
 jsonbTypeof = unsafeFunction "jsonb_typeof"
 
 -- | Returns its argument with all object fields that have null values omitted.
 -- Other null values are untouched.
 jsonStripNulls
-  :: Expression schema relations grouping params (nullity 'PGjson)
-  -> Expression schema relations grouping params (nullity 'PGjson)
+  :: Expression schema from grouping params (nullity 'PGjson)
+  -> Expression schema from grouping params (nullity 'PGjson)
 jsonStripNulls = unsafeFunction "json_strip_nulls"
 
 -- | Returns its argument with all object fields that have null values omitted.
 -- Other null values are untouched.
 jsonbStripNulls
-  :: Expression schema relations grouping params (nullity 'PGjsonb)
-  -> Expression schema relations grouping params (nullity 'PGjsonb)
+  :: Expression schema from grouping params (nullity 'PGjsonb)
+  -> Expression schema from grouping params (nullity 'PGjsonb)
 jsonbStripNulls = unsafeFunction "jsonb_strip_nulls"
 
 -- | @ jsonbSet target path new_value create_missing @
@@ -1286,12 +1319,12 @@
 -- operators, negative integers that appear in path count from the end of JSON
 -- arrays.
 jsonbSet
-  :: PGtextArray "jsonbSet" arr
-  => Expression schema relations grouping params (nullity 'PGjsonb)
-  -> Expression schema relations grouping params (nullity arr)
-  -> Expression schema relations grouping params (nullity 'PGjsonb)
-  -> Maybe (Expression schema relations grouping params (nullity 'PGbool))
-  -> Expression schema relations grouping params (nullity 'PGjsonb)
+  :: PGTextArray "jsonbSet" arr
+  => Expression schema from grouping params (nullity 'PGjsonb)
+  -> Expression schema from grouping params (nullity arr)
+  -> Expression schema from grouping params (nullity 'PGjsonb)
+  -> Maybe (Expression schema from grouping params (nullity 'PGbool))
+  -> Expression schema from grouping params (nullity 'PGjsonb)
 jsonbSet tgt path val createMissing = case createMissing of
   Just m -> unsafeVariadicFunction "jsonb_set" (tgt :* path :* val :* m :* Nil)
   Nothing -> unsafeVariadicFunction "jsonb_set" (tgt :* path :* val :* Nil)
@@ -1305,20 +1338,20 @@
 -- exist. As with the path orientated operators, negative integers that appear
 -- in path count from the end of JSON arrays.
 jsonbInsert
-  :: PGtextArray "jsonbInsert" arr
-  => Expression schema relations grouping params (nullity 'PGjsonb)
-  -> Expression schema relations grouping params (nullity arr)
-  -> Expression schema relations grouping params (nullity 'PGjsonb)
-  -> Maybe (Expression schema relations grouping params (nullity 'PGbool))
-  -> Expression schema relations grouping params (nullity 'PGjsonb)
+  :: PGTextArray "jsonbInsert" arr
+  => Expression schema from grouping params (nullity 'PGjsonb)
+  -> Expression schema from grouping params (nullity arr)
+  -> Expression schema from grouping params (nullity 'PGjsonb)
+  -> Maybe (Expression schema from grouping params (nullity 'PGbool))
+  -> Expression schema from grouping params (nullity 'PGjsonb)
 jsonbInsert tgt path val insertAfter = case insertAfter of
   Just i -> unsafeVariadicFunction "jsonb_insert" (tgt :* path :* val :* i :* Nil)
   Nothing -> unsafeVariadicFunction "jsonb_insert" (tgt :* path :* val :* Nil)
 
 -- | Returns its argument as indented JSON text.
 jsonbPretty
-  :: Expression schema relations grouping params (nullity 'PGjsonb)
-  -> Expression schema relations grouping params (nullity 'PGtext)
+  :: Expression schema from grouping params (nullity 'PGjsonb)
+  -> Expression schema from grouping params (nullity 'PGtext)
 jsonbPretty = unsafeFunction "jsonb_pretty"
 
 {-----------------------------------------
@@ -1328,16 +1361,16 @@
 -- | escape hatch to define aggregate functions
 unsafeAggregate
   :: ByteString -- ^ aggregate function
-  -> Expression schema relations 'Ungrouped params (xty)
-  -> Expression schema relations ('Grouped bys) params (yty)
+  -> Expression schema from 'Ungrouped params (xty)
+  -> Expression schema from ('Grouped bys) params (yty)
 unsafeAggregate fun x = UnsafeExpression $ mconcat
   [fun, "(", renderExpression x, ")"]
 
 -- | escape hatch to define aggregate functions over distinct values
 unsafeAggregateDistinct
   :: ByteString -- ^ aggregate function
-  -> Expression schema relations 'Ungrouped params (xty)
-  -> Expression schema relations ('Grouped bys) params (yty)
+  -> Expression schema from 'Ungrouped params (xty)
+  -> Expression schema from ('Grouped bys) params (yty)
 unsafeAggregateDistinct fun x = UnsafeExpression $ mconcat
   [fun, "(DISTINCT ", renderExpression x, ")"]
 
@@ -1349,10 +1382,10 @@
 -- :}
 -- sum("col")
 sum_
-  :: PGNum ty
-  => Expression schema relations 'Ungrouped params (nullity ty)
+  :: ty `In` PGNum
+  => Expression schema from 'Ungrouped params (nullity ty)
   -- ^ what to sum
-  -> Expression schema relations ('Grouped bys) params (nullity ty)
+  -> Expression schema from ('Grouped bys) params (nullity ty)
 sum_ = unsafeAggregate "sum"
 
 -- | >>> :{
@@ -1363,19 +1396,19 @@
 -- :}
 -- sum(DISTINCT "col")
 sumDistinct
-  :: PGNum ty
-  => Expression schema relations 'Ungrouped params (nullity ty)
+  :: ty `In` PGNum
+  => Expression schema from 'Ungrouped params (nullity ty)
   -- ^ what to sum
-  -> Expression schema relations ('Grouped bys) params (nullity ty)
+  -> Expression schema from ('Grouped bys) params (nullity ty)
 sumDistinct = unsafeAggregateDistinct "sum"
 
 -- | A constraint for `PGType`s that you can take averages of and the resulting
 -- `PGType`.
 class PGAvg ty avg | ty -> avg where
   avg, avgDistinct
-    :: Expression schema relations 'Ungrouped params (nullity ty)
+    :: Expression schema from 'Ungrouped params (nullity ty)
     -- ^ what to average
-    -> Expression schema relations ('Grouped bys) params (nullity avg)
+    -> Expression schema from ('Grouped bys) params (nullity avg)
   avg = unsafeAggregate "avg"
   avgDistinct = unsafeAggregateDistinct "avg"
 instance PGAvg 'PGint2 'PGnumeric
@@ -1394,10 +1427,10 @@
 -- :}
 -- bit_and("col")
 bitAnd
-  :: PGIntegral int
-  => Expression schema relations 'Ungrouped params (nullity int)
+  :: int `In` PGIntegral
+  => Expression schema from 'Ungrouped params (nullity int)
   -- ^ what to aggregate
-  -> Expression schema relations ('Grouped bys) params (nullity int)
+  -> Expression schema from ('Grouped bys) params (nullity int)
 bitAnd = unsafeAggregate "bit_and"
 
 -- | >>> :{
@@ -1408,10 +1441,10 @@
 -- :}
 -- bit_or("col")
 bitOr
-  :: PGIntegral int
-  => Expression schema relations 'Ungrouped params (nullity int)
+  :: int `In` PGIntegral
+  => Expression schema from 'Ungrouped params (nullity int)
   -- ^ what to aggregate
-  -> Expression schema relations ('Grouped bys) params (nullity int)
+  -> Expression schema from ('Grouped bys) params (nullity int)
 bitOr = unsafeAggregate "bit_or"
 
 -- | >>> :{
@@ -1422,10 +1455,10 @@
 -- :}
 -- bit_and(DISTINCT "col")
 bitAndDistinct
-  :: PGIntegral int
-  => Expression schema relations 'Ungrouped params (nullity int)
+  :: int `In` PGIntegral
+  => Expression schema from 'Ungrouped params (nullity int)
   -- ^ what to aggregate
-  -> Expression schema relations ('Grouped bys) params (nullity int)
+  -> Expression schema from ('Grouped bys) params (nullity int)
 bitAndDistinct = unsafeAggregateDistinct "bit_and"
 
 -- | >>> :{
@@ -1436,10 +1469,10 @@
 -- :}
 -- bit_or(DISTINCT "col")
 bitOrDistinct
-  :: PGIntegral int
-  => Expression schema relations 'Ungrouped params (nullity int)
+  :: int `In` PGIntegral
+  => Expression schema from 'Ungrouped params (nullity int)
   -- ^ what to aggregate
-  -> Expression schema relations ('Grouped bys) params (nullity int)
+  -> Expression schema from ('Grouped bys) params (nullity int)
 bitOrDistinct = unsafeAggregateDistinct "bit_or"
 
 -- | >>> :{
@@ -1450,9 +1483,9 @@
 -- :}
 -- bool_and("col")
 boolAnd
-  :: Expression schema relations 'Ungrouped params (nullity 'PGbool)
+  :: Expression schema from 'Ungrouped params (nullity 'PGbool)
   -- ^ what to aggregate
-  -> Expression schema relations ('Grouped bys) params (nullity 'PGbool)
+  -> Expression schema from ('Grouped bys) params (nullity 'PGbool)
 boolAnd = unsafeAggregate "bool_and"
 
 -- | >>> :{
@@ -1463,9 +1496,9 @@
 -- :}
 -- bool_or("col")
 boolOr
-  :: Expression schema relations 'Ungrouped params (nullity 'PGbool)
+  :: Expression schema from 'Ungrouped params (nullity 'PGbool)
   -- ^ what to aggregate
-  -> Expression schema relations ('Grouped bys) params (nullity 'PGbool)
+  -> Expression schema from ('Grouped bys) params (nullity 'PGbool)
 boolOr = unsafeAggregate "bool_or"
 
 -- | >>> :{
@@ -1476,9 +1509,9 @@
 -- :}
 -- bool_and(DISTINCT "col")
 boolAndDistinct
-  :: Expression schema relations 'Ungrouped params (nullity 'PGbool)
+  :: Expression schema from 'Ungrouped params (nullity 'PGbool)
   -- ^ what to aggregate
-  -> Expression schema relations ('Grouped bys) params (nullity 'PGbool)
+  -> Expression schema from ('Grouped bys) params (nullity 'PGbool)
 boolAndDistinct = unsafeAggregateDistinct "bool_and"
 
 -- | >>> :{
@@ -1489,9 +1522,9 @@
 -- :}
 -- bool_or(DISTINCT "col")
 boolOrDistinct
-  :: Expression schema relations 'Ungrouped params (nullity 'PGbool)
+  :: Expression schema from 'Ungrouped params (nullity 'PGbool)
   -- ^ what to aggregate
-  -> Expression schema relations ('Grouped bys) params (nullity 'PGbool)
+  -> Expression schema from ('Grouped bys) params (nullity 'PGbool)
 boolOrDistinct = unsafeAggregateDistinct "bool_or"
 
 -- | A special aggregation that does not require an input
@@ -1499,7 +1532,7 @@
 -- >>> printSQL countStar
 -- count(*)
 countStar
-  :: Expression schema relations ('Grouped bys) params ('NotNull 'PGint8)
+  :: Expression schema from ('Grouped bys) params ('NotNull 'PGint8)
 countStar = UnsafeExpression $ "count(*)"
 
 -- | >>> :{
@@ -1510,9 +1543,9 @@
 -- :}
 -- count("col")
 count
-  :: Expression schema relations 'Ungrouped params ty
+  :: Expression schema from 'Ungrouped params ty
   -- ^ what to count
-  -> Expression schema relations ('Grouped bys) params ('NotNull 'PGint8)
+  -> Expression schema from ('Grouped bys) params ('NotNull 'PGint8)
 count = unsafeAggregate "count"
 
 -- | >>> :{
@@ -1523,9 +1556,9 @@
 -- :}
 -- count(DISTINCT "col")
 countDistinct
-  :: Expression schema relations 'Ungrouped params ty
+  :: Expression schema from 'Ungrouped params ty
   -- ^ what to count
-  -> Expression schema relations ('Grouped bys) params ('NotNull 'PGint8)
+  -> Expression schema from ('Grouped bys) params ('NotNull 'PGint8)
 countDistinct = unsafeAggregateDistinct "count"
 
 -- | synonym for `boolAnd`
@@ -1538,9 +1571,9 @@
 -- :}
 -- every("col")
 every
-  :: Expression schema relations 'Ungrouped params (nullity 'PGbool)
+  :: Expression schema from 'Ungrouped params (nullity 'PGbool)
   -- ^ what to aggregate
-  -> Expression schema relations ('Grouped bys) params (nullity 'PGbool)
+  -> Expression schema from ('Grouped bys) params (nullity 'PGbool)
 every = unsafeAggregate "every"
 
 -- | synonym for `boolAndDistinct`
@@ -1553,16 +1586,16 @@
 -- :}
 -- every(DISTINCT "col")
 everyDistinct
-  :: Expression schema relations 'Ungrouped params (nullity 'PGbool)
+  :: Expression schema from 'Ungrouped params (nullity 'PGbool)
   -- ^ what to aggregate
-  -> Expression schema relations ('Grouped bys) params (nullity 'PGbool)
+  -> Expression schema from ('Grouped bys) params (nullity 'PGbool)
 everyDistinct = unsafeAggregateDistinct "every"
 
 -- | minimum and maximum aggregation
 max_, min_, maxDistinct, minDistinct
-  :: Expression schema relations 'Ungrouped params (nullity ty)
+  :: Expression schema from 'Ungrouped params (nullity ty)
   -- ^ what to aggregate
-  -> Expression schema relations ('Grouped bys) params (nullity ty)
+  -> Expression schema from ('Grouped bys) params (nullity ty)
 max_ = unsafeAggregate "max"
 min_ = unsafeAggregate "min"
 maxDistinct = unsafeAggregateDistinct "max"
@@ -1573,134 +1606,153 @@
 -----------------------------------------}
 
 -- | `TypeExpression`s are used in `cast`s and `createTable` commands.
-newtype TypeExpression (schema :: SchemaType) (ty :: PGType)
+newtype TypeExpression (schema :: SchemaType) (ty :: NullityType)
   = UnsafeTypeExpression { renderTypeExpression :: ByteString }
   deriving (GHC.Generic,Show,Eq,Ord,NFData)
 
-instance (Has alias schema ('Typedef ty))
-  => IsLabel alias (TypeExpression schema ty) where
-    fromLabel = UnsafeTypeExpression (renderAlias (fromLabel @alias))
+-- | The enum or composite type in a `Typedef` can be expressed by its alias.
+typedef
+  :: Has alias schema ('Typedef ty)
+  => Alias alias
+  -> TypeExpression schema (nullity ty)
+typedef = UnsafeTypeExpression . renderAlias
 
+-- | The composite type corresponding to a `Table` definition can be expressed
+-- by its alias.
+typetable
+  :: Has alias schema ('Table tab)
+  => Alias alias
+  -> TypeExpression schema (nullity ('PGcomposite (TableToRow tab)))
+typetable = UnsafeTypeExpression . renderAlias
+
+-- | The composite type corresponding to a `View` definition can be expressed
+-- by its alias.
+typeview
+  :: Has alias schema ('View view)
+  => Alias alias
+  -> TypeExpression schema (nullity ('PGcomposite view))
+typeview = UnsafeTypeExpression . renderAlias
+
 -- | logical Boolean (true/false)
-bool :: TypeExpression schema 'PGbool
+bool :: TypeExpression schema (nullity 'PGbool)
 bool = UnsafeTypeExpression "bool"
 -- | signed two-byte integer
-int2, smallint :: TypeExpression schema 'PGint2
+int2, smallint :: TypeExpression schema (nullity 'PGint2)
 int2 = UnsafeTypeExpression "int2"
 smallint = UnsafeTypeExpression "smallint"
 -- | signed four-byte integer
-int4, int, integer :: TypeExpression schema 'PGint4
+int4, int, integer :: TypeExpression schema (nullity 'PGint4)
 int4 = UnsafeTypeExpression "int4"
 int = UnsafeTypeExpression "int"
 integer = UnsafeTypeExpression "integer"
 -- | signed eight-byte integer
-int8, bigint :: TypeExpression schema 'PGint8
+int8, bigint :: TypeExpression schema (nullity 'PGint8)
 int8 = UnsafeTypeExpression "int8"
 bigint = UnsafeTypeExpression "bigint"
 -- | arbitrary precision numeric type
-numeric :: TypeExpression schema 'PGnumeric
+numeric :: TypeExpression schema (nullity 'PGnumeric)
 numeric = UnsafeTypeExpression "numeric"
 -- | single precision floating-point number (4 bytes)
-float4, real :: TypeExpression schema 'PGfloat4
+float4, real :: TypeExpression schema (nullity 'PGfloat4)
 float4 = UnsafeTypeExpression "float4"
 real = UnsafeTypeExpression "real"
 -- | double precision floating-point number (8 bytes)
-float8, doublePrecision :: TypeExpression schema 'PGfloat8
+float8, doublePrecision :: TypeExpression schema (nullity 'PGfloat8)
 float8 = UnsafeTypeExpression "float8"
 doublePrecision = UnsafeTypeExpression "double precision"
 -- | variable-length character string
-text :: TypeExpression schema 'PGtext
+text :: TypeExpression schema (nullity 'PGtext)
 text = UnsafeTypeExpression "text"
 -- | fixed-length character string
 char, character
-  :: (KnownNat n, 1 <= n)
-  => proxy n
-  -> TypeExpression schema ('PGchar n)
-char p = UnsafeTypeExpression $ "char(" <> renderNat p <> ")"
-character p = UnsafeTypeExpression $  "character(" <> renderNat p <> ")"
+  :: forall n schema nullity. (KnownNat n, 1 <= n)
+  => TypeExpression schema (nullity ('PGchar n))
+char = UnsafeTypeExpression $ "char(" <> renderNat @n <> ")"
+character = UnsafeTypeExpression $  "character(" <> renderNat @n <> ")"
 -- | variable-length character string
 varchar, characterVarying
-  :: (KnownNat n, 1 <= n)
-  => proxy n
-  -> TypeExpression schema ('PGvarchar n)
-varchar p = UnsafeTypeExpression $ "varchar(" <> renderNat p <> ")"
-characterVarying p = UnsafeTypeExpression $
-  "character varying(" <> renderNat p <> ")"
+  :: forall n schema nullity. (KnownNat n, 1 <= n)
+  => TypeExpression schema (nullity ('PGvarchar n))
+varchar = UnsafeTypeExpression $ "varchar(" <> renderNat @n <> ")"
+characterVarying = UnsafeTypeExpression $
+  "character varying(" <> renderNat @n <> ")"
 -- | binary data ("byte array")
-bytea :: TypeExpression schema 'PGbytea
+bytea :: TypeExpression schema (nullity 'PGbytea)
 bytea = UnsafeTypeExpression "bytea"
 -- | date and time (no time zone)
-timestamp :: TypeExpression schema 'PGtimestamp
+timestamp :: TypeExpression schema (nullity 'PGtimestamp)
 timestamp = UnsafeTypeExpression "timestamp"
 -- | date and time, including time zone
-timestampWithTimeZone :: TypeExpression schema 'PGtimestamptz
+timestampWithTimeZone :: TypeExpression schema (nullity 'PGtimestamptz)
 timestampWithTimeZone = UnsafeTypeExpression "timestamp with time zone"
 -- | calendar date (year, month, day)
-date :: TypeExpression schema 'PGdate
+date :: TypeExpression schema (nullity 'PGdate)
 date = UnsafeTypeExpression "date"
 -- | time of day (no time zone)
-time :: TypeExpression schema 'PGtime
+time :: TypeExpression schema (nullity 'PGtime)
 time = UnsafeTypeExpression "time"
 -- | time of day, including time zone
-timeWithTimeZone :: TypeExpression schema 'PGtimetz
+timeWithTimeZone :: TypeExpression schema (nullity 'PGtimetz)
 timeWithTimeZone = UnsafeTypeExpression "time with time zone"
 -- | time span
-interval :: TypeExpression schema 'PGinterval
+interval :: TypeExpression schema (nullity 'PGinterval)
 interval = UnsafeTypeExpression "interval"
 -- | universally unique identifier
-uuid :: TypeExpression schema 'PGuuid
+uuid :: TypeExpression schema (nullity 'PGuuid)
 uuid = UnsafeTypeExpression "uuid"
 -- | IPv4 or IPv6 host address
-inet :: TypeExpression schema 'PGinet
+inet :: TypeExpression schema (nullity 'PGinet)
 inet = UnsafeTypeExpression "inet"
 -- | textual JSON data
-json :: TypeExpression schema 'PGjson
+json :: TypeExpression schema (nullity 'PGjson)
 json = UnsafeTypeExpression "json"
 -- | binary JSON data, decomposed
-jsonb :: TypeExpression schema 'PGjsonb
+jsonb :: TypeExpression schema (nullity 'PGjsonb)
 jsonb = UnsafeTypeExpression "jsonb"
 -- | variable length array
 vararray
   :: TypeExpression schema pg
-  -> TypeExpression schema ('PGvararray pg)
+  -> TypeExpression schema (nullity ('PGvararray pg))
 vararray ty = UnsafeTypeExpression $ renderTypeExpression ty <> "[]"
 -- | fixed length array
 --
--- >>> renderTypeExpression (fixarray (Proxy @2) json)
+-- >>> renderTypeExpression (fixarray @2 json)
 -- "json[2]"
 fixarray
-  :: KnownNat n
-  => proxy n
-  -> TypeExpression schema pg
-  -> TypeExpression schema ('PGfixarray n pg)
-fixarray p ty = UnsafeTypeExpression $
-  renderTypeExpression ty <> "[" <> renderNat p <> "]"
+  :: forall n schema nullity pg. KnownNat n
+  => TypeExpression schema pg
+  -> TypeExpression schema (nullity ('PGfixarray n pg))
+fixarray ty = UnsafeTypeExpression $
+  renderTypeExpression ty <> "[" <> renderNat @n <> "]"
 
 -- | `pgtype` is a demoted version of a `PGType`
-class PGTyped schema (ty :: PGType) where pgtype :: TypeExpression schema ty
-instance PGTyped schema 'PGbool where pgtype = bool
-instance PGTyped schema 'PGint2 where pgtype = int2
-instance PGTyped schema 'PGint4 where pgtype = int4
-instance PGTyped schema 'PGint8 where pgtype = int8
-instance PGTyped schema 'PGnumeric where pgtype = numeric
-instance PGTyped schema 'PGfloat4 where pgtype = float4
-instance PGTyped schema 'PGfloat8 where pgtype = float8
-instance PGTyped schema 'PGtext where pgtype = text
+class PGTyped schema (ty :: NullityType) where
+  pgtype :: TypeExpression schema ty
+instance PGTyped schema (nullity 'PGbool) where pgtype = bool
+instance PGTyped schema (nullity 'PGint2) where pgtype = int2
+instance PGTyped schema (nullity 'PGint4) where pgtype = int4
+instance PGTyped schema (nullity 'PGint8) where pgtype = int8
+instance PGTyped schema (nullity 'PGnumeric) where pgtype = numeric
+instance PGTyped schema (nullity 'PGfloat4) where pgtype = float4
+instance PGTyped schema (nullity 'PGfloat8) where pgtype = float8
+instance PGTyped schema (nullity 'PGtext) where pgtype = text
 instance (KnownNat n, 1 <= n)
-  => PGTyped schema ('PGchar n) where pgtype = char (Proxy @n)
+  => PGTyped schema (nullity ('PGchar n)) where pgtype = char @n
 instance (KnownNat n, 1 <= n)
-  => PGTyped schema ('PGvarchar n) where pgtype = varchar (Proxy @n)
-instance PGTyped schema 'PGbytea where pgtype = bytea
-instance PGTyped schema 'PGtimestamp where pgtype = timestamp
-instance PGTyped schema 'PGtimestamptz where pgtype = timestampWithTimeZone
-instance PGTyped schema 'PGdate where pgtype = date
-instance PGTyped schema 'PGtime where pgtype = time
-instance PGTyped schema 'PGtimetz where pgtype = timeWithTimeZone
-instance PGTyped schema 'PGinterval where pgtype = interval
-instance PGTyped schema 'PGuuid where pgtype = uuid
-instance PGTyped schema 'PGjson where pgtype = json
-instance PGTyped schema 'PGjsonb where pgtype = jsonb
-instance PGTyped schema ty => PGTyped schema ('PGvararray ty) where
-  pgtype = vararray (pgtype @schema @ty)
-instance (KnownNat n, PGTyped schema ty) => PGTyped schema ('PGfixarray n ty) where
-  pgtype = fixarray (Proxy @n) (pgtype @schema @ty)
+  => PGTyped schema (nullity ('PGvarchar n)) where pgtype = varchar @n
+instance PGTyped schema (nullity 'PGbytea) where pgtype = bytea
+instance PGTyped schema (nullity 'PGtimestamp) where pgtype = timestamp
+instance PGTyped schema (nullity 'PGtimestamptz) where pgtype = timestampWithTimeZone
+instance PGTyped schema (nullity 'PGdate) where pgtype = date
+instance PGTyped schema (nullity 'PGtime) where pgtype = time
+instance PGTyped schema (nullity 'PGtimetz) where pgtype = timeWithTimeZone
+instance PGTyped schema (nullity 'PGinterval) where pgtype = interval
+instance PGTyped schema (nullity 'PGuuid) where pgtype = uuid
+instance PGTyped schema (nullity 'PGjson) where pgtype = json
+instance PGTyped schema (nullity 'PGjsonb) where pgtype = jsonb
+instance PGTyped schema ty
+  => PGTyped schema (nullity ('PGvararray ty)) where
+    pgtype = vararray (pgtype @schema @ty)
+instance (KnownNat n, PGTyped schema ty)
+  => PGTyped schema (nullity ('PGfixarray n ty)) where
+    pgtype = fixarray @n (pgtype @schema @ty)
diff --git a/src/Squeal/PostgreSQL/Manipulation.hs b/src/Squeal/PostgreSQL/Manipulation.hs
--- a/src/Squeal/PostgreSQL/Manipulation.hs
+++ b/src/Squeal/PostgreSQL/Manipulation.hs
@@ -42,13 +42,10 @@
     -- * Delete
   , deleteFrom
   , deleteFrom_
-    -- * With
-  , with
   ) where
 
 import Control.DeepSeq
 import Data.ByteString hiding (foldr)
-import Data.Monoid
 
 import qualified Generics.SOP as SOP
 import qualified GHC.Generics as GHC
@@ -180,16 +177,38 @@
 in printSQL manipulation
 :}
 DELETE FROM "tab" WHERE ("col1" = "col2") RETURNING *
+
+with manipulation:
+
+>>> type ProductsTable = '[] :=> '["product" ::: 'NoDef :=> 'NotNull 'PGtext, "date" ::: 'Def :=> 'NotNull 'PGdate]
+
+>>> :{
+let
+  manipulation :: Manipulation
+    '[ "products" ::: 'Table ProductsTable
+     , "products_deleted" ::: 'Table ProductsTable
+     ] '[ 'NotNull 'PGdate] '[]
+  manipulation = with
+    (deleteFrom #products (#date .< param @1) ReturningStar `as` #deleted_rows)
+    (insertQuery_ #products_deleted (selectStar (from (view (#deleted_rows `as` #t)))))
+in printSQL manipulation
+:}
+WITH "deleted_rows" AS (DELETE FROM "products" WHERE ("date" < ($1 :: date)) RETURNING *) INSERT INTO "products_deleted" SELECT * FROM "deleted_rows" AS "t"
 -}
+
 newtype Manipulation
   (schema :: SchemaType)
   (params :: [NullityType])
-  (columns :: RelationType)
+  (columns :: RowType)
     = UnsafeManipulation { renderManipulation :: ByteString }
     deriving (GHC.Generic,Show,Eq,Ord,NFData)
-
 instance RenderSQL (Manipulation schema params columns) where
   renderSQL = renderManipulation
+instance With Manipulation where
+  with Done manip = manip
+  with (cte :>> ctes) manip = UnsafeManipulation $
+    "WITH" <+> renderCommonTableExpressions renderManipulation cte ctes
+    <+> renderManipulation manip
 
 -- | Convert a `Query` into a `Manipulation`.
 queryStatement
@@ -212,13 +231,14 @@
   :: ( SOP.SListI columns
      , SOP.SListI results
      , Has tab schema ('Table table)
+     , row ~ TableToRow table
      , columns ~ TableToColumns table )
   => Alias tab -- ^ table to insert into
   -> NP (Aliased (ColumnValue schema '[] params)) columns -- ^ row to insert
   -> [NP (Aliased (ColumnValue schema '[] params)) columns] -- ^ more rows to insert
-  -> ConflictClause schema columns params
+  -> ConflictClause schema table params
   -- ^ what to do in case of constraint conflict
-  -> ReturningClause schema columns params results -- ^ results to return
+  -> ReturningClause schema params row results -- ^ results to return
   -> Manipulation schema params results
 insertRows tab rw rws conflict returning = UnsafeManipulation $
   "INSERT" <+> "INTO" <+> renderAlias tab
@@ -242,12 +262,13 @@
   :: ( SOP.SListI columns
      , SOP.SListI results
      , Has tab schema ('Table table)
+     , row ~ TableToRow table
      , columns ~ TableToColumns table )
   => Alias tab -- ^ table to insert into
   -> NP (Aliased (ColumnValue schema '[] params)) columns -- ^ row to insert
-  -> ConflictClause schema columns params
+  -> ConflictClause schema table params
   -- ^ what to do in case of constraint conflict
-  -> ReturningClause schema columns params results -- ^ results to return
+  -> ReturningClause schema params row results -- ^ results to return
   -> Manipulation schema params results
 insertRow tab rw = insertRows tab rw []
 
@@ -278,12 +299,13 @@
   :: ( SOP.SListI columns
      , SOP.SListI results
      , Has tab schema ('Table table)
+     , row ~ TableToRow table
      , columns ~ TableToColumns table )
   => Alias tab -- ^ table to insert into
-  -> Query schema params (ColumnsToRelation columns)
-  -> ConflictClause schema columns params
+  -> Query schema params (TableToRow table)
+  -> ConflictClause schema table params
   -- ^ what to do in case of constraint conflict
-  -> ReturningClause schema columns params results -- ^ results to return
+  -> ReturningClause schema params row results -- ^ results to return
   -> Manipulation schema params results
 insertQuery tab query conflict returning = UnsafeManipulation $
   "INSERT" <+> "INTO" <+> renderAlias tab
@@ -297,7 +319,7 @@
      , Has tab schema ('Table table)
      , columns ~ TableToColumns table )
   => Alias tab -- ^ table to insert into
-  -> Query schema params (ColumnsToRelation columns)
+  -> Query schema params (TableToRow table)
   -> Manipulation schema params '[]
 insertQuery_ tab query =
   insertQuery tab query OnConflictDoRaise (Returning Nil)
@@ -309,7 +331,7 @@
 -- existing value in the row for an update.
 data ColumnValue
   (schema :: SchemaType)
-  (columns :: RelationType)
+  (columns :: RowType)
   (params :: [NullityType])
   (ty :: ColumnType)
   where
@@ -331,17 +353,15 @@
 -- values are desired.
 data ReturningClause
   (schema :: SchemaType)
-  (columns :: ColumnsType)
   (params :: [NullityType])
-  (results :: RelationType)
+  (row0 :: RowType)
+  (row1 :: RowType)
   where
     ReturningStar
-      :: results ~ ColumnsToRelation columns
-      => ReturningClause schema columns params results
+      :: ReturningClause schema params row row
     Returning
-      :: rel ~ ColumnsToRelation columns
-      => NP (Aliased (Expression schema '[table ::: rel] 'Ungrouped params)) results
-      -> ReturningClause schema columns params results
+      :: NP (Aliased (Expression schema '[table ::: row0] 'Ungrouped params)) row1
+      -> ReturningClause schema params row0 row1
 
 -- | Render a `ReturningClause`.
 renderReturningClause
@@ -359,18 +379,22 @@
 -- `OnConflictDoNothing` simply avoids inserting a row.
 -- `OnConflictDoUpdate` updates the existing row that conflicts with the row
 -- proposed for insertion.
-data ConflictClause (schema :: SchemaType) (columns :: ColumnsType) params where
-  OnConflictDoRaise :: ConflictClause schema columns params
-  OnConflictDoNothing :: ConflictClause schema columns params
-  OnConflictDoUpdate
-    :: NP (Aliased (ColumnValue schema (ColumnsToRelation columns) params)) columns
-    -> [Condition schema '[table ::: ColumnsToRelation columns] 'Ungrouped params]
-    -> ConflictClause schema columns params
+data ConflictClause
+  (schema :: SchemaType)
+  (table :: TableType)
+  (params :: [NullityType]) where
+    OnConflictDoRaise :: ConflictClause schema table params
+    OnConflictDoNothing :: ConflictClause schema table params
+    OnConflictDoUpdate
+      :: (row ~ TableToRow table, columns ~ TableToColumns table)
+      => NP (Aliased (ColumnValue schema row params)) columns
+      -> [Condition schema '[t ::: row] 'Ungrouped params]
+      -> ConflictClause schema table params
 
 -- | Render a `ConflictClause`.
 renderConflictClause
-  :: SOP.SListI columns
-  => ConflictClause schema columns params
+  :: SOP.SListI (TableToColumns table)
+  => ConflictClause schema table params
   -> ByteString
 renderConflictClause = \case
   OnConflictDoRaise -> ""
@@ -402,13 +426,14 @@
   :: ( SOP.SListI columns
      , SOP.SListI results
      , Has tab schema ('Table table)
+     , row ~ TableToRow table
      , columns ~ TableToColumns table )
   => Alias tab -- ^ table to update
-  -> NP (Aliased (ColumnValue schema (ColumnsToRelation columns) params)) columns
+  -> NP (Aliased (ColumnValue schema row params)) columns
   -- ^ modified values to replace old values
-  -> Condition schema '[tab ::: ColumnsToRelation columns] 'Ungrouped params
+  -> (forall t. Condition schema '[t ::: row] 'Ungrouped params)
   -- ^ condition under which to perform update on a row
-  -> ReturningClause schema columns params results -- ^ results to return
+  -> ReturningClause schema params row results -- ^ results to return
   -> Manipulation schema params results
 update tab columns wh returning = UnsafeManipulation $
   "UPDATE"
@@ -432,11 +457,12 @@
 update_
   :: ( SOP.SListI columns
      , Has tab schema ('Table table)
+     , row ~ TableToRow table
      , columns ~ TableToColumns table )
   => Alias tab -- ^ table to update
-  -> NP (Aliased (ColumnValue schema (ColumnsToRelation columns) params)) columns
+  -> NP (Aliased (ColumnValue schema row params)) columns
   -- ^ modified values to replace old values
-  -> Condition schema '[tab ::: ColumnsToRelation columns] 'Ungrouped params
+  -> (forall t. Condition schema '[t ::: row] 'Ungrouped params)
   -- ^ condition under which to perform update on a row
   -> Manipulation schema params '[]
 update_ tab columns wh = update tab columns wh (Returning Nil)
@@ -449,11 +475,12 @@
 deleteFrom
   :: ( SOP.SListI results
      , Has tab schema ('Table table)
+     , row ~ TableToRow table
      , columns ~ TableToColumns table )
   => Alias tab -- ^ table to delete from
-  -> Condition schema '[tab ::: ColumnsToRelation columns] 'Ungrouped params
+  -> Condition schema '[tab ::: row] 'Ungrouped params
   -- ^ condition under which to delete a row
-  -> ReturningClause schema columns params results -- ^ results to return
+  -> ReturningClause schema params row results -- ^ results to return
   -> Manipulation schema params results
 deleteFrom tab wh returning = UnsafeManipulation $
   "DELETE FROM" <+> renderAlias tab
@@ -463,45 +490,10 @@
 -- | Delete rows returning `Nil`.
 deleteFrom_
   :: ( Has tab schema ('Table table)
+     , row ~ TableToRow table
      , columns ~ TableToColumns table )
   => Alias tab -- ^ table to delete from
-  -> Condition schema '[tab ::: ColumnsToRelation columns] 'Ungrouped params
+  -> (forall t. Condition schema '[t ::: row] 'Ungrouped params)
   -- ^ condition under which to delete a row
   -> Manipulation schema params '[]
 deleteFrom_ tab wh = deleteFrom tab wh (Returning Nil)
-
-{-----------------------------------------
-WITH statements
------------------------------------------}
-
--- | `with` provides a way to write auxiliary statements for use in a larger statement.
--- These statements, which are often referred to as Common Table Expressions or CTEs,
--- can be thought of as defining temporary tables that exist just for one statement.
---
--- >>> type ProductsTable = '[] :=> '["product" ::: 'NoDef :=> 'NotNull 'PGtext, "date" ::: 'Def :=> 'NotNull 'PGdate]
---
--- >>> :{
--- let
---   manipulation :: Manipulation '["products" ::: 'Table ProductsTable, "products_deleted" ::: 'Table ProductsTable] '[ 'NotNull 'PGdate] '[]
---   manipulation = with
---     (deleteFrom #products (#date .< param @1) ReturningStar `as` #deleted_rows)
---     (insertQuery_ #products_deleted (selectStar (from (view (#deleted_rows `as` #t)))))
--- in printSQL manipulation
--- :}
--- WITH "deleted_rows" AS (DELETE FROM "products" WHERE ("date" < ($1 :: date)) RETURNING *) INSERT INTO "products_deleted" SELECT * FROM "deleted_rows" AS "t"
-with
-  :: SOP.SListI commons
-  => NP (Aliased (Manipulation schema params)) (common ': commons)
-  -- ^ common table expressions
-  -> Manipulation (With (common ': commons) schema) params results
-  -> Manipulation schema params results
-with commons manipulation = UnsafeManipulation $
-  "WITH" <+> renderCommaSeparated renderCommon commons
-  <+> renderManipulation manipulation
-  where
-    renderCommon
-      :: Aliased (Manipulation schema params) common
-      -> ByteString
-    renderCommon (common `As` alias) =
-      renderAlias alias <+> "AS" <+>
-        parenthesized (renderManipulation common)
diff --git a/src/Squeal/PostgreSQL/Migration.hs b/src/Squeal/PostgreSQL/Migration.hs
--- a/src/Squeal/PostgreSQL/Migration.hs
+++ b/src/Squeal/PostgreSQL/Migration.hs
@@ -105,9 +105,6 @@
     Migration (..)
   , migrateUp
   , migrateDown
-    -- * Aligned lists
-  , AlignedList (..)
-  , single
     -- * Migration table
   , MigrationsTable
   , createMigrations
@@ -116,15 +113,12 @@
   , selectMigration
   ) where
 
-import Control.Category
 import Control.Monad
 import Control.Monad.Base
 import Control.Monad.Trans.Control
-import Data.Monoid
 import Generics.SOP (K(..))
 import Data.Function ((&))
 import Data.Text (Text)
-import Prelude hiding (id, (.))
 
 import Squeal.PostgreSQL
 
@@ -136,21 +130,6 @@
   , up :: PQ schema0 schema1 io () -- ^ The `up` instruction of a `Migration`.
   , down :: PQ schema1 schema0 io () -- ^ The `down` instruction of a `Migration`.
   }
-
--- | An `AlignedList` is a type-aligned list or free category.
-data AlignedList p x0 x1 where
-  Done :: AlignedList p x x
-  (:>>) :: p x0 x1 -> AlignedList p x1 x2 -> AlignedList p x0 x2
-infixr 7 :>>
-instance Category (AlignedList p) where
-  id = Done
-  (.) list = \case
-    Done -> list
-    step :>> steps -> step :>> (steps >>> list)
-
--- | A `single` step.
-single :: p x0 x1 -> AlignedList p x0 x1
-single step = step :>> Done
 
 -- | Run `Migration`s by creating the `MigrationsTable`
 -- if it does not exist and then in a transaction, for each each `Migration`
diff --git a/src/Squeal/PostgreSQL/PQ.hs b/src/Squeal/PostgreSQL/PQ.hs
--- a/src/Squeal/PostgreSQL/PQ.hs
+++ b/src/Squeal/PostgreSQL/PQ.hs
@@ -50,7 +50,7 @@
   , MonadPQ (..)
   , PQRun
   , pqliftWith
-    -- * Result
+    -- * Results
   , LibPQ.Result
   , LibPQ.Row
   , ntuples
@@ -62,6 +62,11 @@
   , LibPQ.ExecStatus (..)
   , resultStatus
   , resultErrorMessage
+  , resultErrorCode
+    -- * Exceptions
+  , SquealException (..)
+  , catchSqueal
+  , handleSqueal
   ) where
 
 import Control.Exception.Lifted
@@ -73,9 +78,10 @@
 import Data.Foldable
 import Data.Function ((&))
 import Data.Kind
-import Data.Monoid
+import Data.Text (pack, Text)
 import Data.Traversable
 import Generics.SOP
+import PostgreSQL.Binary.Encoding (encodingBytes)
 
 import qualified Database.PostgreSQL.LibPQ as LibPQ
 
@@ -90,7 +96,6 @@
 import Control.Monad.Trans.Reader
 import Control.Monad.Trans.Maybe
 import Control.Monad.Trans.Cont
-import Control.Monad.Trans.List
 
 import qualified Control.Monad.Trans.State.Lazy as Lazy
 import qualified Control.Monad.Trans.State.Strict as Strict
@@ -269,7 +274,7 @@
   define (UnsafeDefinition q) = PQ $ \ (K conn) -> do
     resultMaybe <- liftBase $ LibPQ.exec conn q
     case resultMaybe of
-      Nothing -> error
+      Nothing -> throw $ ResultException
         "define: LibPQ.exec returned no results"
       Just result -> return $ K (K result)
 
@@ -393,14 +398,17 @@
     (UnsafeManipulation q :: Manipulation schema ps ys) (params :: x) =
       PQ $ \ (K conn) -> do
         let
-          toParam' bytes = (LibPQ.invalidOid,bytes,LibPQ.Binary)
+          toParam' encoding =
+            (LibPQ.invalidOid, encodingBytes encoding, LibPQ.Binary)
           params' = fmap (fmap toParam') (hcollapse (toParams @x @ps params))
           q' = q <> ";"
         resultMaybe <- liftBase $ LibPQ.execParams conn q' params' LibPQ.Binary
         case resultMaybe of
-          Nothing -> error
+          Nothing -> throw $ ResultException
             "manipulateParams: LibPQ.execParams returned no results"
-          Just result -> return $ K (K result)
+          Just result -> do
+            tryResult result
+            return $ K (K result)
 
   traversePrepared
     (UnsafeManipulation q :: Manipulation schema xs ys) (list :: list x) =
@@ -408,29 +416,25 @@
         let temp = "temporary_statement"
         prepResultMaybe <- LibPQ.prepare conn temp q Nothing
         case prepResultMaybe of
-          Nothing -> error
+          Nothing -> throw $ ResultException
             "traversePrepared: LibPQ.prepare returned no results"
-          Just prepResult -> do
-            status <- LibPQ.resultStatus prepResult
-            unless (status == LibPQ.CommandOk) . error $
-              "traversePrepared: LibPQ.prepare status " <> show status
+          Just prepResult -> tryResult prepResult
         results <- for list $ \ params -> do
           let
-            toParam' bytes = (bytes,LibPQ.Binary)
+            toParam' encoding = (encodingBytes encoding,LibPQ.Binary)
             params' = fmap (fmap toParam') (hcollapse (toParams @x @xs params))
           resultMaybe <- LibPQ.execPrepared conn temp params' LibPQ.Binary
           case resultMaybe of
-            Nothing -> error
+            Nothing -> throw $ ResultException
               "traversePrepared: LibPQ.execParams returned no results"
-            Just result -> return $ K result
+            Just result -> do
+              tryResult result
+              return $ K result
         deallocResultMaybe <- LibPQ.exec conn ("DEALLOCATE " <> temp <> ";")
         case deallocResultMaybe of
-          Nothing -> error
+          Nothing -> throw $ ResultException
             "traversePrepared: LibPQ.exec DEALLOCATE returned no results"
-          Just deallocResult -> do
-            status <- LibPQ.resultStatus deallocResult
-            unless (status == LibPQ.CommandOk) . error $
-              "traversePrepared: DEALLOCATE status " <> show status
+          Just deallocResult -> tryResult deallocResult
         return (K results)
 
   traversePrepared_
@@ -439,29 +443,23 @@
         let temp = "temporary_statement"
         prepResultMaybe <- LibPQ.prepare conn temp q Nothing
         case prepResultMaybe of
-          Nothing -> error
+          Nothing -> throw $ ResultException
             "traversePrepared_: LibPQ.prepare returned no results"
-          Just prepResult -> do
-            status <- LibPQ.resultStatus prepResult
-            unless (status == LibPQ.CommandOk) . error $
-              "traversePrepared: LibPQ.prepare status " <> show status
+          Just prepResult -> tryResult prepResult
         for_ list $ \ params -> do
           let
-            toParam' bytes = (bytes,LibPQ.Binary)
+            toParam' encoding = (encodingBytes encoding, LibPQ.Binary)
             params' = fmap (fmap toParam') (hcollapse (toParams @x @xs params))
           resultMaybe <- LibPQ.execPrepared conn temp params' LibPQ.Binary
           case resultMaybe of
-            Nothing -> error
+            Nothing -> throw $ ResultException
               "traversePrepared_: LibPQ.execParams returned no results"
-            Just _result -> return ()
+            Just result -> tryResult result
         deallocResultMaybe <- LibPQ.exec conn ("DEALLOCATE " <> temp <> ";")
         case deallocResultMaybe of
-          Nothing -> error
+          Nothing -> throw $ ResultException
             "traversePrepared: LibPQ.exec DEALLOCATE returned no results"
-          Just deallocResult -> do
-            status <- LibPQ.resultStatus deallocResult
-            unless (status == LibPQ.CommandOk) . error $
-              "traversePrepared: DEALLOCATE status " <> show status
+          Just deallocResult -> tryResult deallocResult
         return (K ())
 
   liftPQ pq = PQ $ \ (K conn) -> do
@@ -479,7 +477,6 @@
 instance (Monoid w, MonadPQ schema m) => MonadPQ schema (Strict.RWST r w s m)
 instance (Monoid w, MonadPQ schema m) => MonadPQ schema (Lazy.RWST r w s m)
 instance MonadPQ schema m => MonadPQ schema (ContT r m)
-instance MonadPQ schema m => MonadPQ schema (ListT m)
 
 instance (Monad m, schema0 ~ schema1)
   => Applicative (PQ schema0 schema1 m) where
@@ -534,14 +531,16 @@
   -> io y
 getRow r (K result :: K LibPQ.Result columns) = liftBase $ do
   numRows <- LibPQ.ntuples result
-  when (numRows < r) $ error $
-    "getRow: expected at least " <> show r <> "rows but only saw "
-    <> show numRows
+  when (numRows < r) $ throw $ ResultException $
+    "getRow: expected at least " <> pack (show r) <> "rows but only saw "
+    <> pack (show numRows)
   let len = fromIntegral (lengthSList (Proxy @columns))
   row' <- traverse (LibPQ.getvalue result r) [0 .. len - 1]
   case fromList row' of
-    Nothing -> error "getRow: found unexpected length"
-    Just row -> return $ fromRow @columns row
+    Nothing -> throw $ ResultException "getRow: found unexpected length"
+    Just row -> case fromRow @columns row of
+      Left parseError -> throw $ ParseException $ "getRow: " <> parseError
+      Right y -> return y
 
 -- | Intended to be used for unfolding in streaming libraries, `nextRow`
 -- takes a total number of rows (which can be found with `ntuples`)
@@ -558,8 +557,10 @@
     let len = fromIntegral (lengthSList (Proxy @columns))
     row' <- traverse (LibPQ.getvalue result r) [0 .. len - 1]
     case fromList row' of
-      Nothing -> error "nextRow: found unexpected length"
-      Just row -> return $ Just (r+1, fromRow @columns row)
+      Nothing -> throw $ ResultException "nextRow: found unexpected length"
+      Just row -> case fromRow @columns row of
+        Left parseError -> throw $ ParseException $ "nextRow: " <> parseError
+        Right y -> return $ Just (r+1, y)
 
 -- | Get all rows from a `LibPQ.Result`.
 getRows
@@ -572,8 +573,10 @@
   for [0 .. numRows - 1] $ \ r -> do
     row' <- traverse (LibPQ.getvalue result r) [0 .. len - 1]
     case fromList row' of
-      Nothing -> error "getRows: found unexpected length"
-      Just row -> return $ fromRow @columns row
+      Nothing -> throw $ ResultException "getRows: found unexpected length"
+      Just row -> case fromRow @columns row of
+        Left parseError -> throw $ ParseException $ "getRows: " <> parseError
+        Right y -> return y
 
 -- | Get the first row if possible from a `LibPQ.Result`.
 firstRow
@@ -586,8 +589,10 @@
     let len = fromIntegral (lengthSList (Proxy @columns))
     row' <- traverse (LibPQ.getvalue result 0) [0 .. len - 1]
     case fromList row' of
-      Nothing -> error "firstRow: found unexpected length"
-      Just row -> return . Just $ fromRow @columns row
+      Nothing -> throw $ ResultException "firstRow: found unexpected length"
+      Just row -> case fromRow @columns row of
+        Left parseError -> throw $ ParseException $ "firstRow: " <> parseError
+        Right y -> return $ Just y
 
 -- | Lifts actions on results from @LibPQ@.
 liftResult
@@ -609,3 +614,55 @@
 resultErrorMessage
   :: MonadBase IO io => K LibPQ.Result results -> io (Maybe ByteString)
 resultErrorMessage = liftResult LibPQ.resultErrorMessage
+
+-- | Returns the error code most recently generated by an operation
+-- on the connection.
+--
+-- https://www.postgresql.org/docs/current/static/errcodes-appendix.html
+resultErrorCode
+  :: MonadBase IO io
+  => K LibPQ.Result results
+  -> io (Maybe ByteString)
+resultErrorCode = liftResult (flip LibPQ.resultErrorField LibPQ.DiagSqlstate)
+
+-- | `Exception`s that can be thrown by Squeal.
+data SquealException
+  = PQException
+  { sqlExecStatus :: LibPQ.ExecStatus
+  , sqlStateCode :: Maybe ByteString
+    -- ^ https://www.postgresql.org/docs/current/static/errcodes-appendix.html
+  , sqlErrorMessage :: Maybe ByteString
+  }
+  | ResultException Text
+  | ParseException Text
+  deriving (Eq, Show)
+instance Exception SquealException
+
+tryResult
+  :: MonadBase IO io
+  => LibPQ.Result
+  -> io ()
+tryResult result = liftBase $ do
+  status <- LibPQ.resultStatus result
+  case status of
+    LibPQ.CommandOk -> return ()
+    LibPQ.TuplesOk -> return ()
+    _ -> do
+      stateCode <- LibPQ.resultErrorField result LibPQ.DiagSqlstate
+      msg <- LibPQ.resultErrorMessage result
+      throw $ PQException status stateCode msg
+
+-- | Catch `SquealException`s.
+catchSqueal
+  :: MonadBaseControl IO io
+  => io a
+  -> (SquealException -> io a) -- ^ handler
+  -> io a
+catchSqueal = catch
+
+-- | Handle `SquealException`s.
+handleSqueal
+  :: MonadBaseControl IO io
+  => (SquealException -> io a) -- ^ handler
+  -> io a -> io a
+handleSqueal = handle
diff --git a/src/Squeal/PostgreSQL/Query.hs b/src/Squeal/PostgreSQL/Query.hs
--- a/src/Squeal/PostgreSQL/Query.hs
+++ b/src/Squeal/PostgreSQL/Query.hs
@@ -29,21 +29,43 @@
 module Squeal.PostgreSQL.Query
   ( -- * Queries
     Query (UnsafeQuery, renderQuery)
-  , union
-  , unionAll
-  , intersect
-  , intersectAll
-  , except
-  , exceptAll
-    -- * Select
+    -- ** Select
   , select
   , selectDistinct
   , selectStar
   , selectDistinctStar
   , selectDotStar
   , selectDistinctDotStar
+    -- ** Values
   , values
   , values_
+    -- ** Set Operations
+  , union
+  , unionAll
+  , intersect
+  , intersectAll
+  , except
+  , exceptAll
+    -- ** With
+  , With (with)
+  , CommonTableExpression (..)
+  , renderCommonTableExpression
+  , renderCommonTableExpressions
+    -- ** Json
+  , jsonEach
+  , jsonbEach
+  , jsonEachAsText
+  , jsonbEachAsText
+  , jsonObjectKeys
+  , jsonbObjectKeys
+  , jsonPopulateRecord
+  , jsonbPopulateRecord
+  , jsonPopulateRecordSet
+  , jsonbPopulateRecordSet
+  , jsonToRecord
+  , jsonbToRecord
+  , jsonToRecordSet
+  , jsonbToRecordSet
     -- * Table Expressions
   , TableExpression (..)
   , renderTableExpression
@@ -54,22 +76,7 @@
   , orderBy
   , limit
   , offset
-    -- ** JSON table expressions
-  , PGjson_each
-  , PGjsonb_each
-  , jsonEach
-  , jsonbEach
-  , jsonEachAsText
-  , jsonbEachAsText
-  , jsonPopulateRecordAs
-  , jsonbPopulateRecordAs
-  , jsonPopulateRecordSetAs
-  , jsonbPopulateRecordSetAs
-  , jsonToRecordAs
-  , jsonbToRecordAs
-  , jsonToRecordSetAs
-  , jsonbToRecordSetAs
-    -- * From
+    -- * From Clauses
   , FromClause (..)
   , table
   , subquery
@@ -89,11 +96,37 @@
     -- * Sorting
   , SortExpression (..)
   , renderSortExpression
+    -- * Subquery Expressions
+  , in_
+  , rowIn
+  , eqAll
+  , rowEqAll
+  , eqAny
+  , rowEqAny
+  , neqAll
+  , rowNeqAll
+  , neqAny
+  , rowNeqAny
+  , allLt
+  , rowLtAll
+  , ltAny
+  , rowLtAny
+  , lteAll
+  , rowLteAll
+  , lteAny
+  , rowLteAny
+  , gtAll
+  , rowGtAll
+  , gtAny
+  , rowGtAny
+  , gteAll
+  , rowGteAll
+  , gteAny
+  , rowGteAny
   ) where
 
 import Control.DeepSeq
 import Data.ByteString (ByteString)
-import Data.Monoid hiding (All)
 import Data.String
 import Data.Word
 import Generics.SOP hiding (from)
@@ -107,9 +140,7 @@
 
 {- |
 The process of retrieving or the command to retrieve data from a database
-is called a `Query`. The `select`, `selectStar`, `selectDotStar`,
-`selectDistinct`, `selectDistinctStar` and `selectDistinctDotStar` commands
-are used to specify queries.
+is called a `Query`. Let's see some examples of queries.
 
 simple query:
 
@@ -280,6 +311,16 @@
 :}
 SELECT "t1".* FROM "tab" AS "t1" CROSS JOIN "tab" AS "t2"
 
+value queries:
+
+>>> :{
+let
+  query :: Query '[] '[] '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
+  query = values (1 `as` #foo :* true `as` #bar) [2 `as` #foo :* false `as` #bar]
+in printSQL query
+:}
+SELECT * FROM (VALUES (1, TRUE), (2, FALSE)) AS t ("foo", "bar")
+
 set operations:
 
 >>> :{
@@ -295,11 +336,30 @@
 in printSQL query
 :}
 (SELECT * FROM "tab" AS "tab") UNION ALL (SELECT * FROM "tab" AS "tab")
+
+with queries:
+
+>>> :{
+let
+  query :: Query
+    '[ "t1" ::: 'View
+       '[ "c1" ::: 'NotNull 'PGtext
+        , "c2" ::: 'NotNull 'PGtext] ]
+    '[]
+    '[ "c1" ::: 'NotNull 'PGtext
+     , "c2" ::: 'NotNull 'PGtext ]
+  query = with (
+    selectStar (from (view #t1)) `as` #t2 :>>
+    selectStar (from (view #t2)) `as` #t3
+    ) (selectStar (from (view #t3)))
+in printSQL query
+:}
+WITH "t2" AS (SELECT * FROM "t1" AS "t1"), "t3" AS (SELECT * FROM "t2" AS "t2") SELECT * FROM "t3" AS "t3"
 -}
 newtype Query
   (schema :: SchemaType)
   (params :: [NullityType])
-  (columns :: RelationType)
+  (columns :: RowType)
     = UnsafeQuery { renderQuery :: ByteString }
     deriving (GHC.Generic,Show,Eq,Ord,NFData)
 instance RenderSQL (Query schema params columns) where renderSQL = renderQuery
@@ -381,9 +441,9 @@
 -- the intermediate table are actually output.
 select
   :: SListI columns
-  => NP (Aliased (Expression schema relations grouping params)) (column ': columns)
+  => NP (Aliased (Expression schema from grouping params)) (column ': columns)
   -- ^ select list
-  -> TableExpression schema params relations grouping
+  -> TableExpression schema params from grouping
   -- ^ intermediate virtual table
   -> Query schema params (column ': columns)
 select list rels = UnsafeQuery $
@@ -395,9 +455,9 @@
 -- be subject to the elimination of duplicate rows using `selectDistinct`.
 selectDistinct
   :: SListI columns
-  => NP (Aliased (Expression schema relations 'Ungrouped params)) (column ': columns)
+  => NP (Aliased (Expression schema from 'Ungrouped params)) (column ': columns)
   -- ^ select list
-  -> TableExpression schema params relations 'Ungrouped
+  -> TableExpression schema params from 'Ungrouped
   -- ^ intermediate virtual table
   -> Query schema params (column ': columns)
 selectDistinct list rels = UnsafeQuery $
@@ -408,8 +468,8 @@
 -- | The simplest kind of query is `selectStar` which emits all columns
 -- that the table expression produces.
 selectStar
-  :: HasUnique relation relations columns
-  => TableExpression schema params relations 'Ungrouped
+  :: HasUnique table from columns
+  => TableExpression schema params from 'Ungrouped
   -- ^ intermediate virtual table
   -> Query schema params columns
 selectStar rels = UnsafeQuery $ "SELECT" <+> "*" <+> renderTableExpression rels
@@ -417,8 +477,8 @@
 -- | A `selectDistinctStar` emits all columns that the table expression
 -- produces and eliminates duplicate rows.
 selectDistinctStar
-  :: HasUnique relation relations columns
-  => TableExpression schema params relations 'Ungrouped
+  :: HasUnique table from columns
+  => TableExpression schema params from 'Ungrouped
   -- ^ intermediate virtual table
   -> Query schema params columns
 selectDistinctStar rels = UnsafeQuery $
@@ -427,27 +487,27 @@
 -- | When working with multiple tables, it can also be useful to ask
 -- for all the columns of a particular table, using `selectDotStar`.
 selectDotStar
-  :: Has relation relations columns
-  => Alias relation
+  :: Has table from columns
+  => Alias table
   -- ^ particular virtual subtable
-  -> TableExpression schema params relations 'Ungrouped
+  -> TableExpression schema params from 'Ungrouped
   -- ^ intermediate virtual table
   -> Query schema params columns
-selectDotStar rel relations = UnsafeQuery $
-  "SELECT" <+> renderAlias rel <> ".*" <+> renderTableExpression relations
+selectDotStar rel tab = UnsafeQuery $
+  "SELECT" <+> renderAlias rel <> ".*" <+> renderTableExpression tab
 
 -- | A `selectDistinctDotStar` asks for all the columns of a particular table,
 -- and eliminates duplicate rows.
 selectDistinctDotStar
-  :: Has relation relations columns
-  => Alias relation
+  :: Has table from columns
+  => Alias table
   -- ^ particular virtual table
-  -> TableExpression schema params relations 'Ungrouped
+  -> TableExpression schema params from 'Ungrouped
   -- ^ intermediate virtual table
   -> Query schema params columns
-selectDistinctDotStar rel relations = UnsafeQuery $
+selectDistinctDotStar rel tab = UnsafeQuery $
   "SELECT DISTINCT" <+> renderAlias rel <> ".*"
-  <+> renderTableExpression relations
+  <+> renderTableExpression tab
 
 -- | `values` computes a row value or set of row values
 -- specified by value expressions. It is most commonly used
@@ -501,13 +561,13 @@
 data TableExpression
   (schema :: SchemaType)
   (params :: [NullityType])
-  (relations :: RelationsType)
+  (from :: FromType)
   (grouping :: Grouping)
     = TableExpression
-    { fromClause :: FromClause schema params relations
+    { fromClause :: FromClause schema params from
     -- ^ A table reference that can be a table name, or a derived table such
     -- as a subquery, a @JOIN@ construct, or complex combinations of these.
-    , whereClause :: [Condition schema relations 'Ungrouped params]
+    , whereClause :: [Condition schema from 'Ungrouped params]
     -- ^ optional search coditions, combined with `.&&`. After the processing
     -- of the `fromClause` is done, each row of the derived virtual table
     -- is checked against the search condition. If the result of the
@@ -516,20 +576,20 @@
     -- at least one column of the table generated in the `fromClause`;
     -- this is not required, but otherwise the WHERE clause will
     -- be fairly useless.
-    , groupByClause :: GroupByClause relations grouping
+    , groupByClause :: GroupByClause from grouping
     -- ^ The `groupByClause` is used to group together those rows in a table
     -- that have the same values in all the columns listed. The order in which
     -- the columns are listed does not matter. The effect is to combine each
     -- set of rows having common values into one group row that represents all
     -- rows in the group. This is done to eliminate redundancy in the output
     -- and/or compute aggregates that apply to these groups.
-    , havingClause :: HavingClause schema relations grouping params
+    , havingClause :: HavingClause schema from grouping params
     -- ^ If a table has been grouped using `groupBy`, but only certain groups
     -- are of interest, the `havingClause` can be used, much like a
     -- `whereClause`, to eliminate groups from the result. Expressions in the
     -- `havingClause` can refer both to grouped expressions and to ungrouped
     -- expressions (which necessarily involve an aggregate function).
-    , orderByClause :: [SortExpression schema relations grouping params]
+    , orderByClause :: [SortExpression schema from grouping params]
     -- ^ The `orderByClause` is for optional sorting. When more than one
     -- `SortExpression` is specified, the later (right) values are used to sort
     -- rows that are equal according to the earlier (left) values.
@@ -547,7 +607,7 @@
 
 -- | Render a `TableExpression`
 renderTableExpression
-  :: TableExpression schema params relations grouping
+  :: TableExpression schema params from grouping
   -> ByteString
 renderTableExpression
   (TableExpression frm' whs' grps' hvs' srts' lims' offs') = mconcat
@@ -581,16 +641,16 @@
 -- `group`, `having`, `orderBy`, `limit` and `offset`, using the `&` operator
 -- to match the left-to-right sequencing of their placement in SQL.
 from
-  :: FromClause schema params relations -- ^ table reference
-  -> TableExpression schema params relations 'Ungrouped
+  :: FromClause schema params from -- ^ table reference
+  -> TableExpression schema params from 'Ungrouped
 from rels = TableExpression rels [] NoGroups NoHaving [] [] []
 
 -- | A `where_` is an endomorphism of `TableExpression`s which adds a
 -- search condition to the `whereClause`.
 where_
-  :: Condition schema relations 'Ungrouped params -- ^ filtering condition
-  -> TableExpression schema params relations grouping
-  -> TableExpression schema params relations grouping
+  :: Condition schema from 'Ungrouped params -- ^ filtering condition
+  -> TableExpression schema params from grouping
+  -> TableExpression schema params from grouping
 where_ wh rels = rels {whereClause = wh : whereClause rels}
 
 -- | A `groupBy` is a transformation of `TableExpression`s which switches
@@ -598,9 +658,9 @@
 -- a "grand total" aggregation query.
 groupBy
   :: SListI bys
-  => NP (By relations) bys -- ^ grouped columns
-  -> TableExpression schema params relations 'Ungrouped
-  -> TableExpression schema params relations ('Grouped bys)
+  => NP (By from) bys -- ^ grouped columns
+  -> TableExpression schema params from 'Ungrouped
+  -> TableExpression schema params from ('Grouped bys)
 groupBy bys rels = TableExpression
   { fromClause = fromClause rels
   , whereClause = whereClause rels
@@ -614,218 +674,176 @@
 -- | A `having` is an endomorphism of `TableExpression`s which adds a
 -- search condition to the `havingClause`.
 having
-  :: Condition schema relations ('Grouped bys) params -- ^ having condition
-  -> TableExpression schema params relations ('Grouped bys)
-  -> TableExpression schema params relations ('Grouped bys)
+  :: Condition schema from ('Grouped bys) params -- ^ having condition
+  -> TableExpression schema params from ('Grouped bys)
+  -> TableExpression schema params from ('Grouped bys)
 having hv rels = rels
   { havingClause = case havingClause rels of Having hvs -> Having (hv:hvs) }
 
 -- | An `orderBy` is an endomorphism of `TableExpression`s which appends an
 -- ordering to the right of the `orderByClause`.
 orderBy
-  :: [SortExpression schema relations grouping params] -- ^ sort expressions
-  -> TableExpression schema params relations grouping
-  -> TableExpression schema params relations grouping
+  :: [SortExpression schema from grouping params] -- ^ sort expressions
+  -> TableExpression schema params from grouping
+  -> TableExpression schema params from grouping
 orderBy srts rels = rels {orderByClause = orderByClause rels ++ srts}
 
 -- | A `limit` is an endomorphism of `TableExpression`s which adds to the
 -- `limitClause`.
 limit
   :: Word64 -- ^ limit parameter
-  -> TableExpression schema params relations grouping
-  -> TableExpression schema params relations grouping
+  -> TableExpression schema params from grouping
+  -> TableExpression schema params from grouping
 limit lim rels = rels {limitClause = lim : limitClause rels}
 
 -- | An `offset` is an endomorphism of `TableExpression`s which adds to the
 -- `offsetClause`.
 offset
   :: Word64 -- ^ offset parameter
-  -> TableExpression schema params relations grouping
-  -> TableExpression schema params relations grouping
+  -> TableExpression schema params from grouping
+  -> TableExpression schema params from grouping
 offset off rels = rels {offsetClause = off : offsetClause rels}
 
 {-----------------------------------------
 JSON stuff
 -----------------------------------------}
 
-type PGjson_each_variant val tab =
-  '[ tab ::: '[ "key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull val ] ]
-
--- | The type of a `json_each` `FromClause`.
-type PGjson_each tab = PGjson_each_variant 'PGjson tab
--- | The type of a `jsonb_each` `FromClause`.
-type PGjsonb_each tab = PGjson_each_variant 'PGjsonb tab
--- | The type of a `json_each_text` `FromClause`.
-type PGjson_each_text tab = PGjson_each_variant 'PGtext tab
--- | The type of a `jsonb_each_text` `FromClause`.
-type PGjsonb_each_text tab = PGjson_each_variant 'PGtext tab
-
-unsafeAliasedFromClauseExpression
-  :: Aliased (Expression schema' relations' grouping' params') ty'
-  -> FromClause schema params relations
-unsafeAliasedFromClauseExpression aliasedExpr = UnsafeFromClause
-  (renderAliasedAs renderExpression aliasedExpr)
+unsafeSetOfFunction
+  :: ByteString
+  -> Expression schema '[] 'Ungrouped params ty
+  -> Query schema params row
+unsafeSetOfFunction fun expr = UnsafeQuery $
+  "SELECT * FROM " <> fun <> "(" <> renderExpression expr <> ")"
 
 -- | Expands the outermost JSON object into a set of key/value pairs.
 jsonEach
-  :: Aliased (Expression schema '[] 'Ungrouped params) '(tab, nullity 'PGjson)
-  -> FromClause schema params (PGjson_each tab)
-jsonEach (As jexpr jname) = unsafeAliasedFromClauseExpression
-  (As (unsafeFunction "json_each" jexpr) jname)
+  :: Expression schema '[] 'Ungrouped params (nullity 'PGjson) -- ^ json object
+  -> Query schema params
+      '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGjson]
+jsonEach = unsafeSetOfFunction "json_each"
 
 -- | Expands the outermost binary JSON object into a set of key/value pairs.
 jsonbEach
-  :: Aliased (Expression schema '[] 'Ungrouped params) '(tab, nullity 'PGjsonb)
-  -> FromClause schema params (PGjsonb_each tab)
-jsonbEach (As jexpr jname) = unsafeAliasedFromClauseExpression
-  (As (unsafeFunction "jsonb_each" jexpr) jname)
+  :: Expression schema '[] 'Ungrouped params (nullity 'PGjsonb) -- ^ jsonb object
+  -> Query schema params
+      '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGjsonb]
+jsonbEach = unsafeSetOfFunction "jsonb_each"
 
 -- | Expands the outermost JSON object into a set of key/value pairs.
 jsonEachAsText
-  :: Aliased (Expression schema '[] 'Ungrouped params) '(tab, nullity 'PGjson)
-  -> FromClause schema params (PGjson_each_text tab)
-jsonEachAsText (As jexpr jname) = unsafeAliasedFromClauseExpression
-  (As (unsafeFunction "json_each" jexpr) jname)
+  :: Expression schema '[] 'Ungrouped params (nullity 'PGjson) -- ^ json object
+  -> Query schema params
+      '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGtext]
+jsonEachAsText = unsafeSetOfFunction "json_each_text"
 
 -- | Expands the outermost binary JSON object into a set of key/value pairs.
 jsonbEachAsText
-  :: Aliased (Expression schema '[] 'Ungrouped params) '(tab, nullity 'PGjsonb)
-  -> FromClause schema params (PGjsonb_each_text tab)
-jsonbEachAsText (As jexpr jname) = unsafeAliasedFromClauseExpression
-  (As (unsafeFunction "jsonb_each" jexpr) jname)
+  :: Expression schema '[] 'Ungrouped params (nullity 'PGjsonb) -- ^ jsonb object
+  -> Query schema params
+    '["key" ::: 'NotNull 'PGtext, "value" ::: 'NotNull 'PGtext]
+jsonbEachAsText = unsafeSetOfFunction "jsonb_each_text"
 
-nullRow
-  :: Has tab schema ('Table table)
-  => Alias tab
-  -> Expression schema relations grouping params
-     (nullity ('PGcomposite (RelationToRowType (TableToRelation table))))
-nullRow tab = UnsafeExpression ("null::"<+>renderAlias tab)
+-- | Returns set of keys in the outermost JSON object.
+jsonObjectKeys
+  :: Expression schema '[] 'Ungrouped params (nullity 'PGjson) -- ^ json object
+  -> Query schema params '["json_object_keys" ::: 'NotNull 'PGtext]
+jsonObjectKeys = unsafeSetOfFunction "json_object_keys"
 
+-- | Returns set of keys in the outermost JSON object.
+jsonbObjectKeys
+  :: Expression schema '[] 'Ungrouped params (nullity 'PGjsonb) -- ^ jsonb object
+  -> Query schema params '["jsonb_object_keys" ::: 'NotNull 'PGtext]
+jsonbObjectKeys = unsafeSetOfFunction "jsonb_object_keys"
+
+unsafePopulateFunction
+  :: ByteString
+  -> TypeExpression schema (nullity ('PGcomposite row))
+  -> Expression schema '[] 'Ungrouped params ty
+  -> Query schema params row
+unsafePopulateFunction fun ty expr = UnsafeQuery $
+  "SELECT * FROM " <> fun <> "("
+    <> "null::" <> renderTypeExpression ty <> ", "
+    <> renderExpression expr <> ")"
+
 -- | Expands the JSON expression to a row whose columns match the record
 -- type defined by the given table.
-jsonPopulateRecordAs
-  :: (KnownSymbol alias, Has tab schema ('Table table))
-  => Alias tab
-  -> Expression schema '[] 'Ungrouped params (nullity 'PGjson)
-  -> Alias alias
-  -> FromClause schema params '[alias ::: TableToRelation table]
-jsonPopulateRecordAs tableName expr alias = unsafeAliasedFromClauseExpression
-  (unsafeVariadicFunction "json_populate_record"
-   (nullRow tableName :* expr :* Nil) `As` alias)
+jsonPopulateRecord
+  :: TypeExpression schema (nullity ('PGcomposite row)) -- ^ row type
+  -> Expression schema '[] 'Ungrouped params (nullity 'PGjson) -- ^ json object
+  -> Query schema params row
+jsonPopulateRecord = unsafePopulateFunction "json_populate_record"
 
 -- | Expands the binary JSON expression to a row whose columns match the record
 -- type defined by the given table.
-jsonbPopulateRecordAs
-  :: (KnownSymbol alias, Has tab schema ('Table table))
-  => Alias tab
-  -> Expression schema '[] 'Ungrouped params (nullity 'PGjsonb)
-  -> Alias alias
-  -> FromClause schema params '[alias ::: TableToRelation table]
-jsonbPopulateRecordAs tableName expr alias = unsafeAliasedFromClauseExpression
-  (unsafeVariadicFunction "jsonb_populate_record"
-   (nullRow tableName :* expr :* Nil) `As` alias)
+jsonbPopulateRecord
+  :: TypeExpression schema (nullity ('PGcomposite row)) -- ^ row type
+  -> Expression schema '[] 'Ungrouped params (nullity 'PGjsonb) -- ^ jsonb object
+  -> Query schema params row
+jsonbPopulateRecord = unsafePopulateFunction "jsonb_populate_record"
 
 -- | Expands the outermost array of objects in the given JSON expression to a
 -- set of rows whose columns match the record type defined by the given table.
-jsonPopulateRecordSetAs
-  :: (KnownSymbol alias, Has tab schema ('Table table))
-  => Alias tab
-  -> Expression schema '[] 'Ungrouped params (nullity 'PGjson)
-  -> Alias alias
-  -> FromClause schema params '[alias ::: TableToRelation table]
-jsonPopulateRecordSetAs tableName expr alias = unsafeAliasedFromClauseExpression
-  (unsafeVariadicFunction "json_populate_record_set"
-   (nullRow tableName :* expr :* Nil) `As` alias)
+jsonPopulateRecordSet
+  :: TypeExpression schema (nullity ('PGcomposite row)) -- ^ row type
+  -> Expression schema '[] 'Ungrouped params (nullity 'PGjson) -- ^ json array
+  -> Query schema params row
+jsonPopulateRecordSet = unsafePopulateFunction "json_populate_record_set"
 
 -- | Expands the outermost array of objects in the given binary JSON expression
 -- to a set of rows whose columns match the record type defined by the given
 -- table.
-jsonbPopulateRecordSetAs
-  :: (KnownSymbol alias, Has tab schema ('Table table))
-  => Alias tab
-  -> Expression schema '[] 'Ungrouped params (nullity 'PGjsonb)
-  -> Alias alias
-  -> FromClause schema params '[alias ::: TableToRelation table]
-jsonbPopulateRecordSetAs tableName expr alias = unsafeAliasedFromClauseExpression
-  (unsafeVariadicFunction "jsonb_populate_record_set"
-   (nullRow tableName :* expr :* Nil) `As` alias)
-
-renderTableTypeExpression
-  :: All Top types
-  => Aliased (NP (Aliased (TypeExpression schema))) (tab ::: types)
-  -> ByteString
-renderTableTypeExpression (hc `As` tab)
-  = (renderAlias tab <>)
-  . parenthesized
-  . commaSeparated
-  . flip appEndo []
-  . hcfoldMap
-    (Proxy :: Proxy Top)
-    (\(ty `As` name) -> Endo
-      ((renderAlias name <+> renderTypeExpression ty):))
-  $ hc
+jsonbPopulateRecordSet
+  :: TypeExpression schema (nullity ('PGcomposite row)) -- ^ row type
+  -> Expression schema '[] 'Ungrouped params (nullity 'PGjsonb) -- ^ jsonb array
+  -> Query schema params row
+jsonbPopulateRecordSet = unsafePopulateFunction "jsonb_populate_record_set"
 
-unsafeTableAliasedFromClauseExpression
-  :: All Top types
-  => Expression schema' relations' grouping' params' ty'
-  -> Aliased (NP (Aliased (TypeExpression schema))) (tab ::: types)
-  -> FromClause schema params '[alias ::: types]
-unsafeTableAliasedFromClauseExpression expr types = UnsafeFromClause
-  (renderExpression expr
-   <+> "AS"
-   <+> renderTableTypeExpression types)
+unsafeRecordFunction
+  :: (SListI record, json `In` PGJsonType)
+  => ByteString
+  -> Expression schema '[] 'Ungrouped params (nullity json)
+  -> NP (Aliased (TypeExpression schema)) record
+  -> Query schema params record
+unsafeRecordFunction fun expr types = UnsafeQuery $
+  "SELECT * FROM " <> fun <> "("
+    <> renderExpression expr <> ")"
+    <+> "AS" <+> "x" <> parenthesized (renderCommaSeparated renderTy types)
+    where
+      renderTy :: Aliased (TypeExpression schema) ty -> ByteString
+      renderTy (ty `As` alias) =
+        renderAlias alias <+> renderTypeExpression ty
 
--- | Builds an arbitrary record from a JSON object. As with all functions
--- returning record, the caller must explicitly define the structure of the
--- record with an AS clause.
-jsonToRecordAs
-  :: All Top types
-  => Expression schema '[] 'Ungrouped params (nullity 'PGjson)
-  -> Aliased (NP (Aliased (TypeExpression schema))) (tab ::: types)
-  -> FromClause schema params '[tab ::: types]
-jsonToRecordAs expr types =
-  unsafeTableAliasedFromClauseExpression
-  (unsafeFunction "json_to_record" expr)
-  types
+-- | Builds an arbitrary record from a JSON object.
+jsonToRecord
+  :: SListI record
+  => Expression schema '[] 'Ungrouped params (nullity 'PGjson) -- ^ json object
+  -> NP (Aliased (TypeExpression schema)) record -- ^ record types
+  -> Query schema params record
+jsonToRecord = unsafeRecordFunction "json_to_record"
 
--- | Builds an arbitrary record from a binary JSON object. As with all functions
--- returning record, the caller must explicitly define the structure of the
--- record with an AS clause.
-jsonbToRecordAs
-  :: All Top types
-  => Expression schema '[] 'Ungrouped params (nullity 'PGjsonb)
-  -> Aliased (NP (Aliased (TypeExpression schema))) (tab ::: types)
-  -> FromClause schema params '[tab ::: types]
-jsonbToRecordAs expr types =
-  unsafeTableAliasedFromClauseExpression
-  (unsafeFunction "jsonb_to_record" expr)
-  types
+-- | Builds an arbitrary record from a binary JSON object.
+jsonbToRecord
+  :: SListI record
+  => Expression schema '[] 'Ungrouped params (nullity 'PGjsonb) -- ^ jsonb object
+  -> NP (Aliased (TypeExpression schema)) record -- ^ record types
+  -> Query schema params record
+jsonbToRecord = unsafeRecordFunction "jsonb_to_record"
 
--- | Builds an arbitrary set of records from a JSON array of objects (see note
--- below). As with all functions returning record, the caller must explicitly
--- define the structure of the record with an AS clause.
-jsonToRecordSetAs
-  :: All Top types
-  => Expression schema '[] 'Ungrouped params (nullity 'PGjson)
-  -> Aliased (NP (Aliased (TypeExpression schema))) (tab ::: types)
-  -> FromClause schema params '[tab ::: types]
-jsonToRecordSetAs expr types =
-  unsafeTableAliasedFromClauseExpression
-  (unsafeFunction "json_to_recordset" expr)
-  types
+-- | Builds an arbitrary set of records from a JSON array of objects.
+jsonToRecordSet
+  :: SListI record
+  => Expression schema '[] 'Ungrouped params (nullity 'PGjson) -- ^ json array
+  -> NP (Aliased (TypeExpression schema)) record -- ^ record types
+  -> Query schema params record
+jsonToRecordSet = unsafeRecordFunction "json_to_record_set"
 
--- | Builds an arbitrary set of records from a binary JSON array of objects (see
--- note below). As with all functions returning record, the caller must
--- explicitly define the structure of the record with an AS clause.
-jsonbToRecordSetAs
-  :: All Top types
-  => Expression schema '[] 'Ungrouped params (nullity 'PGjsonb)
-  -> Aliased (NP (Aliased (TypeExpression schema))) (tab ::: types)
-  -> FromClause schema params '[tab ::: types]
-jsonbToRecordSetAs expr types =
-  unsafeTableAliasedFromClauseExpression
-  (unsafeFunction "jsonb_to_recordset" expr)
-  types
+-- | Builds an arbitrary set of records from a binary JSON array of objects.
+jsonbToRecordSet
+  :: SListI record
+  => Expression schema '[] 'Ungrouped params (nullity 'PGjsonb) -- ^ jsonb array
+  -> NP (Aliased (TypeExpression schema)) record -- ^ record types
+  -> Query schema params record
+jsonbToRecordSet = unsafeRecordFunction "jsonb_to_record_set"
 
 {-----------------------------------------
 FROM clauses
@@ -835,7 +853,7 @@
 A `FromClause` can be a table name, or a derived table such
 as a subquery, a @JOIN@ construct, or complex combinations of these.
 -}
-newtype FromClause schema params relations
+newtype FromClause schema params from
   = UnsafeFromClause { renderFromClause :: ByteString }
   deriving (GHC.Generic,Show,Eq,Ord,NFData)
 
@@ -843,7 +861,7 @@
 table
   :: Has tab schema ('Table table)
   => Aliased Alias (alias ::: tab)
-  -> FromClause schema params '[alias ::: TableToRelation table]
+  -> FromClause schema params '[alias ::: TableToRow table]
 table (tab `As` alias) = UnsafeFromClause $
   renderAlias tab <+> "AS" <+> renderAlias alias
 
@@ -855,9 +873,9 @@
 
 -- | `view` derives a table from a `View`.
 view
-  :: Has view schema ('View rel)
+  :: Has view schema ('View row)
   => Aliased Alias (alias ::: view)
-  -> FromClause schema params '[alias ::: rel]
+  -> FromClause schema params '[alias ::: row]
 view (vw `As` alias) = UnsafeFromClause $
   renderAlias vw <+> "AS" <+> renderAlias alias
 
@@ -903,7 +921,7 @@
   -- ^ @on@ condition
   -> FromClause schema params left
   -- ^ left
-  -> FromClause schema params (Join left (NullifyRelations right))
+  -> FromClause schema params (Join left (NullifyFrom right))
 leftOuterJoin right on left = UnsafeFromClause $
   renderFromClause left <+> "LEFT OUTER JOIN" <+> renderFromClause right
   <+> "ON" <+> renderExpression on
@@ -921,7 +939,7 @@
   -- ^ @on@ condition
   -> FromClause schema params left
   -- ^ left
-  -> FromClause schema params (Join (NullifyRelations left) right)
+  -> FromClause schema params (Join (NullifyFrom left) right)
 rightOuterJoin right on left = UnsafeFromClause $
   renderFromClause left <+> "RIGHT OUTER JOIN" <+> renderFromClause right
   <+> "ON" <+> renderExpression on
@@ -941,7 +959,7 @@
   -> FromClause schema params left
   -- ^ left
   -> FromClause schema params
-      (Join (NullifyRelations left) (NullifyRelations right))
+      (Join (NullifyFrom left) (NullifyFrom right))
 fullOuterJoin right on left = UnsafeFromClause $
   renderFromClause left <+> "FULL OUTER JOIN" <+> renderFromClause right
   <+> "ON" <+> renderExpression on
@@ -956,20 +974,20 @@
 -- column @col@; otherwise @By2 (\#tab \! \#col)@ will reference a table
 -- qualified column @tab.col@.
 data By
-    (relations :: RelationsType)
+    (from :: FromType)
     (by :: (Symbol,Symbol)) where
     By1
-      :: (HasUnique relation relations columns, Has column columns ty)
+      :: (HasUnique table from columns, Has column columns ty)
       => Alias column
-      -> By relations '(relation, column)
+      -> By from '(table, column)
     By2
-      :: (Has relation relations columns, Has column columns ty)
-      => Alias relation
+      :: (Has table from columns, Has column columns ty)
+      => Alias table
       -> Alias column
-      -> By relations '(relation, column)
-deriving instance Show (By relations by)
-deriving instance Eq (By relations by)
-deriving instance Ord (By relations by)
+      -> By from '(table, column)
+deriving instance Show (By from by)
+deriving instance Eq (By from by)
+deriving instance Ord (By from by)
 
 instance (HasUnique rel rels cols, Has col cols ty, by ~ '(rel, col))
   => IsLabel col (By rels by) where fromLabel = By1 fromLabel
@@ -982,7 +1000,7 @@
     rel ! col = By2 rel col :* Nil
 
 -- | Renders a `By`.
-renderBy :: By relations by -> ByteString
+renderBy :: By from by -> ByteString
 renderBy = \case
   By1 column -> renderAlias column
   By2 rel column -> renderAlias rel <> "." <> renderAlias column
@@ -993,15 +1011,15 @@
 -- done on @NoGroups@ while all output `Expression`s must be aggregated
 -- in @Group Nil@. In general, all output `Expression`s in the
 -- complement of @bys@ must be aggregated in @Group bys@.
-data GroupByClause relations grouping where
-  NoGroups :: GroupByClause relations 'Ungrouped
+data GroupByClause from grouping where
+  NoGroups :: GroupByClause from 'Ungrouped
   Group
     :: SListI bys
-    => NP (By relations) bys
-    -> GroupByClause relations ('Grouped bys)
+    => NP (By from) bys
+    -> GroupByClause from ('Grouped bys)
 
 -- | Renders a `GroupByClause`.
-renderGroupByClause :: GroupByClause relations grouping -> ByteString
+renderGroupByClause :: GroupByClause from grouping -> ByteString
 renderGroupByClause = \case
   NoGroups -> ""
   Group Nil -> ""
@@ -1011,17 +1029,17 @@
 -- An `Ungrouped` `TableExpression` may only use `NoHaving` while a `Grouped`
 -- `TableExpression` must use `Having` whose conditions are combined with
 -- `.&&`.
-data HavingClause schema relations grouping params where
-  NoHaving :: HavingClause schema relations 'Ungrouped params
+data HavingClause schema from grouping params where
+  NoHaving :: HavingClause schema from 'Ungrouped params
   Having
-    :: [Condition schema relations ('Grouped bys) params]
-    -> HavingClause schema relations ('Grouped bys) params
-deriving instance Show (HavingClause schema relations grouping params)
-deriving instance Eq (HavingClause schema relations grouping params)
-deriving instance Ord (HavingClause schema relations grouping params)
+    :: [Condition schema from ('Grouped bys) params]
+    -> HavingClause schema from ('Grouped bys) params
+deriving instance Show (HavingClause schema from grouping params)
+deriving instance Eq (HavingClause schema from grouping params)
+deriving instance Ord (HavingClause schema from grouping params)
 
 -- | Render a `HavingClause`.
-renderHavingClause :: HavingClause schema relations grouping params -> ByteString
+renderHavingClause :: HavingClause schema from grouping params -> ByteString
 renderHavingClause = \case
   NoHaving -> ""
   Having [] -> ""
@@ -1040,29 +1058,29 @@
 -- `AscNullsLast`, `DescNullsFirst` and `DescNullsLast` options are used to
 -- determine whether nulls appear before or after non-null values in the sort
 -- ordering of a `Null` result column.
-data SortExpression schema relations grouping params where
+data SortExpression schema from grouping params where
     Asc
-      :: Expression schema relations grouping params ('NotNull ty)
-      -> SortExpression schema relations grouping params
+      :: Expression schema from grouping params ('NotNull ty)
+      -> SortExpression schema from grouping params
     Desc
-      :: Expression schema relations grouping params ('NotNull ty)
-      -> SortExpression schema relations grouping params
+      :: Expression schema from grouping params ('NotNull ty)
+      -> SortExpression schema from grouping params
     AscNullsFirst
-      :: Expression schema relations grouping params  ('Null ty)
-      -> SortExpression schema relations grouping params
+      :: Expression schema from grouping params  ('Null ty)
+      -> SortExpression schema from grouping params
     AscNullsLast
-      :: Expression schema relations grouping params  ('Null ty)
-      -> SortExpression schema relations grouping params
+      :: Expression schema from grouping params  ('Null ty)
+      -> SortExpression schema from grouping params
     DescNullsFirst
-      :: Expression schema relations grouping params  ('Null ty)
-      -> SortExpression schema relations grouping params
+      :: Expression schema from grouping params  ('Null ty)
+      -> SortExpression schema from grouping params
     DescNullsLast
-      :: Expression schema relations grouping params  ('Null ty)
-      -> SortExpression schema relations grouping params
-deriving instance Show (SortExpression schema relations grouping params)
+      :: Expression schema from grouping params  ('Null ty)
+      -> SortExpression schema from grouping params
+deriving instance Show (SortExpression schema from grouping params)
 
 -- | Render a `SortExpression`.
-renderSortExpression :: SortExpression schema relations grouping params -> ByteString
+renderSortExpression :: SortExpression schema from grouping params -> ByteString
 renderSortExpression = \case
   Asc expression -> renderExpression expression <+> "ASC"
   Desc expression -> renderExpression expression <+> "DESC"
@@ -1072,3 +1090,326 @@
     <+> "DESC NULLS FIRST"
   AscNullsLast expression -> renderExpression expression <+> "ASC NULLS LAST"
   DescNullsLast expression -> renderExpression expression <+> "DESC NULLS LAST"
+
+unsafeSubqueryExpression
+  :: ByteString
+  -> Expression schema from grp params ty
+  -> Query schema params '[alias ::: ty]
+  -> Expression schema from grp params (nullity 'PGbool)
+unsafeSubqueryExpression op x q = UnsafeExpression $
+  renderExpression x <+> op <+> parenthesized (renderQuery q)
+
+unsafeRowSubqueryExpression
+  :: SListI row
+  => ByteString
+  -> NP (Aliased (Expression schema from grp params)) row
+  -> Query schema params row
+  -> Expression schema from grp params (nullity 'PGbool)
+unsafeRowSubqueryExpression op xs q = UnsafeExpression $
+  renderExpression (row xs) <+> op <+> parenthesized (renderQuery q)
+
+-- | The right-hand side is a sub`Query`, which must return exactly one column.
+-- The left-hand expression is evaluated and compared to each row of the
+-- sub`Query` result. The result of `in_` is `true` if any equal subquery row is found.
+-- The result is `false` if no equal row is found
+-- (including the case where the subquery returns no rows).
+--
+-- >>> printSQL $ true `in_` values_ (true `as` #foo)
+-- TRUE IN (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
+in_
+  :: Expression schema from grp params ty -- ^ expression
+  -> Query schema params '[alias ::: ty] -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+in_ = unsafeSubqueryExpression "IN"
+
+{- | The left-hand side of this form of `rowIn` is a row constructor.
+The right-hand side is a sub`Query`,
+which must return exactly as many columns as
+there are expressions in the left-hand row.
+The left-hand expressions are evaluated and compared row-wise to each row
+of the subquery result. The result of `rowIn`
+is `true` if any equal subquery row is found.
+The result is `false` if no equal row is found
+(including the case where the subquery returns no rows).
+
+>>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression '[] '[] 'Ungrouped '[])) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
+>>> printSQL $ myRow `rowIn` values_ myRow
+ROW(1, FALSE) IN (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
+-}
+rowIn
+  :: SListI row
+  => NP (Aliased (Expression schema from grp params)) row -- ^ row constructor
+  -> Query schema params row -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+rowIn = unsafeRowSubqueryExpression "IN"
+
+-- | >>> printSQL $ true `eqAll` values_ (true `as` #foo)
+-- TRUE = ALL (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
+eqAll
+  :: Expression schema from grp params ty -- ^ expression
+  -> Query schema params '[alias ::: ty] -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+eqAll = unsafeSubqueryExpression "= ALL"
+
+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression '[] '[] 'Ungrouped '[])) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
+-- >>> printSQL $ myRow `rowEqAll` values_ myRow
+-- ROW(1, FALSE) = ALL (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
+rowEqAll
+  :: SListI row
+  => NP (Aliased (Expression schema from grp params)) row -- ^ row constructor
+  -> Query schema params row -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+rowEqAll = unsafeRowSubqueryExpression "= ALL"
+
+-- | >>> printSQL $ true `eqAny` values_ (true `as` #foo)
+-- TRUE = ANY (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
+eqAny
+  :: Expression schema from grp params ty -- ^ expression
+  -> Query schema params '[alias ::: ty] -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+eqAny = unsafeSubqueryExpression "= ANY"
+
+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression '[] '[] 'Ungrouped '[])) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
+-- >>> printSQL $ myRow `rowEqAny` values_ myRow
+-- ROW(1, FALSE) = ANY (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
+rowEqAny
+  :: SListI row
+  => NP (Aliased (Expression schema from grp params)) row -- ^ row constructor
+  -> Query schema params row -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+rowEqAny = unsafeRowSubqueryExpression "= ANY"
+
+-- | >>> printSQL $ true `neqAll` values_ (true `as` #foo)
+-- TRUE <> ALL (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
+neqAll
+  :: Expression schema from grp params ty -- ^ expression
+  -> Query schema params '[alias ::: ty] -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+neqAll = unsafeSubqueryExpression "<> ALL"
+
+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression '[] '[] 'Ungrouped '[])) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
+-- >>> printSQL $ myRow `rowNeqAll` values_ myRow
+-- ROW(1, FALSE) <> ALL (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
+rowNeqAll
+  :: SListI row
+  => NP (Aliased (Expression schema from grp params)) row -- ^ row constructor
+  -> Query schema params row -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+rowNeqAll = unsafeRowSubqueryExpression "<> ALL"
+
+-- | >>> printSQL $ true `neqAny` values_ (true `as` #foo)
+-- TRUE <> ANY (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
+neqAny
+  :: Expression schema from grp params ty -- ^ expression
+  -> Query schema params '[alias ::: ty] -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+neqAny = unsafeSubqueryExpression "<> ANY"
+
+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression '[] '[] 'Ungrouped '[])) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
+-- >>> printSQL $ myRow `rowNeqAny` values_ myRow
+-- ROW(1, FALSE) <> ANY (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
+rowNeqAny
+  :: SListI row
+  => NP (Aliased (Expression schema from grp params)) row -- ^ row constructor
+  -> Query schema params row -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+rowNeqAny = unsafeRowSubqueryExpression "<> ANY"
+
+-- | >>> printSQL $ true `allLt` values_ (true `as` #foo)
+-- TRUE ALL < (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
+allLt
+  :: Expression schema from grp params ty -- ^ expression
+  -> Query schema params '[alias ::: ty] -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+allLt = unsafeSubqueryExpression "ALL <"
+
+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression '[] '[] 'Ungrouped '[])) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
+-- >>> printSQL $ myRow `rowLtAll` values_ myRow
+-- ROW(1, FALSE) ALL < (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
+rowLtAll
+  :: SListI row
+  => NP (Aliased (Expression schema from grp params)) row -- ^ row constructor
+  -> Query schema params row -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+rowLtAll = unsafeRowSubqueryExpression "ALL <"
+
+-- | >>> printSQL $ true `ltAny` values_ (true `as` #foo)
+-- TRUE ANY < (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
+ltAny
+  :: Expression schema from grp params ty -- ^ expression
+  -> Query schema params '[alias ::: ty] -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+ltAny = unsafeSubqueryExpression "ANY <"
+
+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression '[] '[] 'Ungrouped '[])) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
+-- >>> printSQL $ myRow `rowLtAll` values_ myRow
+-- ROW(1, FALSE) ALL < (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
+rowLtAny
+  :: SListI row
+  => NP (Aliased (Expression schema from grp params)) row -- ^ row constructor
+  -> Query schema params row -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+rowLtAny = unsafeRowSubqueryExpression "ANY <"
+
+-- | >>> printSQL $ true `lteAll` values_ (true `as` #foo)
+-- TRUE <= ALL (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
+lteAll
+  :: Expression schema from grp params ty -- ^ expression
+  -> Query schema params '[alias ::: ty] -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+lteAll = unsafeSubqueryExpression "<= ALL"
+
+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression '[] '[] 'Ungrouped '[])) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
+-- >>> printSQL $ myRow `rowLteAll` values_ myRow
+-- ROW(1, FALSE) <= ALL (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
+rowLteAll
+  :: SListI row
+  => NP (Aliased (Expression schema from grp params)) row -- ^ row constructor
+  -> Query schema params row -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+rowLteAll = unsafeRowSubqueryExpression "<= ALL"
+
+-- | >>> printSQL $ true `lteAny` values_ (true `as` #foo)
+-- TRUE <= ANY (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
+lteAny
+  :: Expression schema from grp params ty -- ^ expression
+  -> Query schema params '[alias ::: ty] -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+lteAny = unsafeSubqueryExpression "<= ANY"
+
+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression '[] '[] 'Ungrouped '[])) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
+-- >>> printSQL $ myRow `rowLteAny` values_ myRow
+-- ROW(1, FALSE) <= ANY (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
+rowLteAny
+  :: SListI row
+  => NP (Aliased (Expression schema from grp params)) row -- ^ row constructor
+  -> Query schema params row -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+rowLteAny = unsafeRowSubqueryExpression "<= ANY"
+
+-- | >>> printSQL $ true `gtAll` values_ (true `as` #foo)
+-- TRUE > ALL (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
+gtAll
+  :: Expression schema from grp params ty -- ^ expression
+  -> Query schema params '[alias ::: ty] -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+gtAll = unsafeSubqueryExpression "> ALL"
+
+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression '[] '[] 'Ungrouped '[])) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
+-- >>> printSQL $ myRow `rowGtAll` values_ myRow
+-- ROW(1, FALSE) > ALL (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
+rowGtAll
+  :: SListI row
+  => NP (Aliased (Expression schema from grp params)) row -- ^ row constructor
+  -> Query schema params row -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+rowGtAll = unsafeRowSubqueryExpression "> ALL"
+
+-- | >>> printSQL $ true `gtAny` values_ (true `as` #foo)
+-- TRUE > ANY (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
+gtAny
+  :: Expression schema from grp params ty -- ^ expression
+  -> Query schema params '[alias ::: ty] -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+gtAny = unsafeSubqueryExpression "> ANY"
+
+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression '[] '[] 'Ungrouped '[])) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
+-- >>> printSQL $ myRow `rowGtAny` values_ myRow
+-- ROW(1, FALSE) > ANY (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
+rowGtAny
+  :: SListI row
+  => NP (Aliased (Expression schema from grp params)) row -- ^ row constructor
+  -> Query schema params row -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+rowGtAny = unsafeRowSubqueryExpression "> ANY"
+
+-- | >>> printSQL $ true `gteAll` values_ (true `as` #foo)
+-- TRUE >= ALL (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
+gteAll
+  :: Expression schema from grp params ty -- ^ expression
+  -> Query schema params '[alias ::: ty] -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+gteAll = unsafeSubqueryExpression ">= ALL"
+
+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression '[] '[] 'Ungrouped '[])) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
+-- >>> printSQL $ myRow `rowGteAll` values_ myRow
+-- ROW(1, FALSE) >= ALL (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
+rowGteAll
+  :: SListI row
+  => NP (Aliased (Expression schema from grp params)) row -- ^ row constructor
+  -> Query schema params row -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+rowGteAll = unsafeRowSubqueryExpression ">= ALL"
+
+-- | >>> printSQL $ true `gteAny` values_ (true `as` #foo)
+-- TRUE >= ANY (SELECT * FROM (VALUES (TRUE)) AS t ("foo"))
+gteAny
+  :: Expression schema from grp params ty -- ^ expression
+  -> Query schema params '[alias ::: ty] -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+gteAny = unsafeSubqueryExpression ">= ANY"
+
+-- | >>> let myRow = 1 `as` #foo :* false `as` #bar :: NP (Aliased (Expression '[] '[] 'Ungrouped '[])) '["foo" ::: 'NotNull 'PGint2, "bar" ::: 'NotNull 'PGbool]
+-- >>> printSQL $ myRow `rowGteAny` values_ myRow
+-- ROW(1, FALSE) >= ANY (SELECT * FROM (VALUES (1, FALSE)) AS t ("foo", "bar"))
+rowGteAny
+  :: SListI row
+  => NP (Aliased (Expression schema from grp params)) row -- ^ row constructor
+  -> Query schema params row -- ^ subquery
+  -> Expression schema from grp params (nullity 'PGbool)
+rowGteAny = unsafeRowSubqueryExpression ">= ANY"
+
+-- | A `CommonTableExpression` is an auxiliary statement in a `with` clause.
+data CommonTableExpression statement
+  (params :: [NullityType])
+  (schema0 :: SchemaType)
+  (schema1 :: SchemaType) where
+  CommonTableExpression
+    :: Aliased (statement schema params) (alias ::: cte)
+    -> CommonTableExpression
+      statement params schema (alias ::: 'View cte ': schema)
+instance (KnownSymbol alias, schema1 ~ (alias ::: 'View cte ': schema))
+  => Aliasable alias
+    (statement schema params cte)
+    (CommonTableExpression statement params schema schema1) where
+      statement `as` alias = CommonTableExpression (statement `as` alias)
+instance (KnownSymbol alias, schema1 ~ (alias ::: 'View cte ': schema))
+  => Aliasable alias (statement schema params cte)
+    (AlignedList (CommonTableExpression statement params) schema schema1) where
+      statement `as` alias = single (statement `as` alias)
+
+-- | render a `CommonTableExpression`.
+renderCommonTableExpression
+  :: (forall sch ps row. statement ps sch row -> ByteString) -- ^ render statement
+  -> CommonTableExpression statement params schema0 schema1 -> ByteString
+renderCommonTableExpression renderStatement
+  (CommonTableExpression (statement `As` alias)) =
+    renderAlias alias <+> "AS" <+> parenthesized (renderStatement statement)
+
+-- | render a non-empty `AlignedList` of `CommonTableExpression`s.
+renderCommonTableExpressions
+  :: (forall sch ps row. statement ps sch row -> ByteString) -- ^ render statement
+  -> CommonTableExpression statement params schema0 schema1
+  -> AlignedList (CommonTableExpression statement params) schema1 schema2
+  -> ByteString
+renderCommonTableExpressions renderStatement cte ctes =
+  renderCommonTableExpression renderStatement cte <> case ctes of
+    Done           -> ""
+    cte' :>> ctes' -> "," <+>
+      renderCommonTableExpressions renderStatement cte' ctes'
+
+-- | `with` provides a way to write auxiliary statements for use in a larger query.
+-- These statements, referred to as `CommonTableExpression`s, can be thought of as
+-- defining temporary tables that exist just for one query.
+class With statement where
+  with
+    :: AlignedList (CommonTableExpression statement params) schema0 schema1
+    -- ^ common table expressions
+    -> statement schema1 params row
+    -- ^ larger query
+    -> statement schema0 params row
+instance With Query where
+  with Done query = query
+  with (cte :>> ctes) query = UnsafeQuery $
+    "WITH" <+> renderCommonTableExpressions renderQuery cte ctes
+      <+> renderQuery query
diff --git a/src/Squeal/PostgreSQL/Render.hs b/src/Squeal/PostgreSQL/Render.hs
--- a/src/Squeal/PostgreSQL/Render.hs
+++ b/src/Squeal/PostgreSQL/Render.hs
@@ -9,12 +9,14 @@
 -}
 
 {-# LANGUAGE
-    FlexibleContexts
+    AllowAmbiguousTypes
+  , FlexibleContexts
   , MagicHash
   , OverloadedStrings
   , PolyKinds
   , RankNTypes
   , ScopedTypeVariables
+  , TypeApplications
 #-}
 
 module Squeal.PostgreSQL.Render
@@ -28,6 +30,7 @@
   , renderCommaSeparated
   , renderCommaSeparatedMaybe
   , renderNat
+  , renderSymbol
   , RenderSQL (..)
   , printSQL
   ) where
@@ -51,6 +54,7 @@
 
 -- | Concatenate two `ByteString`s with a space between.
 (<+>) :: ByteString -> ByteString -> ByteString
+infixr 7 <+>
 str1 <+> str2 = str1 <> " " <> str2
 
 -- | Comma separate a list of `ByteString`s.
@@ -92,8 +96,12 @@
   . hmap (K . render)
 
 -- | Render a promoted `Nat`.
-renderNat :: KnownNat n => proxy n -> ByteString
-renderNat (_ :: proxy n) = fromString (show (natVal' (proxy# :: Proxy# n)))
+renderNat :: forall n. KnownNat n => ByteString
+renderNat = fromString (show (natVal' (proxy# :: Proxy# n)))
+
+-- | Render a promoted `Symbol`.
+renderSymbol :: forall s. KnownSymbol s => ByteString
+renderSymbol = fromString (symbolVal' (proxy# :: Proxy# s))
 
 -- | A class for rendering SQL
 class RenderSQL sql where
diff --git a/src/Squeal/PostgreSQL/Schema.hs b/src/Squeal/PostgreSQL/Schema.hs
--- a/src/Squeal/PostgreSQL/Schema.hs
+++ b/src/Squeal/PostgreSQL/Schema.hs
@@ -20,6 +20,7 @@
   , FlexibleInstances
   , FunctionalDependencies
   , GADTs
+  , LambdaCase
   , OverloadedStrings
   , RankNTypes
   , ScopedTypeVariables
@@ -33,18 +34,24 @@
 #-}
 
 module Squeal.PostgreSQL.Schema
-  ( -- * Types
+  ( -- * Postgres Types
     PGType (..)
-  , HasOid (..)
   , NullityType (..)
+  , RowType
+  , FromType
+    -- * Haskell to Postgres Types
+  , PG
+  , NullPG
+  , TuplePG
+  , RowPG
+  , Json (..)
+  , Jsonb (..)
+  , Composite (..)
+  , Enumerated (..)
+    -- * Schema Types
   , ColumnType
-    -- * Tables
   , ColumnsType
-  , RelationType
-  , NilRelation
-  , RelationsType
   , TableType
-    -- * Schema
   , SchemumType (..)
   , SchemaType
     -- * Constraints
@@ -52,7 +59,6 @@
   , ColumnConstraint (..)
   , TableConstraint (..)
   , TableConstraints
-  , NilTableConstraints
   , Uniquely
     -- * Aliases
   , (:::)
@@ -62,8 +68,6 @@
   , Aliased (As)
   , Aliasable (as)
   , renderAliasedAs
-  , AliasesOf
-  , ZipAliased (..)
   , Has
   , HasUnique
   , HasAll
@@ -75,62 +79,48 @@
   , PGlabel (..)
   , renderLabel
   , renderLabels
+  , LabelsPG
     -- * Grouping
   , Grouping (..)
   , GroupedBy
-    -- * Type Families
-  , Join
-  , With
+    -- * Aligned lists
+  , AlignedList (..)
+  , single
+    -- * Data Definitions
   , Create
   , Drop
   , Alter
   , Rename
+  , DropIfConstraintsInvolve
+    -- * Lists
+  , Join
   , Elem
   , In
+  , Length
+    -- * Type Classifications
+  , HasOid (..)
   , PGNum
   , PGIntegral
   , PGFloating
   , PGTypeOf
-  , PGarrayOf
-  , PGarray
-  , PGtextArray
-  , SameTypes
+  , PGArrayOf
+  , PGArray
+  , PGTextArray
+  , PGJsonType
+  , PGJsonKey
   , SamePGType
   , AllNotNull
   , NotAllNull
+    -- * Nullifications
   , NullifyType
-  , NullifyRelation
-  , NullifyRelations
-  , ColumnsToRelation
+  , NullifyRow
+  , NullifyFrom
+    -- * Table Conversions
   , TableToColumns
-  , TableToRelation
-  , RelationToRowType
-  , RelationToNullityTypes
-  , ConstraintInvolves
-  , DropIfConstraintsInvolve
-    -- ** JSON support
-  , PGjson_
-  , PGjsonKey
-    -- * Embedding
-  , PG
-  , EnumFrom
-  , LabelsFrom
-  , CompositeFrom
-  , FieldNamesFrom
-  , FieldTypesFrom
-  , ConstructorsOf
-  , ConstructorNameOf
-  , ConstructorNamesOf
-  , FieldsOf
-  , FieldNameOf
-  , FieldNamesOf
-  , FieldTypeOf
-  , FieldTypesOf
-  , RecordCodeOf
-  , MapMaybes (..)
-  , Nulls
+  , TableToRow
   ) where
 
+import Control.Category
 import Control.DeepSeq
 import Data.Aeson (Value)
 import Data.ByteString (ByteString)
@@ -144,10 +134,13 @@
 import Data.Word (Word16, Word32, Word64)
 import Data.Type.Bool
 import Data.UUID.Types (UUID)
+import Data.Vector (Vector)
 import Generics.SOP
+import Generics.SOP.Record
 import GHC.OverloadedLabels
 import GHC.TypeLits
 import Network.IP.Addr
+import Prelude hiding (id, (.))
 
 import qualified Data.ByteString.Lazy as Lazy
 import qualified Data.Text.Lazy as Lazy
@@ -183,10 +176,10 @@
   | PGinet -- ^ IPv4 or IPv6 host address
   | PGjson -- ^	textual JSON data
   | PGjsonb -- ^ binary JSON data, decomposed
-  | PGvararray PGType -- ^ variable length array
-  | PGfixarray Nat PGType -- ^ fixed length array
-  | PGenum [Symbol]
-  | PGcomposite [(Symbol, PGType)]
+  | PGvararray NullityType -- ^ variable length array
+  | PGfixarray Nat NullityType -- ^ fixed length array
+  | PGenum [Symbol] -- ^ enumerated (enum) types are data types that comprise a static, ordered set of values.
+  | PGcomposite RowType -- ^ a composite type represents the structure of a row or record; it is essentially just a list of field names and their data types.
   | UnsafePGType Symbol -- ^ an escape hatch for unsupported PostgreSQL types
 
 -- | The object identifier of a `PGType`.
@@ -283,9 +276,6 @@
 
 -- | A `TableConstraints` is a row of `TableConstraint`s.
 type TableConstraints = [(Symbol,TableConstraint)]
--- | A monokinded empty `TableConstraints`.
-type family NilTableConstraints :: TableConstraints where
-  NilTableConstraints = '[]
 
 -- | A `ForeignKey` must reference columns that either are
 -- a `PrimaryKey` or form a `Unique` constraint.
@@ -309,36 +299,39 @@
 -- :}
 type TableType = (TableConstraints,ColumnsType)
 
--- | `RelationType` is a row of `NullityType`
---
--- >>> :{
--- type family PersonRelation :: RelationType where
---   PersonRelation =
---     '[ "name"        ::: 'NotNull 'PGtext
---      , "age"         ::: 'NotNull 'PGint4
---      , "dateOfBirth" :::    'Null 'PGdate
---      ]
--- :}
-type RelationType = [(Symbol,NullityType)]
--- | A monokinded empty `RelationType`.
-type family NilRelation :: RelationType where NilRelation = '[]
+{- | A `RowType` is a row of `NullityType`. They correspond to Haskell
+record types by means of `RowPG` and are used in many places.
 
--- | `RelationsType` is a row of `RelationType`s, thought of as a product.
-type RelationsType = [(Symbol,RelationType)]
+>>> :{
+type family PersonRow :: RowType where
+  PersonRow =
+    '[ "name"        ::: 'NotNull 'PGtext
+     , "age"         ::: 'NotNull 'PGint4
+     , "dateOfBirth" :::    'Null 'PGdate
+     ]
+:}
+-}
+type RowType = [(Symbol,NullityType)]
 
--- | `ColumnsToRelation` removes column constraints.
-type family ColumnsToRelation (columns :: ColumnsType) :: RelationType where
-  ColumnsToRelation '[] = '[]
-  ColumnsToRelation (column ::: constraint :=> ty ': columns) =
-    column ::: ty ': ColumnsToRelation columns
+{- | `FromType` is a row of `RowType`s. It can be thought of as
+a product, or horizontal gluing and is used in `Squeal.PostgreSQL.Query.FromClause`s
+and `Squeal.PostgreSQL.Query.TableExpression`s.
+-}
+type FromType = [(Symbol,RowType)]
 
+-- | `ColumnsToRow` removes column constraints.
+type family ColumnsToRow (columns :: ColumnsType) :: RowType where
+  ColumnsToRow '[] = '[]
+  ColumnsToRow (column ::: constraint :=> ty ': columns) =
+    column ::: ty ': ColumnsToRow columns
+
 -- | `TableToColumns` removes table constraints.
 type family TableToColumns (table :: TableType) :: ColumnsType where
   TableToColumns (constraints :=> columns) = columns
 
--- | Convert a table to a relation.
-type family TableToRelation (table :: TableType) :: RelationType where
-  TableToRelation tab = ColumnsToRelation (TableToColumns tab)
+-- | Convert a table to a row type.
+type family TableToRow (table :: TableType) :: RowType where
+  TableToRow tab = ColumnsToRow (TableToColumns tab)
 
 -- | `Grouping` is an auxiliary namespace, created by
 -- @GROUP BY@ clauses (`Squeal.PostgreSQL.Query.group`), and used
@@ -351,15 +344,15 @@
 a member of the auxiliary namespace created by @GROUP BY@ clauses and thus,
 may be called in an output `Squeal.PostgreSQL.Expression.Expression` without aggregating.
 -}
-class (KnownSymbol relation, KnownSymbol column)
-  => GroupedBy relation column bys where
-instance {-# OVERLAPPING #-} (KnownSymbol relation, KnownSymbol column)
-  => GroupedBy relation column ('(table,column) ': bys)
+class (KnownSymbol table, KnownSymbol column)
+  => GroupedBy table column bys where
+instance {-# OVERLAPPING #-} (KnownSymbol table, KnownSymbol column)
+  => GroupedBy table column ('(table,column) ': bys)
 instance {-# OVERLAPPABLE #-}
-  ( KnownSymbol relation
+  ( KnownSymbol table
   , KnownSymbol column
-  , GroupedBy relation column bys
-  ) => GroupedBy relation column (tabcol ': bys)
+  , GroupedBy table column bys
+  ) => GroupedBy table column (tabcol ': bys)
 
 -- | `Alias`es are proxies for a type level string or `Symbol`
 -- and have an `IsLabel` instance so that with @-XOverloadedLabels@
@@ -420,7 +413,7 @@
   | aliased -> expression
   , aliased -> alias
   where as :: expression -> Alias alias -> aliased
-instance (alias ~ alias1, KnownSymbol alias) => Aliasable alias
+instance (KnownSymbol alias, alias ~ alias1) => Aliasable alias
   (expression ty)
   (Aliased expression (alias1 ::: ty))
     where
@@ -441,41 +434,6 @@
 renderAliasedAs render (expression `As` alias) =
   render expression <> " AS " <> renderAlias alias
 
--- | `AliasesOf` retains the AliasesOf in a row.
-type family AliasesOf aliaseds where
-  AliasesOf '[] = '[]
-  AliasesOf (alias ::: ty ': tys) = alias ': AliasesOf tys
-
--- | The `ZipAliased` class provides a type family for zipping
--- `Symbol` lists together with arbitrary lists of the same size,
--- with an associated type family `ZipAs`, together with
--- a method `zipAs` for zipping heterogeneous lists of `Alias`es
--- together with a heterogeneous list of expressions into
--- a heterogeneous list of `Aliased` expressions.
-class
-  ( SListI (ZipAs ns xs)
-  , All KnownSymbol ns
-  ) => ZipAliased ns xs where
-
-  type family ZipAs
-    (ns :: [Symbol]) (xs :: [k]) = (zs :: [(Symbol,k)]) | zs -> ns xs
-
-  zipAs
-    :: NP Alias ns
-    -> NP expr xs
-    -> NP (Aliased expr) (ZipAs ns xs)
-
-instance ZipAliased '[] '[] where
-  type ZipAs '[] '[] = '[]
-  zipAs Nil Nil = Nil
-
-instance
-  ( KnownSymbol n
-  , ZipAliased ns xs
-  ) => ZipAliased (n ': ns) (x ': xs) where
-  type ZipAs (n ': ns) (x ': xs) = '(n,x) ': ZipAs ns xs
-  zipAs (n :* ns) (x :* xs) = x `As` n :* zipAs ns xs
-
 -- | @HasUnique alias fields field@ is a constraint that proves that
 -- @fields@ is a singleton of @alias ::: field@.
 type HasUnique alias fields field = fields ~ '[alias ::: field]
@@ -522,21 +480,15 @@
 -- | @In x xs@ is a constraint that proves that @x@ is in @xs@.
 type family In x xs :: Constraint where In x xs = Elem x xs ~ 'True
 
--- | `PGNum` is a constraint on `PGType` whose
--- `Squeal.PostgreSQL.Expression.Expression`s have a `Num` constraint.
-type PGNum ty =
-  In ty '[ 'PGint2, 'PGint4, 'PGint8, 'PGnumeric, 'PGfloat4, 'PGfloat8]
+-- | Numeric Postgres types.
+type PGNum =
+  '[ 'PGint2, 'PGint4, 'PGint8, 'PGnumeric, 'PGfloat4, 'PGfloat8]
 
--- | `PGFloating` is a constraint on `PGType` whose
--- `Squeal.PostgreSQL.Expression.Expression`s
--- have `Fractional` and `Floating` constraints.
-type PGFloating ty = In ty '[ 'PGfloat4, 'PGfloat8, 'PGnumeric]
+-- | Floating Postgres types.
+type PGFloating = '[ 'PGfloat4, 'PGfloat8, 'PGnumeric]
 
--- | `PGIntegral` is a constraint on `PGType` whose
--- `Squeal.PostgreSQL.Expression.Expression`s
--- have `Squeal.PostgreSQL.Expression.div_` and
--- `Squeal.PostgreSQL.Expression.mod_` functions.
-type PGIntegral ty = In ty '[ 'PGint2, 'PGint4, 'PGint8]
+-- | Integral Postgres types.
+type PGIntegral = '[ 'PGint2, 'PGint4, 'PGint8]
 
 -- | Error message helper for displaying unavailable\/unknown\/placeholder type
 -- variables whose kind is known.
@@ -547,40 +499,32 @@
 type ErrPGvararrayOf t = ErrArrayOf ('ShowType 'PGvararray) t
 
 -- | Ensure a type is a valid array type.
-type family PGarray name arr :: Constraint where
-  PGarray name ('PGvararray x) = ()
-  PGarray name ('PGfixarray n x) = ()
-  PGarray name val = TypeError
-    ('Text name :<>: 'Text ": Unsatisfied PGarray constraint. Expected either: "
+type family PGArray name arr :: Constraint where
+  PGArray name ('PGvararray x) = ()
+  PGArray name ('PGfixarray n x) = ()
+  PGArray name val = TypeError
+    ('Text name :<>: 'Text ": Unsatisfied PGArray constraint. Expected either: "
      :$$: 'Text " • " :<>: ErrPGvararrayOf (Placeholder PGType)
      :$$: 'Text " • " :<>: ErrPGfixarrayOf (Placeholder PGType)
      :$$: 'Text "But got: " :<>: 'ShowType val)
 
 -- | Ensure a type is a valid array type with a specific element type.
-type family PGarrayOf name arr ty :: Constraint where
-  PGarrayOf name ('PGvararray x) ty = x ~ ty
-  PGarrayOf name ('PGfixarray n x) ty = x ~ ty
-  PGarrayOf name val ty = TypeError
-    ( 'Text name :<>: 'Text "Unsatisfied PGarrayOf constraint. Expected either: "
+type family PGArrayOf name arr ty :: Constraint where
+  PGArrayOf name ('PGvararray x) ty = x ~ ty
+  PGArrayOf name ('PGfixarray n x) ty = x ~ ty
+  PGArrayOf name val ty = TypeError
+    ( 'Text name :<>: 'Text "Unsatisfied PGArrayOf constraint. Expected either: "
       :$$: 'Text " • " :<>: ErrPGvararrayOf ( 'ShowType ty )
       :$$: 'Text " • " :<>: ErrPGfixarrayOf ( 'ShowType ty )
       :$$: 'Text "But got: " :<>: 'ShowType val)
 
 -- | Ensure a type is a valid array type whose elements are text.
-type PGtextArray name arr = PGarrayOf name arr 'PGtext
+type PGTextArray name arr = PGArrayOf name arr ('NotNull 'PGtext)
 
 -- | `PGTypeOf` forgets about @NULL@ and any column constraints.
 type family PGTypeOf (ty :: NullityType) :: PGType where
   PGTypeOf (nullity pg) = pg
 
--- | `SameTypes` is a constraint that proves two `ColumnsType`s have the same
--- length and the same `ColumnType`s.
-type family SameTypes (columns0 :: ColumnsType) (columns1 :: ColumnsType)
-  :: Constraint where
-  SameTypes '[] '[] = ()
-  SameTypes (column0 ::: def0 :=> ty0 ': columns0) (column1 ::: def1 :=> ty1 ': columns1)
-    = (ty0 ~ ty1, SameTypes columns0 columns1)
-
 -- | Equality constraint on the underlying `PGType` of two columns.
 class SamePGType
   (ty0 :: (Symbol,ColumnType)) (ty1 :: (Symbol,ColumnType)) where
@@ -599,34 +543,24 @@
   NotAllNull (column ::: def :=> 'NotNull ty ': columns) = ()
   NotAllNull (column ::: def :=> 'Null ty ': columns) = NotAllNull columns
 
--- | `NullifyType` is an idempotent that nullifies a `ColumnType`.
+-- | `NullifyType` is an idempotent that nullifies a `NullityType`.
 type family NullifyType (ty :: NullityType) :: NullityType where
   NullifyType ('Null ty) = 'Null ty
   NullifyType ('NotNull ty) = 'Null ty
 
--- | `NullifyRelation` is an idempotent that nullifies a `ColumnsType`.
-type family NullifyRelation (columns :: RelationType) :: RelationType where
-  NullifyRelation '[] = '[]
-  NullifyRelation (column ::: ty ': columns) =
-    column ::: NullifyType ty ': NullifyRelation columns
+-- | `NullifyRow` is an idempotent that nullifies a `RowType`.
+type family NullifyRow (columns :: RowType) :: RowType where
+  NullifyRow '[] = '[]
+  NullifyRow (column ::: ty ': columns) =
+    column ::: NullifyType ty ': NullifyRow columns
 
--- | `NullifyRelations` is an idempotent that nullifies a `RelationsType`
+-- | `NullifyFrom` is an idempotent that nullifies a `FromType`
 -- used to nullify the left or right hand side of an outer join
 -- in a `Squeal.PostgreSQL.Query.FromClause`.
-type family NullifyRelations (tables :: RelationsType) :: RelationsType where
-  NullifyRelations '[] = '[]
-  NullifyRelations (table ::: columns ': tables) =
-    table ::: NullifyRelation columns ': NullifyRelations tables
-
--- | `RelationToRowType` drops the nullity constraints of its argument relations.
-type family RelationToRowType (tables :: RelationType) :: [(Symbol, PGType)] where
-  RelationToRowType (nullity x : xs) = x : RelationToRowType xs
-  RelationToRowType '[] = '[]
-
--- | `RelationToNullityTypes` drops the column constraints.
-type family RelationToNullityTypes (rel :: RelationType) :: [NullityType] where
-  RelationToNullityTypes ('(k, x) : xs) = x : RelationToNullityTypes xs
-  RelationToNullityTypes '[]            = '[]
+type family NullifyFrom (tables :: FromType) :: FromType where
+  NullifyFrom '[] = '[]
+  NullifyFrom (table ::: columns ': tables) =
+    table ::: NullifyRow columns ': NullifyFrom tables
 
 -- | `Join` is simply promoted `++` and is used in @JOIN@s in
 -- `Squeal.PostgreSQL.Query.FromClause`s.
@@ -659,19 +593,6 @@
   Alter alias x1 (alias ::: x0 ': xs) = alias ::: x1 ': xs
   Alter alias x1 (x0 ': xs) = x0 ': Alter alias x1 xs
 
--- type family AddConstraint constraint ty where
---   AddConstraint constraint (constraints :=> ty)
---     = AsSet (constraint ': constraints) :=> ty
-
--- type family DeleteFromList (e :: elem) (list :: [elem]) where
---   DeleteFromList elem '[] = '[]
---   DeleteFromList elem (x ': xs) =
---     If (Cmp elem x == 'EQ) xs (x ': DeleteFromList elem xs)
-
--- type family DropConstraint constraint ty where
---   DropConstraint constraint (constraints :=> ty)
---     = (AsSet (DeleteFromList constraint constraints)) :=> ty
-
 -- | @Rename alias0 alias1 xs@ replaces the alias @alias0@ by @alias1@ in @xs@
 -- and is used in `Squeal.PostgreSQL.Definition.alterTableRename` and
 -- `Squeal.PostgreSQL.Definition.renameColumn`.
@@ -679,26 +600,6 @@
   Rename alias0 alias1 ((alias0 ::: x0) ': xs) = (alias1 ::: x0) ': xs
   Rename alias0 alias1 (x ': xs) = x ': Rename alias0 alias1 xs
 
--- | `MapMaybes` is used in the binary instances of composite types.
-class MapMaybes xs where
-  type family Maybes (xs :: [Type]) = (mxs :: [Type]) | mxs -> xs
-  maybes :: NP Maybe xs -> NP I (Maybes xs)
-  unMaybes :: NP I (Maybes xs) -> NP Maybe xs
-instance MapMaybes '[] where
-  type Maybes '[] = '[]
-  maybes Nil = Nil
-  unMaybes Nil = Nil
-instance MapMaybes xs => MapMaybes (x ': xs) where
-  type Maybes (x ': xs) = Maybe x ': Maybes xs
-  maybes (x :* xs) = I x :* maybes xs
-  unMaybes (I mx :* xs) = mx :* unMaybes xs
-
--- | `Nulls` is used to construct a `Squeal.Postgresql.Expression.row`
--- of a composite type.
-type family Nulls tys where
-  Nulls '[] = '[]
-  Nulls (field ::: ty ': tys) = field ::: 'Null ty ': Nulls tys
-
 -- | Check if a `TableConstraint` involves a column
 type family ConstraintInvolves column constraint where
   ConstraintInvolves column ('Check columns) = column `Elem` columns
@@ -715,25 +616,46 @@
         (DropIfConstraintsInvolve column constraints)
         (alias ::: constraint ': DropIfConstraintsInvolve column constraints)
 
+{- | Calculate the `Length` of a type level list
+
+>>> :kind! Length '[Char,String,Bool,Double]
+Length '[Char,String,Bool,Double] :: Nat
+= 4
+-}
+type family Length (xs :: [k]) :: Nat where
+  Length (x : xs) = 1 + Length xs
+  Length '[] = 0
+
 -- | A `SchemumType` is a user-defined type, either a `Table`,
 -- `View` or `Typedef`.
 data SchemumType
   = Table TableType
-  | View RelationType
+  | View RowType
   | Typedef PGType
 
--- | The schema of a database consists of a list of aliased,
--- user-defined `SchemumType`s.
-type SchemaType = [(Symbol,SchemumType)]
+{- | The schema of a database consists of a list of aliased,
+user-defined `SchemumType`s.
 
--- | Used in `Squeal.Postgresql.Manipulation.with`.
-type family With
-  (relations :: RelationsType)
-  (schema :: SchemaType)
-  :: SchemaType where
-    With '[] schema = schema
-    With (alias ::: rel ': rels) schema =
-      alias ::: 'View rel ': With rels schema
+>>> :{
+type family Schema :: SchemaType where
+  Schema =
+    '[ "users" ::: 'Table (
+        '[ "pk_users" ::: 'PrimaryKey '["id"] ] :=>
+        '[ "id"   :::   'Def :=> 'NotNull 'PGint4
+        , "name" ::: 'NoDef :=> 'NotNull 'PGtext
+        ])
+    , "emails" ::: 'Table (
+        '[ "pk_emails"  ::: 'PrimaryKey '["id"]
+        , "fk_user_id" ::: 'ForeignKey '["user_id"] "users" '["id"]
+        ] :=>
+        '[ "id"      :::   'Def :=> 'NotNull 'PGint4
+        , "user_id" ::: 'NoDef :=> 'NotNull 'PGint4
+        , "email"   ::: 'NoDef :=>    'Null 'PGtext
+        ])
+    ]
+:}
+-}
+type SchemaType = [(Symbol,SchemumType)]
 
 -- | `IsPGlabel` looks very much like the `IsLabel` class. Whereas
 -- the overloaded label, `fromLabel` is used for column references,
@@ -749,105 +671,166 @@
 -- | Renders a label
 renderLabel :: KnownSymbol label => proxy label -> ByteString
 renderLabel (_ :: proxy label) =
-  "\'" <> fromString (symbolVal (Proxy @label)) <> "\'"
+  "\'" <> renderSymbol @label <> "\'"
 -- | Renders a list of labels
 renderLabels
   :: All KnownSymbol labels => NP PGlabel labels -> [ByteString]
 renderLabels = hcollapse
   . hcmap (Proxy @KnownSymbol) (K . renderLabel)
 
--- | The `PG` type family embeds a subset of Haskell types
--- as Postgres basic types.
---
--- >>> :kind! PG LocalTime
--- PG LocalTime :: PGType
--- = 'PGtimestamp
-type family PG (hask :: Type) :: PGType where
-  PG Bool = 'PGbool
-  PG Int16 = 'PGint2
-  PG Int32 = 'PGint4
-  PG Int64 = 'PGint8
-  PG Word16 = 'PGint2
-  PG Word32 = 'PGint4
-  PG Word64 = 'PGint8
-  PG Scientific = 'PGnumeric
-  PG Float = 'PGfloat4
-  PG Double = 'PGfloat8
-  PG Char = 'PGchar 1
-  PG Text = 'PGtext
-  PG Lazy.Text = 'PGtext
-  PG ByteString = 'PGbytea
-  PG Lazy.ByteString = 'PGbytea
-  PG LocalTime = 'PGtimestamp
-  PG UTCTime = 'PGtimestamptz
-  PG Day = 'PGdate
-  PG TimeOfDay = 'PGtime
-  PG (TimeOfDay, TimeZone) = 'PGtimetz
-  PG DiffTime = 'PGinterval
-  PG UUID = 'PGuuid
-  PG (NetAddr IP) = 'PGinet
-  PG Value = 'PGjson
-  PG ty = TypeError
-    ('Text "There is no Postgres basic type for " ':<>: 'ShowType ty)
+{- | The `PG` type family embeds a subset of Haskell types
+as Postgres types. As an open type family, `PG` is extensible.
 
--- | The `EnumFrom` type family embeds Haskell enum types, ADTs with
--- nullary constructors, as Postgres enum types
---
--- >>> data Schwarma = Beef | Lamb | Chicken deriving GHC.Generic
--- >>> instance Generic Schwarma
--- >>> instance HasDatatypeInfo Schwarma
--- >>> :kind! EnumFrom Schwarma
--- EnumFrom Schwarma :: PGType
--- = 'PGenum '["Beef", "Lamb", "Chicken"]
-type family EnumFrom (hask :: Type) :: PGType where
-  EnumFrom hask = 'PGenum (LabelsFrom hask)
+>>> :kind! PG LocalTime
+PG LocalTime :: PGType
+= 'PGtimestamp
 
--- | The `LabelsFrom` type family calculates the constructors of a
--- Haskell enum type.
---
--- >>> data Schwarma = Beef | Lamb | Chicken deriving GHC.Generic
--- >>> instance Generic Schwarma
--- >>> instance HasDatatypeInfo Schwarma
--- >>> :kind! LabelsFrom Schwarma
--- LabelsFrom Schwarma :: [Type.ConstructorName]
--- = '["Beef", "Lamb", "Chicken"]
-type family LabelsFrom (hask :: Type) :: [Type.ConstructorName] where
-  LabelsFrom hask =
+>>> newtype MyDouble = My Double
+>>> type instance PG MyDouble = 'PGfloat8
+-}
+type family PG (hask :: Type) :: PGType
+type instance PG Bool = 'PGbool
+type instance PG Int16 = 'PGint2
+type instance PG Int32 = 'PGint4
+type instance PG Int64 = 'PGint8
+type instance PG Word16 = 'PGint2
+type instance PG Word32 = 'PGint4
+type instance PG Word64 = 'PGint8
+type instance PG Scientific = 'PGnumeric
+type instance PG Float = 'PGfloat4
+type instance PG Double = 'PGfloat8
+type instance PG Char = 'PGchar 1
+type instance PG Text = 'PGtext
+type instance PG Lazy.Text = 'PGtext
+type instance PG String = 'PGtext
+type instance PG ByteString = 'PGbytea
+type instance PG Lazy.ByteString = 'PGbytea
+type instance PG LocalTime = 'PGtimestamp
+type instance PG UTCTime = 'PGtimestamptz
+type instance PG Day = 'PGdate
+type instance PG TimeOfDay = 'PGtime
+type instance PG (TimeOfDay, TimeZone) = 'PGtimetz
+type instance PG DiffTime = 'PGinterval
+type instance PG UUID = 'PGuuid
+type instance PG (NetAddr IP) = 'PGinet
+type instance PG Value = 'PGjson
+type instance PG (Json hask) = 'PGjson
+type instance PG (Jsonb hask) = 'PGjsonb
+type instance PG (Vector hask) = 'PGvararray (NullPG hask)
+type instance PG (hask, hask) = 'PGfixarray 2 (NullPG hask)
+type instance PG (hask, hask, hask) = 'PGfixarray 3 (NullPG hask)
+type instance PG (hask, hask, hask, hask) = 'PGfixarray 4 (NullPG hask)
+type instance PG (hask, hask, hask, hask, hask) = 'PGfixarray 5 (NullPG hask)
+type instance PG (hask, hask, hask, hask, hask, hask)
+  = 'PGfixarray 6 (NullPG hask)
+type instance PG (hask, hask, hask, hask, hask, hask, hask)
+  = 'PGfixarray 7 (NullPG hask)
+type instance PG (hask, hask, hask, hask, hask, hask, hask, hask)
+  = 'PGfixarray 8 (NullPG hask)
+type instance PG (hask, hask, hask, hask, hask, hask, hask, hask, hask)
+  = 'PGfixarray 9 (NullPG hask)
+type instance PG (hask, hask, hask, hask, hask, hask, hask, hask, hask, hask)
+  = 'PGfixarray 10 (NullPG hask)
+type instance PG (Composite hask) = 'PGcomposite (RowPG hask)
+type instance PG (Enumerated hask) = 'PGenum (LabelsPG hask)
+
+{- | The `Json` newtype is an indication that the Haskell
+type it's applied to should be stored as `PGjson`.
+-}
+newtype Json hask = Json {getJson :: hask}
+  deriving (Eq, Ord, Show, Read, GHC.Generic)
+
+{- | The `Jsonb` newtype is an indication that the Haskell
+type it's applied to should be stored as `PGjsonb`.
+-}
+newtype Jsonb hask = Jsonb {getJsonb :: hask}
+  deriving (Eq, Ord, Show, Read, GHC.Generic)
+
+{- | The `Composite` newtype is an indication that the Haskell
+type it's applied to should be stored as a `PGcomposite`.
+-}
+newtype Composite record = Composite {getComposite :: record}
+  deriving (Eq, Ord, Show, Read, GHC.Generic)
+
+{- | The `Enumerated` newtype is an indication that the Haskell
+type it's applied to should be stored as `PGenum`.
+-}
+newtype Enumerated enum = Enumerated {getEnumerated :: enum}
+  deriving (Eq, Ord, Show, Read, GHC.Generic)
+
+{-| The `LabelsPG` type family calculates the constructors of a
+Haskell enum type.
+
+>>> data Schwarma = Beef | Lamb | Chicken deriving GHC.Generic
+>>> instance Generic Schwarma
+>>> instance HasDatatypeInfo Schwarma
+>>> :kind! LabelsPG Schwarma
+LabelsPG Schwarma :: [Type.ConstructorName]
+= '["Beef", "Lamb", "Chicken"]
+-}
+type family LabelsPG (hask :: Type) :: [Type.ConstructorName] where
+  LabelsPG hask =
     ConstructorNamesOf (ConstructorsOf (DatatypeInfoOf hask))
 
--- | The `CompositeFrom` type family embeds Haskell record types as
--- Postgres composite types, as long as the record fields
--- are `Maybe`s of Haskell types that can be embedded as basic types
--- with the `PG` type family.
---
--- >>> data Row = Row { a :: Maybe Int16, b :: Maybe LocalTime } deriving GHC.Generic
--- >>> instance Generic Row
--- >>> instance HasDatatypeInfo Row
--- >>> :kind! CompositeFrom Row
--- CompositeFrom Row :: PGType
--- = 'PGcomposite '['("a", 'PGint2), '("b", 'PGtimestamp)]
-type family CompositeFrom (hask :: Type) :: PGType where
-  CompositeFrom hask =
-    'PGcomposite (ZipAs (FieldNamesFrom hask) (FieldTypesFrom hask))
+{- | `RowPG` turns a Haskell record type into a `RowType`.
 
--- | >>> data Row = Row { a :: Maybe Int16, b :: Maybe LocalTime } deriving GHC.Generic
--- >>> instance Generic Row
--- >>> instance HasDatatypeInfo Row
--- >>> :kind! FieldNamesFrom Row
--- FieldNamesFrom Row :: [Type.FieldName]
--- = '["a", "b"]
-type family FieldNamesFrom (hask :: Type) :: [Type.FieldName] where
-  FieldNamesFrom hask = FieldNamesOf (FieldsOf (DatatypeInfoOf hask))
+>>> data Person = Person { name :: Text, age :: Int32 } deriving GHC.Generic
+>>> instance Generic Person
+>>> instance HasDatatypeInfo Person
+>>> :kind! RowPG Person
+RowPG Person :: [(Symbol, NullityType)]
+= '["name" ::: 'NotNull 'PGtext, "age" ::: 'NotNull 'PGint4]
+-}
+type family RowPG (hask :: Type) :: RowType where
+  RowPG hask = RowOf (RecordCodeOf hask)
 
--- | >>> data Row = Row { a :: Maybe Int16, b :: Maybe LocalTime } deriving GHC.Generic
--- >>> instance Generic Row
--- >>> instance HasDatatypeInfo Row
--- >>> :kind! FieldTypesFrom Row
--- FieldTypesFrom Row :: [PGType]
--- = '['PGint2, 'PGtimestamp]
-type family FieldTypesFrom (hask :: Type) :: [PGType] where
-  FieldTypesFrom hask = FieldTypesOf (RecordCodeOf hask (Code hask))
+type family RowOf (fields :: [(Symbol, Type)]) :: RowType where
+  RowOf '[] = '[]
+  RowOf (field ': fields) = FieldPG field ': RowOf fields
 
+type family FieldPG (field :: (Symbol, Type)) :: (Symbol, NullityType) where
+  FieldPG (field ::: hask) = field ::: NullPG hask
+
+{- | `NullPG` turns a Haskell type into a `NullityType`.
+
+>>> :kind! NullPG Double
+NullPG Double :: NullityType
+= 'NotNull 'PGfloat8
+>>> :kind! NullPG (Maybe Double)
+NullPG (Maybe Double) :: NullityType
+= 'Null 'PGfloat8
+-}
+type family NullPG (hask :: Type) :: NullityType where
+  NullPG (Maybe hask) = 'Null (PG hask)
+  NullPG hask = 'NotNull (PG hask)
+
+{- | `TuplePG` turns a Haskell tuple type (including record types) into
+the corresponding list of `NullityType`s.
+
+>>> :kind! TuplePG (Double, Maybe Char)
+TuplePG (Double, Maybe Char) :: [NullityType]
+= '['NotNull 'PGfloat8, 'Null ('PGchar 1)]
+-}
+type family TuplePG (hask :: Type) :: [NullityType] where
+  TuplePG hask = TupleOf (TupleCodeOf hask (Code hask))
+
+type family TupleOf (tuple :: [Type]) :: [NullityType] where
+  TupleOf '[] = '[]
+  TupleOf (hask ': tuple) = NullPG hask ': TupleOf tuple
+
+type family TupleCodeOf (hask :: Type) (code :: [[Type]]) :: [Type] where
+  TupleCodeOf hask '[tuple] = tuple
+  TupleCodeOf hask '[] =
+    TypeError
+      (    'Text "The type `" :<>: 'ShowType hask :<>: 'Text "' is not a tuple type."
+      :$$: 'Text "It is a void type with no constructors."
+      )
+  TupleCodeOf hask (_ ': _ ': _) =
+    TypeError
+      (    'Text "The type `" :<>: 'ShowType hask :<>: 'Text "' is not a tuple type."
+      :$$: 'Text "It is a sum type with more than one constructor."
+      )
+
 -- | Calculates constructors of a datatype.
 type family ConstructorsOf (datatype :: Type.DatatypeInfo)
   :: [Type.ConstructorInfo] where
@@ -875,47 +858,23 @@
     ConstructorNamesOf (constructor ': constructors) =
       ConstructorNameOf constructor ': ConstructorNamesOf constructors
 
--- | Calculate the fields of a datatype.
-type family FieldsOf (datatype :: Type.DatatypeInfo)
-  :: [Type.FieldInfo] where
-    FieldsOf ('Type.ADT _module _datatype '[ 'Type.Record _name fields]) =
-      fields
-    FieldsOf ('Type.Newtype _module _datatype ('Type.Record _name fields)) =
-      fields
-
--- | Calculate the name of a field.
-type family FieldNameOf (field :: Type.FieldInfo) :: Type.FieldName where
-  FieldNameOf ('Type.FieldInfo name) = name
-
--- | Calculate the names of fields.
-type family FieldNamesOf (fields :: [Type.FieldInfo])
-  :: [Type.FieldName] where
-    FieldNamesOf '[] = '[]
-    FieldNamesOf (field ': fields) = FieldNameOf field ': FieldNamesOf fields
-
--- | >>> :kind! FieldTypeOf (Maybe Int16)
--- FieldTypeOf (Maybe Int16) :: PGType
--- = 'PGint2
-type family FieldTypeOf (maybe :: Type) where
-  FieldTypeOf (Maybe hask) = PG hask
-  FieldTypeOf ty = TypeError
-    ('Text "FieldTypeOf error: non-Maybe type " ':<>: 'ShowType ty)
-
--- | Calculate the types of fields.
-type family FieldTypesOf (fields :: [Type]) where
-  FieldTypesOf '[] = '[]
-  FieldTypesOf (field ': fields) = FieldTypeOf field ': FieldTypesOf fields
-
--- | Inspect the code of an algebraic datatype and ensure it's a product,
--- otherwise generate a type error
-type family RecordCodeOf (hask :: Type) (code ::[[Type]]) :: [Type] where
-  RecordCodeOf _hask '[tys] = tys
-  RecordCodeOf hask _tys = TypeError
-    ('Text "RecordCodeOf error: non-Record type " ':<>: 'ShowType hask)
-
 -- | Is a type a valid JSON key?
-type PGjsonKey key = key `In` '[ 'PGint2, 'PGint4, 'PGtext ]
+type PGJsonKey = '[ 'PGint2, 'PGint4, 'PGtext ]
 
 -- | Is a type a valid JSON type?
-type PGjson_ json = json `In` '[ 'PGjson, 'PGjsonb ]
+type PGJsonType = '[ 'PGjson, 'PGjsonb ]
 
+-- | An `AlignedList` is a type-aligned list or free category.
+data AlignedList p x0 x1 where
+  Done :: AlignedList p x x
+  (:>>) :: p x0 x1 -> AlignedList p x1 x2 -> AlignedList p x0 x2
+infixr 7 :>>
+instance Category (AlignedList p) where
+  id = Done
+  (.) list = \case
+    Done -> list
+    step :>> steps -> step :>> (steps >>> list)
+
+-- | A `single` step.
+single :: p x0 x1 -> AlignedList p x0 x1
+single step = step :>> Done
diff --git a/src/Squeal/PostgreSQL/Transaction.hs b/src/Squeal/PostgreSQL/Transaction.hs
--- a/src/Squeal/PostgreSQL/Transaction.hs
+++ b/src/Squeal/PostgreSQL/Transaction.hs
@@ -9,9 +9,11 @@
 -}
 
 {-# LANGUAGE
-    FlexibleContexts
+    DataKinds
+  , FlexibleContexts
   , LambdaCase
   , OverloadedStrings
+  , TypeInType
 #-}
 
 module Squeal.PostgreSQL.Transaction
@@ -39,7 +41,6 @@
 import Control.Monad.Base
 import Control.Monad.Trans.Control
 import Data.ByteString
-import Data.Monoid
 import Generics.SOP
 
 import qualified Database.PostgreSQL.LibPQ as LibPQ
@@ -69,16 +70,16 @@
 transactionally_ = transactionally defaultMode
 
 -- | @BEGIN@ a transaction.
-begin :: MonadPQ schema tx => TransactionMode -> tx (K Result NilRelation)
+begin :: MonadPQ schema tx => TransactionMode -> tx (K Result ('[] :: RowType))
 begin mode = manipulate . UnsafeManipulation $
   "BEGIN" <+> renderTransactionMode mode <> ";"
 
 -- | @COMMIT@ a schema invariant transaction.
-commit :: MonadPQ schema tx => tx (K Result NilRelation)
+commit :: MonadPQ schema tx => tx (K Result ('[] :: RowType))
 commit = manipulate $ UnsafeManipulation "COMMIT;"
 
 -- | @ROLLBACK@ a schema invariant transaction.
-rollback :: MonadPQ schema tx => tx (K Result NilRelation)
+rollback :: MonadPQ schema tx => tx (K Result ('[] :: RowType))
 rollback = manipulate $ UnsafeManipulation "ROLLBACK;"
 
 -- | Run a schema changing computation `transactionallySchema`.
diff --git a/test/Specs/ExceptionHandling.hs b/test/Specs/ExceptionHandling.hs
new file mode 100644
--- /dev/null
+++ b/test/Specs/ExceptionHandling.hs
@@ -0,0 +1,121 @@
+{-# LANGUAGE DataKinds             #-}
+{-# LANGUAGE DeriveGeneric         #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE FlexibleContexts      #-}
+{-# LANGUAGE OverloadedLabels      #-}
+{-# LANGUAGE OverloadedLists       #-}
+{-# LANGUAGE OverloadedStrings     #-}
+{-# LANGUAGE TypeApplications      #-}
+{-# LANGUAGE TypeFamilies          #-}
+{-# LANGUAGE TypeOperators         #-}
+module ExceptionHandling
+  ( specs
+  , User (..)
+  )
+where
+
+import           Control.Monad               (void)
+import           Control.Monad.Base          (MonadBase)
+import qualified Data.ByteString.Char8       as Char8
+import           Data.Int                    (Int16)
+import           Data.Text                   (Text)
+import           Data.Vector                 (Vector)
+import qualified Generics.SOP                as SOP
+import qualified GHC.Generics                as GHC
+import           Squeal.PostgreSQL
+import           Squeal.PostgreSQL.Migration
+import           Test.Hspec
+
+type Schema =
+  '[ "users" ::: 'Table (
+       '[ "pk_users" ::: 'PrimaryKey '["id"]
+        , "unique_names" ::: 'Unique '["name"]
+        ] :=>
+       '[ "id" ::: 'Def :=> 'NotNull 'PGint4
+        , "name" ::: 'NoDef :=> 'NotNull 'PGtext
+        , "vec" ::: 'NoDef :=> 'NotNull ('PGvararray ('Null 'PGint2))
+        ])
+   , "emails" ::: 'Table (
+       '[  "pk_emails" ::: 'PrimaryKey '["id"]
+        , "fk_user_id" ::: 'ForeignKey '["user_id"] "users" '["id"]
+        ] :=>
+       '[ "id" ::: 'Def :=> 'NotNull 'PGint4
+        , "user_id" ::: 'NoDef :=> 'NotNull 'PGint4
+        , "email" ::: 'NoDef :=> 'Null 'PGtext
+        ])
+   ]
+
+data User =
+  User { userName  :: Text
+       , userEmail :: Maybe Text
+       , userVec   :: Vector (Maybe Int16) }
+  deriving (Show, GHC.Generic)
+instance SOP.Generic User
+instance SOP.HasDatatypeInfo User
+
+insertUser :: Manipulation Schema '[ 'NotNull 'PGtext, 'NotNull ('PGvararray ('Null 'PGint2))]
+  '[ "fromOnly" ::: 'NotNull 'PGint4 ]
+insertUser = insertRows #users
+  (Default `as` #id :* Set (param @1) `as` #name :* Set (param @2) `as` #vec) []
+  OnConflictDoRaise (Returning (#id `as` #fromOnly))
+
+setup :: Definition '[] Schema
+setup =
+  createTable #users
+    ( serial `as` #id :*
+      (text & notNullable) `as` #name :*
+      (vararray int2 & notNullable) `as` #vec )
+    ( primaryKey #id `as` #pk_users
+    :* unique #name `as` #unique_names )
+  >>>
+  createTable #emails
+    ( serial `as` #id :*
+      (int & notNullable) `as` #user_id :*
+      (text & nullable) `as` #email )
+    ( primaryKey #id `as` #pk_emails :*
+      foreignKey #user_id #users #id
+        OnDeleteCascade OnUpdateCascade `as` #fk_user_id )
+
+teardown :: Definition Schema '[]
+teardown = dropTable #emails >>> dropTable #users
+
+migration :: Migration IO '[] Schema
+migration = Migration { name = "test"
+                      , up = void $ define setup
+                      , down = void $ define teardown }
+
+setupDB :: IO ()
+setupDB = void . withConnection connectionString $
+  migrateUp $ single migration
+
+dropDB :: IO ()
+dropDB = void . withConnection connectionString $
+  migrateDown $ single migration
+
+connectionString :: Char8.ByteString
+connectionString = "host=localhost port=5432 dbname=exampledb"
+
+testUser :: User
+testUser = User "TestUser" Nothing []
+
+newUser :: (MonadBase IO m, MonadPQ Schema m) => User -> m ()
+newUser u = void $ manipulateParams insertUser (userName u, userVec u)
+
+insertUserTwice :: (MonadBase IO m, MonadPQ Schema m) => m ()
+insertUserTwice = newUser testUser >> newUser testUser
+
+specs :: SpecWith ()
+specs = before_ setupDB $ after_ dropDB $
+  describe "Exceptions" $ do
+
+    let
+      dupKeyErr = PQException FatalError (Just "23505")
+        (Just "ERROR:  duplicate key value violates unique constraint \"unique_names\"\nDETAIL:  Key (name)=(TestUser) already exists.\n")
+
+    it "should be thrown for unique constraint violation in a manipulation" $
+      withConnection connectionString insertUserTwice
+       `shouldThrow` (== dupKeyErr)
+
+    it "should be rethrown for unique constraint violation in a manipulation by a transaction" $
+      withConnection connectionString (transactionally_ insertUserTwice)
+       `shouldThrow` (== dupKeyErr)
diff --git a/test/Specs/Specs.hs b/test/Specs/Specs.hs
new file mode 100644
--- /dev/null
+++ b/test/Specs/Specs.hs
@@ -0,0 +1,7 @@
+module Specs where
+
+import           Test.Hspec
+import qualified ExceptionHandling
+
+main :: IO ()
+main = hspec ExceptionHandling.specs
