diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
 
 ![squeal-icon](https://raw.githubusercontent.com/morphismtech/squeal/dev/squeal.gif)
 
-[![CircleCI](https://circleci.com/gh/echatav/squeal.svg?style=svg&circle-token=a699a654ef50db2c3744fb039cf2087c484d1226)](https://circleci.com/gh/morphismtech/squeal)
+[![GithubWorkflowCI](https://github.com/morphismtech/squeal/actions/workflows/ci.yml/badge.svg)](https://github.com/morphismtech/squeal/actions/workflows/ci.yml)
 
 [Github](https://github.com/morphismtech/squeal)
 
diff --git a/exe/Example.hs b/exe/Example.hs
--- a/exe/Example.hs
+++ b/exe/Example.hs
@@ -9,8 +9,15 @@
   , TypeOperators
 #-}
 
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeFamilies #-}
+
 module Main (main, main2, upsertUser) where
 
+import Control.Monad.Except (MonadError (throwError))
 import Control.Monad.IO.Class (MonadIO (..))
 import Data.Int (Int16, Int32)
 import Data.Text (Text)
@@ -46,6 +53,7 @@
         '[ "pk_organizations" ::: 'PrimaryKey '["id"] ] :=>
         '[ "id" ::: 'Def :=> 'NotNull 'PGint4
          , "name" ::: 'NoDef :=> 'NotNull 'PGtext
+         , "type" ::: 'NoDef :=> 'NotNull 'PGtext
          ])
    , "members" ::: 'Table (
         '[ "fk_member" ::: 'ForeignKey '["member"] "user" "users" '["id"]
@@ -54,7 +62,7 @@
          , "organization" ::: 'NoDef :=> 'NotNull 'PGint4 ])
    ]
 
-type Schemas 
+type Schemas
   = '[ "public" ::: PublicSchema, "user" ::: UserSchema, "org" ::: OrgSchema ]
 
 setup :: Definition (Public '[]) Schemas
@@ -83,7 +91,8 @@
   >>>
   createTable (#org ! #organizations)
     ( serial `as` #id :*
-      (text & notNullable) `as` #name )
+      (text & notNullable) `as` #name :*
+      (text & notNullable) `as` #type )
     ( primaryKey #id `as` #pk_organizations )
   >>>
   createTable (#org ! #members)
@@ -93,7 +102,7 @@
         (OnDelete Cascade) (OnUpdate Cascade) `as` #fk_member :*
       foreignKey #organization (#org ! #organizations) #id
         (OnDelete Cascade) (OnUpdate Cascade) `as` #fk_organization )
-      
+
 teardown :: Definition Schemas (Public '[])
 teardown = dropType #positive >>> dropSchemaCascade #user >>> dropSchemaCascade #org
 
@@ -106,6 +115,11 @@
 insertEmail = insertInto_ (#user ! #emails)
   (Values_ (Default `as` #id :* Set (param @1) `as` #user_id :* Set (param @2) `as` #email))
 
+insertOrganization :: Manipulation_ Schemas (Text, OrganizationType) (Only Int32)
+insertOrganization = insertInto (#org ! #organizations)
+  (Values_ (Default `as` #id :* Set (param @1) `as` #name :* Set (param @2) `as` #type))
+  (OnConflict (OnConstraint #pk_organizations) DoNothing) (Returning_ (#id `as` #fromOnly))
+
 getUsers :: Query_ Schemas () User
 getUsers = select_
   (#u ! #name `as` #userName :* #e ! #email `as` #userEmail :* #u ! #vec `as` #userVec)
@@ -113,6 +127,36 @@
     & innerJoin (table ((#user ! #emails) `as` #e))
       (#u ! #id .== #e ! #user_id)) )
 
+getOrganizations :: Query_ Schemas () Organization
+getOrganizations = select_
+  ( #o ! #id `as` #orgId :*
+    #o ! #name `as` #orgName :*
+    #o ! #type `as` #orgType
+  )
+  (from (table (#org ! #organizations `as` #o)))
+
+getOrganizationsBy ::
+  forall hsty.
+  (ToPG Schemas hsty) =>
+  Condition
+    'Ungrouped
+    '[]
+    '[]
+    Schemas
+    '[NullPG hsty]
+    '["o" ::: ["id" ::: NotNull PGint4, "name" ::: NotNull PGtext, "type" ::: NotNull PGtext]] ->
+  Query_ Schemas (Only hsty) Organization
+getOrganizationsBy condition =
+  select_
+    ( #o ! #id `as` #orgId :*
+      #o ! #name `as` #orgName :*
+      #o ! #type `as` #orgType
+    )
+    (
+      from (table (#org ! #organizations `as` #o))
+      & where_ condition
+    )
+
 upsertUser :: Manipulation_ Schemas (Int32, String, VarArray [Maybe Int16]) ()
 upsertUser = insertInto (#user ! #users `as` #u)
   (Values_ (Set (param @1) `as` #id :* setUser))
@@ -137,28 +181,98 @@
   , User "Carole" (Just "carole@hotmail.com") (VarArray [Just 3,Nothing, Just 4])
   ]
 
+data Organization
+  = Organization
+  { orgId :: Int32
+  , orgName :: Text
+  , orgType :: OrganizationType
+  } deriving (Show, GHC.Generic)
+instance SOP.Generic Organization
+instance SOP.HasDatatypeInfo Organization
+
+data OrganizationType
+  = ForProfit
+  | NonProfit
+  deriving (Show, GHC.Generic)
+instance SOP.Generic OrganizationType
+instance SOP.HasDatatypeInfo OrganizationType
+
+instance IsPG OrganizationType where
+  type PG OrganizationType = 'PGtext
+instance ToPG db OrganizationType where
+  toPG = toPG . toText
+    where
+      toText ForProfit = "for-profit" :: Text
+      toText NonProfit = "non-profit" :: Text
+
+instance FromPG OrganizationType where
+  fromPG = do
+    value <- fromPG @Text
+    fromText value
+    where
+      fromText "for-profit" = pure ForProfit
+      fromText "non-profit" = pure NonProfit
+      fromText value = throwError $ "Invalid organization type: \"" <> value <> "\""
+
+organizations :: [Organization]
+organizations =
+  [ Organization { orgId = 1, orgName = "ACME", orgType = ForProfit }
+  , Organization { orgId = 2, orgName = "Haskell Foundation", orgType = NonProfit }
+  ]
+
 session :: (MonadIO pq, MonadPQ Schemas pq) => pq ()
 session = do
-  liftIO $ Char8.putStrLn "manipulating"
-  idResults <- traversePrepared insertUser ([(userName user, userVec user) | user <- users])
-  ids <- traverse (fmap fromOnly . getRow 0) (idResults :: [Result (Only Int32)])
-  traversePrepared_ insertEmail (zip (ids :: [Int32]) (userEmail <$> users))
-  liftIO $ Char8.putStrLn "querying"
+  liftIO $ Char8.putStrLn "===> manipulating"
+  userIdResults <- traversePrepared insertUser [(userName user, userVec user) | user <- users]
+  userIds <- traverse (fmap fromOnly . getRow 0) (userIdResults :: [Result (Only Int32)])
+  traversePrepared_ insertEmail (zip (userIds :: [Int32]) (userEmail <$> users))
+
+  orgIdResults <- traversePrepared
+    insertOrganization
+    [(orgName organization, orgType organization) | organization <- organizations]
+  _ <- traverse (fmap fromOnly . getRow 0) (orgIdResults :: [Result (Only Int32)])
+
+  liftIO $ Char8.putStrLn "===> querying: users"
   usersResult <- runQuery getUsers
   usersRows <- getRows usersResult
   liftIO $ print (usersRows :: [User])
 
+  liftIO $ Char8.putStrLn "===> querying: organizations: all"
+  organizationsResult1 <- runQuery getOrganizations
+  organizationRows1 <- getRows organizationsResult1
+  liftIO $ print (organizationRows1 :: [Organization])
+
+  liftIO $ Char8.putStrLn "===> querying: organizations: by ID (2)"
+  organizationsResult2 <- runQueryParams
+    (getOrganizationsBy @Int32 ((#o ! #id) .== param @1)) (Only (2 :: Int32))
+  organizationRows2 <- getRows organizationsResult2
+  liftIO $ print (organizationRows2 :: [Organization])
+
+  liftIO $ Char8.putStrLn "===> querying: organizations: by name (ACME)"
+  organizationsResult3 <- runQueryParams
+    (getOrganizationsBy @Text ((#o ! #name) .== param @1)) (Only ("ACME" :: Text))
+  organizationRows3 <- getRows organizationsResult3
+  liftIO $ print (organizationRows3 :: [Organization])
+
+  liftIO $ Char8.putStrLn "===> querying: organizations: by type (non-profit)"
+  organizationsResult4 <- runQueryParams
+    (getOrganizationsBy @Text ((#o ! #type) .== param @1)) (Only NonProfit)
+  organizationRows4 <- getRows organizationsResult4
+  liftIO $ print (organizationRows4 :: [Organization])
+
 main :: IO ()
 main = do
-  Char8.putStrLn "squeal"
+  Char8.putStrLn "===> squeal"
   connectionString <- pure
     "host=localhost port=5432 dbname=exampledb user=postgres password=postgres"
   Char8.putStrLn $ "connecting to " <> connectionString
   connection0 <- connectdb connectionString
-  Char8.putStrLn "setting up schema"
+
+  Char8.putStrLn "===> setting up schema"
   connection1 <- execPQ (define setup) connection0
   connection2 <- execPQ session connection1
-  Char8.putStrLn "tearing down schema"
+
+  Char8.putStrLn "===> tearing down schema"
   connection3 <- execPQ (define teardown) connection2
   finish connection3
 
diff --git a/squeal-postgresql.cabal b/squeal-postgresql.cabal
--- a/squeal-postgresql.cabal
+++ b/squeal-postgresql.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.2
 name: squeal-postgresql
-version: 0.9.1.3
+version: 0.9.2.0
 synopsis: Squeal PostgreSQL Library
 description: Squeal is a type-safe embedding of PostgreSQL in Haskell
 homepage: https://github.com/morphismtech/squeal
@@ -99,6 +99,7 @@
     , free-categories >= 0.2.0.0
     , generics-sop >= 0.5.1.0
     , hashable >= 1.3.0.0
+    , iproute >= 1.7.0
     , mmorph >= 1.1.3
     , monad-control >= 1.0.2.3
     , mtl >= 2.2.2
diff --git a/src/Squeal/PostgreSQL/Expression/Array.hs b/src/Squeal/PostgreSQL/Expression/Array.hs
--- a/src/Squeal/PostgreSQL/Expression/Array.hs
+++ b/src/Squeal/PostgreSQL/Expression/Array.hs
@@ -37,6 +37,15 @@
   , unnest
   , arrAny
   , arrAll
+  , arrayAppend
+  , arrayPrepend
+  , arrayCat
+  , arrayPosition
+  , arrayPositionBegins
+  , arrayPositions
+  , arrayRemoveNull
+  , arrayReplace
+  , trimArray
   ) where
 
 import Data.String
@@ -47,6 +56,7 @@
 
 import Squeal.PostgreSQL.Expression
 import Squeal.PostgreSQL.Expression.Logic
+import Squeal.PostgreSQL.Expression.Null
 import Squeal.PostgreSQL.Expression.Type
 import Squeal.PostgreSQL.Query.From.Set
 import Squeal.PostgreSQL.Render
@@ -240,3 +250,42 @@
   -> Expression grp lat with db params from (null ('PGvararray ty2)) -- ^ array
   -> Condition grp lat with db params from
 arrAny x (?) xs = x ? (UnsafeExpression $ "ANY" <+> parenthesized (renderSQL xs))
+
+arrayAppend :: '[null ('PGvararray ty), ty] ---> null ('PGvararray ty)
+arrayAppend = unsafeFunctionN "array_append"
+
+arrayPrepend :: '[ty, null ('PGvararray ty)] ---> null ('PGvararray ty)
+arrayPrepend = unsafeFunctionN "array_prepend"
+
+arrayCat
+  :: '[null ('PGvararray ty), null ('PGvararray ty)]
+  ---> null ('PGvararray ty)
+arrayCat = unsafeFunctionN "array_cat"
+
+arrayPosition :: '[null ('PGvararray ty), ty] ---> 'Null 'PGint8
+arrayPosition = unsafeFunctionN "array_position"
+
+arrayPositionBegins
+  :: '[null ('PGvararray ty), ty, null 'PGint8] ---> 'Null 'PGint8
+arrayPositionBegins = unsafeFunctionN "array_position"
+
+arrayPositions
+  :: '[null ('PGvararray ty), ty]
+  ---> null ('PGvararray ('NotNull 'PGint8))
+arrayPositions = unsafeFunctionN "array_positions"
+
+arrayRemove :: '[null ('PGvararray ty), ty] ---> null ('PGvararray ty)
+arrayRemove = unsafeFunctionN "array_remove"
+
+arrayRemoveNull :: null ('PGvararray ('Null ty)) --> null ('PGvararray ('NotNull ty))
+arrayRemoveNull arr = UnsafeExpression (renderSQL (arrayRemove (arr *: null_)))
+
+arrayReplace
+  :: '[null ('PGvararray ty), ty, ty]
+  ---> null ('PGvararray ty)
+arrayReplace = unsafeFunctionN "array_replace"
+
+trimArray
+  :: '[null ('PGvararray ty), 'NotNull 'PGint8]
+  ---> null ('PGvararray ty)
+trimArray = unsafeFunctionN "trim_array"
diff --git a/src/Squeal/PostgreSQL/Session.hs b/src/Squeal/PostgreSQL/Session.hs
--- a/src/Squeal/PostgreSQL/Session.hs
+++ b/src/Squeal/PostgreSQL/Session.hs
@@ -44,9 +44,8 @@
 import Control.Monad.Base (MonadBase(..))
 import Control.Monad.Fix (MonadFix(..))
 import Control.Monad.Catch
-import Control.Monad.IO.Class (MonadIO(..))
 import Control.Monad.Morph
-import Control.Monad.Reader (ReaderT(..))
+import Control.Monad.Reader
 import Control.Monad.Trans.Control (MonadBaseControl(..), MonadTransControl(..))
 import UnliftIO (MonadUnliftIO(..))
 import Data.ByteString (ByteString)
@@ -214,9 +213,9 @@
   return = pure
   (>>=) = flip pqBind
 
-instance (Monad m, db0 ~ db1)
+instance (MonadFail m, db0 ~ db1)
   => Fail.MonadFail (PQ db0 db1 m) where
-  fail = Fail.fail
+  fail = lift . Fail.fail
 
 instance db0 ~ db1 => MFunctor (PQ db0 db1) where
   hoist f (PQ pq) = PQ (f . pq)
diff --git a/src/Squeal/PostgreSQL/Session/Decode.hs b/src/Squeal/PostgreSQL/Session/Decode.hs
--- a/src/Squeal/PostgreSQL/Session/Decode.hs
+++ b/src/Squeal/PostgreSQL/Session/Decode.hs
@@ -43,6 +43,7 @@
   , genericProductRow
   , appendRows
   , consRow
+  , ArrayField (..)
     -- * Decoding Classes
   , FromValue (..)
   , FromField (..)
@@ -68,6 +69,11 @@
 import Data.Coerce (coerce)
 import Data.Functor.Constant (Constant(Constant))
 import Data.Int (Int16, Int32, Int64)
+#if MIN_VERSION_postgresql_binary(0, 14, 0)
+import Data.IP (IPRange)
+#else
+import Network.IP.Addr (NetAddr, IP)
+#endif
 import Data.Kind
 import Data.Scientific (Scientific)
 import Data.String (fromString)
@@ -78,7 +84,6 @@
 import Database.PostgreSQL.LibPQ (Oid(Oid))
 import GHC.OverloadedLabels
 import GHC.TypeLits
-import Network.IP.Addr (NetAddr, IP)
 import PostgreSQL.Binary.Decoding hiding (Composite)
 import Unsafe.Coerce
 
@@ -200,8 +205,11 @@
   fromPG = devalue $  Money <$> int
 instance FromPG UUID where
   fromPG = devalue uuid
-instance FromPG (NetAddr IP) where
-  fromPG = devalue inet
+#if MIN_VERSION_postgresql_binary(0, 14, 0)
+instance FromPG IPRange where fromPG = devalue inet
+#else
+instance FromPG (NetAddr IP) where fromPG = devalue inet
+#endif
 instance FromPG Char where
   fromPG = devalue char
 instance FromPG Strict.Text where
@@ -532,6 +540,42 @@
   => IsLabel fld (MaybeT (DecodeRow (field ': row)) y) where
     fromLabel = MaybeT . decodeRow $ \(_ SOP.:* bs) ->
       runDecodeRow (runMaybeT (fromLabel @fld)) bs
+
+{- | Utility for decoding array fields in a `DecodeRow`,
+accessed via overloaded labels.
+-}
+newtype ArrayField row y = ArrayField
+  { runArrayField
+      :: StateT Strict.ByteString (Except Strict.Text) y
+      -> DecodeRow row [y]
+  }
+instance {-# OVERLAPPING #-}
+  ( KnownSymbol fld
+  , PG y ~ ty
+  , arr ~ 'NotNull ('PGvararray ('NotNull ty))
+  ) => IsLabel fld (ArrayField (fld ::: arr ': row) y) where
+    fromLabel = ArrayField $ \yval ->
+      decodeRow $ \(SOP.K bytesMaybe SOP.:* _) -> do
+        let
+          flderr = mconcat
+            [ "field name: "
+            , "\"", fromString (symbolVal (SOP.Proxy @fld)), "\"; "
+            ]
+          yarr
+            = devalue
+            . array
+            . dimensionArray replicateM
+            . valueArray
+            . revalue
+            $ yval
+        case bytesMaybe of
+          Nothing -> Left (flderr <> "encountered unexpected NULL")
+          Just bytes -> runExcept (evalStateT yarr bytes)
+instance {-# OVERLAPPABLE #-} IsLabel fld (ArrayField row y)
+  => IsLabel fld (ArrayField (field ': row) y) where
+    fromLabel = ArrayField $ \yval ->
+      decodeRow $ \(_ SOP.:* bytess) ->
+        runDecodeRow (runArrayField (fromLabel @fld) yval) bytess
 
 -- | A `GenericRow` constraint to ensure that a Haskell type
 -- is a record type,
diff --git a/src/Squeal/PostgreSQL/Session/Encode.hs b/src/Squeal/PostgreSQL/Session/Encode.hs
--- a/src/Squeal/PostgreSQL/Session/Encode.hs
+++ b/src/Squeal/PostgreSQL/Session/Encode.hs
@@ -11,6 +11,7 @@
 {-# LANGUAGE
     AllowAmbiguousTypes
   , ConstraintKinds
+  , CPP
   , DataKinds
   , DefaultSignatures
   , FlexibleContexts
@@ -59,6 +60,11 @@
 import Data.Functor.Constant (Constant(Constant))
 import Data.Functor.Contravariant
 import Data.Int (Int16, Int32, Int64)
+#if MIN_VERSION_postgresql_binary(0, 14, 0)
+import Data.IP (IPRange)
+#else
+import Network.IP.Addr (NetAddr, IP)
+#endif
 import Data.Kind
 import Data.Scientific (Scientific)
 import Data.Text as Strict (Text)
@@ -69,7 +75,6 @@
 import Data.Word (Word32)
 import Foreign.C.Types (CUInt(CUInt))
 import GHC.TypeLits
-import Network.IP.Addr (NetAddr, IP)
 import PostgreSQL.Binary.Encoding hiding (Composite, field)
 
 import qualified Data.Aeson as Aeson
@@ -121,7 +126,11 @@
 instance ToPG db Scientific where toPG = pure . numeric
 instance ToPG db Money where toPG = pure . int8_int64 . cents
 instance ToPG db UUID where toPG = pure . uuid
+#if MIN_VERSION_postgresql_binary(0, 14, 0)
+instance ToPG db IPRange where toPG = pure . inet
+#else
 instance ToPG db (NetAddr IP) where toPG = pure . inet
+#endif
 instance ToPG db Char where toPG = pure . char_utf8
 instance ToPG db Strict.Text where toPG = pure . text_strict
 instance ToPG db Lazy.Text where toPG = pure . text_lazy
diff --git a/src/Squeal/PostgreSQL/Session/Oid.hs b/src/Squeal/PostgreSQL/Session/Oid.hs
--- a/src/Squeal/PostgreSQL/Session/Oid.hs
+++ b/src/Squeal/PostgreSQL/Session/Oid.hs
@@ -36,7 +36,7 @@
 
 import Control.Monad (when)
 import Control.Monad.Catch (throwM)
-import Control.Monad.Reader (ReaderT(ReaderT))
+import Control.Monad.Reader
 import Data.String
 import GHC.TypeLits
 import PostgreSQL.Binary.Decoding (valueParser, int)
diff --git a/src/Squeal/PostgreSQL/Type/PG.hs b/src/Squeal/PostgreSQL/Type/PG.hs
--- a/src/Squeal/PostgreSQL/Type/PG.hs
+++ b/src/Squeal/PostgreSQL/Type/PG.hs
@@ -10,6 +10,7 @@
 -}
 {-# LANGUAGE
     AllowAmbiguousTypes
+  , CPP
   , DeriveAnyClass
   , DeriveFoldable
   , DeriveFunctor
@@ -57,12 +58,16 @@
 import Data.Functor.Constant (Constant)
 import Data.Kind (Type)
 import Data.Int (Int16, Int32, Int64)
+#if MIN_VERSION_postgresql_binary(0, 14, 0)
+import Data.IP (IPRange)
+#else
+import Network.IP.Addr (NetAddr, IP)
+#endif
 import Data.Scientific (Scientific)
 import Data.Time (Day, DiffTime, LocalTime, TimeOfDay, TimeZone, UTCTime)
 import Data.Vector (Vector)
 import Data.UUID.Types (UUID)
 import GHC.TypeLits
-import Network.IP.Addr (NetAddr, IP)
 
 import qualified Data.ByteString.Lazy as Lazy (ByteString)
 import qualified Data.ByteString as Strict (ByteString)
@@ -166,7 +171,11 @@
 -- | `PGuuid`
 instance IsPG UUID where type PG UUID = 'PGuuid
 -- | `PGinet`
+#if MIN_VERSION_postgresql_binary(0, 14, 0)
+instance IsPG IPRange where type PG IPRange = 'PGinet
+#else
 instance IsPG (NetAddr IP) where type PG (NetAddr IP) = 'PGinet
+#endif
 -- | `PGjson`
 instance IsPG Value where type PG Value = 'PGjson
 -- | `PGvarchar`
