diff --git a/benchmark/Benchmark.hs b/benchmark/Benchmark.hs
new file mode 100644
--- /dev/null
+++ b/benchmark/Benchmark.hs
@@ -0,0 +1,43 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE UndecidableInstances  #-}
+{-# LANGUAGE FlexibleContexts   #-}
+{-# LANGUAGE ScopedTypeVariables   #-}
+
+
+module Main where
+
+import           Control.Monad
+import           Criterion.Main
+import           Database.RethinkDB
+import qualified Data.HashMap.Strict as HMS
+
+
+db :: Exp Database
+db = Database "test"
+
+table :: Exp Table
+table = Table "benchmark"
+
+
+main :: IO ()
+main = do
+    h <- prepare
+
+    let test name = bench name . nfIO . void . run h
+
+    defaultMain
+        [ test "roundtrip" $ lift (0 :: Double)
+        , test "point-get" $ Get table "id"
+        ]
+
+
+prepare :: IO Handle
+prepare = do
+    h <- newHandle "localhost" defaultPort Nothing
+
+    void $ run h (CreateTable db "benchmark")
+    void $ run h (InsertObject table (HMS.singleton "id" (String "id")))
+
+    return h
diff --git a/rethinkdb-client-driver.cabal b/rethinkdb-client-driver.cabal
--- a/rethinkdb-client-driver.cabal
+++ b/rethinkdb-client-driver.cabal
@@ -1,5 +1,5 @@
 name:                   rethinkdb-client-driver
-version:                0.0.0.2
+version:                0.0.3
 license:                MIT
 license-file:           LICENSE
 author:                 Tomas Carnecky
@@ -50,6 +50,7 @@
     exposed-modules   : Database.RethinkDB
     other-modules     : Database.RethinkDB.Messages
                       , Database.RethinkDB.Types
+                      , Database.RethinkDB.Types.Datum
 
     ghc-options       : -Wall
 
@@ -71,3 +72,23 @@
                       , text
                       , unordered-containers
                       , time
+
+
+benchmark bench
+    default-language  : Haskell2010
+    hs-source-dirs    : benchmark
+
+    main-is           : Benchmark.hs
+    type              : exitcode-stdio-1.0
+
+    build-depends     : base < 4.8
+                      , criterion
+
+                      , rethinkdb-client-driver
+                      , vector
+                      , text
+                      , unordered-containers
+                      , time
+
+    ghc-options       : -Wall -O2 -threaded -rtsopts -with-rtsopts=-N
+    ghc-prof-options  : "-with-rtsopts=-p -s -h -i0.1 -N"
diff --git a/src/Database/RethinkDB.hs b/src/Database/RethinkDB.hs
--- a/src/Database/RethinkDB.hs
+++ b/src/Database/RethinkDB.hs
@@ -4,18 +4,21 @@
 module Database.RethinkDB
     ( Handle
     , defaultPort, newHandle
-    , run, nextChunk, collect
+    , run, nextChunk, collect, stop, wait
 
     , Error(..)
 
-    , Term, Exp(..), SomeExp(..)
-    , FromRSON(..), ToRSON(..)
-    , Array, Object, Datum(..), Bound(..), Order(..)
+      -- * The Datum type
+    , Datum(..), Array, Object, ToDatum(..), FromDatum(..)
+    , (.=), (.:), (.:?), object
+
+      -- The Exp type
+    , Exp(..), SomeExp(..)
+    , Bound(..), Order(..)
     , Sequence
     , Table, Database, SingleSelection
     , Res, Result, FromResponse
     , emptyOptions
-    , eqTime
     , lift
     , call1, call2
 
@@ -35,6 +38,7 @@
 import           Data.IORef
 
 import           Database.RethinkDB.Types
+import           Database.RethinkDB.Types.Datum
 import           Database.RethinkDB.Messages
 
 
@@ -47,7 +51,7 @@
     }
 
 
--- | The default port where RethinkDB accepts cliend driver connections.
+-- | The default port where RethinkDB accepts client driver connections.
 defaultPort :: Int
 defaultPort = 28015
 
@@ -72,7 +76,7 @@
 -- | Start a new query and wait for its (first) result. If the result is an
 -- single value ('Datum'), then three will be no further results. If it is
 -- a sequence, then you must consume results until the sequence ends.
-run :: (Term a, FromResponse (Result a))
+run :: (FromResponse (Result a))
     => Handle -> Exp a -> IO (Res a)
 run handle expr = do
     _token <- start handle expr
@@ -100,10 +104,9 @@
 
 -- | Collect all the values in a sequence and make them available as
 -- a 'Vector a'.
-collect :: (FromResponse (Sequence a))
-        => Handle -> Sequence a -> IO (Either Error (V.Vector a))
+collect :: (FromDatum a) => Handle -> Sequence a -> IO (Either Error (V.Vector a))
 collect _        (Done      x) = return $ Right x
-collect handle s@(Partial token x) = do
+collect handle s@(Partial _ x) = do
     chunk <- nextChunk handle s
     case chunk of
         Left e -> return $ Left e
@@ -117,7 +120,7 @@
 
 -- | Start a new query. Returns the 'Token' which can be used to track its
 -- progress.
-start :: (Term a) => Handle -> a -> IO Token
+start :: Handle -> Exp a -> IO Token
 start handle term = do
     token <- atomicModifyIORef (hTokenRef handle) (\x -> (x + 1, x))
     sendMessage (hSocket handle) (queryMessage token msg)
diff --git a/src/Database/RethinkDB/Messages.hs b/src/Database/RethinkDB/Messages.hs
--- a/src/Database/RethinkDB/Messages.hs
+++ b/src/Database/RethinkDB/Messages.hs
@@ -86,13 +86,11 @@
 responseMessageParser = do
     token <- getWord64host
     len   <- getWord32le
-    buf   <- getLazyByteString (fromIntegral len)
-
-    let (Just v) = A.decode buf :: (Maybe Value)
-    --trace (show v) $ return ()
-    case A.parseEither (responseParser token) v of
-        Left e -> do
-            --trace ("Response parser " ++ e) $ return ()
-            fail $ "Response: " ++ e
-        Right x  -> return x
+    buf   <- getByteString (fromIntegral len)
 
+    case A.eitherDecodeStrict buf of
+        Left e -> fail $ "responseMessageParser: response is not a JSON value (" ++ e ++ ")"
+        Right value -> do
+            case A.parseEither (responseParser token) value of
+                Left e  -> fail $ "responseMessageParser: could not parse response (" ++ e ++ ")"
+                Right x -> return x
diff --git a/src/Database/RethinkDB/Types.hs b/src/Database/RethinkDB/Types.hs
--- a/src/Database/RethinkDB/Types.hs
+++ b/src/Database/RethinkDB/Types.hs
@@ -10,27 +10,23 @@
 
 
 import           Control.Applicative
-import           Control.Monad
 import           Control.Monad.State (State, gets, modify, evalState)
 
-import           Data.Function
 import           Data.Word
 import           Data.String
 import           Data.Text           (Text)
 import           Data.Time
-import           System.Locale       (defaultTimeLocale)
 import           Data.Time.Clock.POSIX
 
-import           Data.Aeson          ((.:), (.=), FromJSON, parseJSON, toJSON)
+import           Data.Aeson          (FromJSON, parseJSON, toJSON)
 import           Data.Aeson.Types    (Parser, Value)
 import qualified Data.Aeson          as A
 
 import           Data.Vector         (Vector)
 import qualified Data.Vector         as V
-import           Data.HashMap.Strict (HashMap)
 import qualified Data.HashMap.Strict as HMS
 
-import           GHC.Generics
+import           Database.RethinkDB.Types.Datum
 
 
 
@@ -48,22 +44,6 @@
 
 
 ------------------------------------------------------------------------------
--- | A class describing a type which can be converted to the RethinkDB-specific
--- wire protocol. It is based on JSON, but certain types use a presumably more
--- efficient encoding.
-
-class FromRSON a where
-    parseRSON :: A.Value -> Parser a
-
-------------------------------------------------------------------------------
--- | See 'FromRSON'.
-
-class ToRSON a where
-    toRSON :: a -> A.Value
-
-
-
-------------------------------------------------------------------------------
 -- | Building a RethinkDB query from an expression is a stateful process, and
 -- is done using this as the context.
 
@@ -86,66 +66,33 @@
 
 
 
-------------------------------------------------------------------------------
--- | A sumtype covering all the primitive types which can appear in queries
--- or responses.
-
-data Datum
-    = Null
-    | Bool   !Bool
-    | Number !Double
-    | String !Text
-    | Array  !(Array Datum)
-    | Object !Object
-    | Time   !ZonedTime
-    deriving (Show, Generic)
-
-
-class (Term a) => IsDatum a
-
+class IsDatum a
 
 instance IsDatum Datum
 
--- | We can't automatically derive 'Eq' because 'ZonedTime' does not have an
--- instance of 'Eq'. See the 'eqTime' function for why we can compare times.
-instance Eq Datum where
-    (Null    ) == (Null    ) = True
-    (Bool   x) == (Bool   y) = x == y
-    (Number x) == (Number y) = x == y
-    (String x) == (String y) = x == y
-    (Array  x) == (Array  y) = x == y
-    (Object x) == (Object y) = x == y
-    (Time   x) == (Time   y) = x `eqTime` y
-    _          == _          = False
-
-instance ToRSON Datum where
-    toRSON (Null    ) = A.Null
-    toRSON (Bool   x) = toRSON x
-    toRSON (Number x) = toRSON x
-    toRSON (String x) = toRSON x
-    toRSON (Array  x) = toRSON x
-    toRSON (Object x) = toRSON x
-    toRSON (Time   x) = toRSON x
-
-instance FromRSON Datum where
-    parseRSON   (A.Null    ) = pure Null
-    parseRSON   (A.Bool   x) = pure $ Bool x
-    parseRSON   (A.Number x) = pure $ Number (realToFrac x)
-    parseRSON   (A.String x) = pure $ String x
-    parseRSON   (A.Array  x) = Array <$> V.mapM parseRSON x
-    parseRSON a@(A.Object x) = (Time <$> parseRSON a) <|> do
-        -- HashMap does not provide a mapM, what a shame :(
-        items <- mapM (\(k, v) -> (,) <$> pure k <*> parseRSON v) $ HMS.toList x
-        return $ Object $ HMS.fromList items
-
 instance Term Datum where
-    toTerm = return . toRSON
+    toTerm (Null    ) = return $ A.Null
+    toTerm (Bool   x) = toTerm x
+    toTerm (Number x) = toTerm x
+    toTerm (String x) = toTerm x
+    toTerm (Array  x) = toTerm x
+    toTerm (Object x) = toTerm x
+    toTerm (Time   x) = toTerm x
 
 instance FromResponse Datum where
     parseResponse = responseAtomParser
 
+instance FromResponse (Maybe Datum) where
+    parseResponse r = case (responseType r, V.toList (responseResult r)) of
+        (SuccessAtom, [a]) -> do
+            res0 <- parseWire a
+            case res0 of
+                Null -> return Nothing
+                res  -> return $ Just res
+        _                  -> fail $ "responseAtomParser: Not a single-element vector " ++ show (responseResult r)
 
 
+
 ------------------------------------------------------------------------------
 -- | For a boolean type, we're reusing the standard Haskell 'Bool' type.
 
@@ -154,14 +101,8 @@
 instance FromResponse Bool where
     parseResponse = responseAtomParser
 
-instance FromRSON Bool where
-    parseRSON = parseJSON
-
-instance ToRSON Bool where
-    toRSON = toJSON
-
 instance Term Bool where
-    toTerm = return . toRSON
+    toTerm = return . A.Bool
 
 
 
@@ -174,14 +115,8 @@
 instance FromResponse Double where
     parseResponse = responseAtomParser
 
-instance FromRSON Double where
-    parseRSON = parseJSON
-
-instance ToRSON Double where
-    toRSON = toJSON
-
 instance Term Double where
-    toTerm = return . toRSON
+    toTerm = return . toJSON
 
 
 
@@ -193,40 +128,20 @@
 instance FromResponse Text where
     parseResponse = responseAtomParser
 
-instance FromRSON Text where
-    parseRSON = parseJSON
-
-instance ToRSON Text where
-    toRSON = toJSON
-
 instance Term Text where
-    toTerm = return . toRSON
+    toTerm = return . toJSON
 
 
 
 ------------------------------------------------------------------------------
 -- | Arrays are vectors of 'Datum'.
 
-type Array a = Vector a
-
 instance (IsDatum a) => IsDatum    (Array a)
 instance (IsDatum a) => IsSequence (Array a)
 
-instance (FromRSON a) => FromResponse (Array a) where
+instance (FromDatum a) => FromResponse (Array a) where
     parseResponse = responseAtomParser
 
--- Arrays are encoded as a term MAKE_ARRAY (2).
-instance (ToRSON a) => ToRSON (Array a) where
-    toRSON v = A.Array $ V.fromList $
-        [ A.Number 2
-        , toJSON $ map toRSON (V.toList v)
-        , toJSON $ toRSON emptyOptions
-        ]
-
-instance (FromRSON a) => FromRSON (Array a) where
-    parseRSON (A.Array v) = V.mapM parseRSON v
-    parseRSON _           = fail "Array"
-
 instance (Term a) => Term (Array a) where
     toTerm v = do
         vals    <- mapM toTerm (V.toList v)
@@ -243,10 +158,7 @@
 -- | Objects are maps from 'Text' to 'Datum'. Like 'Aeson', we're using
 -- 'HashMap'.
 
-type Object = HashMap Text Datum
-
-
-class (Term a, IsDatum a) => IsObject a
+class (IsDatum a) => IsObject a
 
 
 instance IsDatum  Object
@@ -255,19 +167,10 @@
 instance FromResponse Object where
     parseResponse = responseAtomParser
 
-instance FromRSON Object where
-    parseRSON (A.Object o) = do
-        -- HashMap does not provide a mapM, what a shame :(
-        items <- mapM (\(k, v) -> (,) <$> pure k <*> parseRSON v) $ HMS.toList o
-        return $ HMS.fromList items
-
-    parseRSON _            = fail "Object"
-
-instance ToRSON Object where
-    toRSON = A.Object . HMS.map toRSON
-
 instance Term Object where
-    toTerm = return . toRSON
+    toTerm x = do
+        items <- mapM (\(k, v) -> (,) <$> pure k <*> toTerm v) $ HMS.toList x
+        return $ A.Object $ HMS.fromList $ items
 
 
 
@@ -283,39 +186,12 @@
 instance FromResponse ZonedTime where
     parseResponse = responseAtomParser
 
-instance ToRSON ZonedTime where
-    toRSON t = A.object
-        [ "$reql_type$" .= ("TIME" :: Text)
-        , "timezone"    .= (timeZoneOffsetString $ zonedTimeZone t)
-        , "epoch_time"  .= (realToFrac $ utcTimeToPOSIXSeconds $ zonedTimeToUTC t :: Double)
-        ]
-
-instance FromRSON ZonedTime where
-    parseRSON (A.Object o) = do
-        reqlType <- o .: "$reql_type$"
-        guard $ reqlType == ("TIME" :: Text)
-
-        -- Parse the timezone using 'parseTime'. This overapproximates the
-        -- possible responses from the server, but better than rolling our
-        -- own timezone parser.
-        tz <- o .: "timezone" >>= \tz -> case parseTime defaultTimeLocale "%Z" tz of
-            Just d -> pure d
-            _      -> fail "Could not parse TimeZone"
-
-        t <- o .: "epoch_time" :: Parser Double
-        return $ utcToZonedTime tz $ posixSecondsToUTCTime $ realToFrac t
-
-    parseRSON _           = fail "Time"
-
 instance Term ZonedTime where
-    toTerm = return . toRSON
-
-
-
--- | Comparing two times is done on the local time, regardless of the timezone.
--- This is exactly how the RethinkDB server does it.
-eqTime :: ZonedTime -> ZonedTime -> Bool
-eqTime = (==) `on` zonedTimeToUTC
+    toTerm x = return $ A.object
+        [ "$reql_type$" A..= ("TIME" :: Text)
+        , "timezone"    A..= (timeZoneOffsetString $ zonedTimeZone x)
+        , "epoch_time"  A..= (realToFrac $ utcTimeToPOSIXSeconds $ zonedTimeToUTC x :: Double)
+        ]
 
 
 
@@ -328,14 +204,8 @@
 instance FromResponse UTCTime where
     parseResponse = responseAtomParser
 
-instance ToRSON UTCTime where
-    toRSON = toRSON . utcToZonedTime utc
-
-instance FromRSON UTCTime where
-    parseRSON v = zonedTimeToUTC <$> parseRSON v
-
 instance Term UTCTime where
-    toTerm = return . toRSON
+    toTerm = toTerm . utcToZonedTime utc
 
 instance Lift Exp UTCTime where
     type Simplified UTCTime = ZonedTime
@@ -353,9 +223,6 @@
 
 instance IsSequence Table
 
-instance ToRSON Table where
-    toRSON = error "toRSON Table: Server-only type"
-
 instance Term Table where
     toTerm = error "toTerm Table: Server-only type"
 
@@ -424,30 +291,31 @@
     | Partial !Token !(Vector a)
 
 
-class (Term a) => IsSequence a
+class IsSequence a
 
 
 instance Show (Sequence a) where
     show (Done      v) = "Done " ++ (show $ V.length v)
     show (Partial _ v) = "Partial " ++ (show $ V.length v)
 
-instance (FromRSON a) => FromResponse (Sequence a) where
+instance (FromDatum a) => FromResponse (Sequence a) where
     parseResponse = responseSequenceParser
 
-instance ToRSON (Sequence a) where
-    toRSON = error "toRSON Sequence: Server-only type"
-
 instance Term (Sequence a) where
     toTerm = error "toTerm Sequence: Server-only type"
 
 instance IsSequence (Sequence a)
 
+instance (FromDatum a) => FromDatum (Sequence a) where
+    parseDatum (Array x) = Done <$> V.mapM parseDatum x
+    parseDatum _         = fail "Sequence"
 
 
+
 ------------------------------------------------------------------------------
 
 data Exp a where
-    Constant :: (ToRSON a) => a -> Exp a
+    Constant :: (ToDatum a) => a -> Exp a
     -- Any object which can be converted to RSON can be treated as a constant.
     -- Furthermore, many basic Haskell types have a 'Lift' instance which turns
     -- their values into constants.
@@ -486,10 +354,14 @@
     Database       :: Exp Text -> Exp Database
     Table          :: Exp Text -> Exp Table
 
-    Coerce         :: (Term a, Term b) => Exp a -> Exp Text -> Exp b
+    Coerce         :: Exp a -> Exp Text -> Exp b
     Eq             :: (IsDatum a, IsDatum b) => Exp a -> Exp b -> Exp Bool
     Ne             :: (IsDatum a, IsDatum b) => Exp a -> Exp b -> Exp Bool
-    Match          :: Exp Text -> Exp Text -> Exp Datum
+    Not            :: Exp Bool -> Exp Bool
+
+    Match :: Exp Text -> Exp Text -> Exp Datum
+    -- ^ First arg is the string, second a regular expression.
+
     Get            :: Exp Table -> Exp Text -> Exp SingleSelection
     GetAll         :: (IsDatum a) => Exp Table -> [Exp a] -> Exp (Array Datum)
     GetAllIndexed  :: (IsDatum a) => Exp Table -> [Exp a] -> Text -> Exp (Sequence Datum)
@@ -509,11 +381,14 @@
     ExtractField :: (IsSequence a) => Exp a -> Exp Text -> Exp a
     -- Like 'ObjectField' but over a sequence.
 
+    HasFields :: (IsObject a) => [Text] -> Exp a -> Exp Bool
+    -- True if the object has all the given fields.
+
     Take           :: (IsSequence s) => Exp Double -> Exp s -> Exp s
     Append         :: (IsDatum a) => Exp (Array a) -> Exp a -> Exp (Array a)
     Prepend        :: (IsDatum a) => Exp (Array a) -> Exp a -> Exp (Array a)
     IsEmpty        :: (IsSequence a) => Exp a -> Exp Bool
-    Delete         :: (Term a) => Exp a -> Exp Object
+    Delete         :: Exp a -> Exp Object
 
     InsertObject   :: Exp Table -> Object -> Exp Object
     -- Insert a single object into the table.
@@ -521,16 +396,16 @@
     InsertSequence :: (IsSequence s) => Exp Table -> Exp s -> Exp Object
     -- Insert a sequence into the table.
 
-    Filter :: (IsSequence s, Term a) => (Exp a -> Exp Bool) -> Exp s -> Exp s
-    Map :: (IsSequence s, Term a, Term b) => (Exp a -> Exp b) -> Exp s -> Exp s
+    Filter :: (IsSequence s) => (Exp a -> Exp Bool) -> Exp s -> Exp s
+    Map :: (IsSequence s) => (Exp a -> Exp b) -> Exp s -> Exp (Sequence b)
 
-    Between :: (IsSequence s) => Exp s -> (Bound, Bound) -> Exp s
+    Between :: (IsSequence s) => (Bound, Bound) -> Exp s -> Exp s
     -- Select all elements whose primary key is between the two bounds.
 
-    BetweenIndexed :: (IsSequence s) => Exp s -> (Bound, Bound) -> Text -> Exp s
+    BetweenIndexed :: (IsSequence s) => Text -> (Bound, Bound) -> Exp s -> Exp s
     -- Select all elements whose secondary index is between the two bounds.
 
-    OrderBy :: (IsSequence s) => [Order] -> Exp s -> Exp s
+    OrderBy :: (IsSequence s) => [Order] -> Exp s -> Exp (Array Datum)
     -- Order a sequence based on the given order specificiation.
 
     Keys :: (IsObject a) => Exp a -> Exp (Array Text)
@@ -538,24 +413,27 @@
     Var :: Int -> Exp a
     -- A 'Var' is used as a placeholder in input to functions.
 
-    Function :: (Term a) => State Context ([Int], Exp a) -> Exp f
+    Function :: State Context ([Int], Exp a) -> Exp f
     -- Creates a function. The action should take care of allocating an
     -- appropriate number of variables from the context. Note that you should
     -- not use this constructor directly. There are 'Lift' instances for all
     -- commonly used functions.
 
-    Call :: (Term f) => Exp f -> [SomeExp] -> Exp r
+    Call :: Exp f -> [SomeExp] -> Exp r
     -- Call the given function. The function should take the same number of
     -- arguments as there are provided.
 
+    Limit :: (IsSequence s) => Double -> Exp s -> Exp s
+    -- ^ Limit the number of items in the sequence.
 
-instance (Term a) => Term (Exp a) where
+
+instance Term (Exp a) where
     toTerm (Constant datum) =
-        toTerm datum
+        toTerm $ toDatum datum
 
 
     toTerm ListDatabases =
-        simpleTerm 59 []
+        simpleTerm 59 ([] :: [SomeExp])
 
     toTerm (CreateDatabase name) =
         simpleTerm 57 [SomeExp name]
@@ -602,14 +480,14 @@
     toTerm (Map f s) =
         simpleTerm 38 [SomeExp s, SomeExp (lift f)]
 
-    toTerm (Between s (l, u)) =
+    toTerm (Between (l, u) s) =
         termWithOptions 36 [SomeExp s, SomeExp $ lift (boundDatum l), SomeExp $ lift (boundDatum u)] $
             HMS.fromList
                 [ ("left_bound",  String (boundString l))
                 , ("right_bound", String (boundString u))
                 ]
 
-    toTerm (BetweenIndexed s (l, u) index) =
+    toTerm (BetweenIndexed index (l, u) s) =
         termWithOptions 36 [SomeExp s, SomeExp $ lift (boundDatum l), SomeExp $ lift (boundDatum u)] $
             HMS.fromList
                 [ ("left_bound",  String (boundString l))
@@ -620,10 +498,10 @@
     toTerm (OrderBy spec s) = do
         s'    <- toTerm s
         spec' <- mapM toTerm spec
-        simpleTerm2 41 ([s'] ++ spec')
+        simpleTerm 41 ([s'] ++ spec')
 
-    toTerm (InsertObject table object) =
-        termWithOptions 56 [SomeExp table, SomeExp (lift object)] emptyOptions
+    toTerm (InsertObject table obj) =
+        termWithOptions 56 [SomeExp table, SomeExp (lift obj)] emptyOptions
 
     toTerm (InsertSequence table s) =
         termWithOptions 56 [SomeExp table, SomeExp s] emptyOptions
@@ -631,12 +509,15 @@
     toTerm (Delete selection) =
         simpleTerm 54 [SomeExp selection]
 
-    toTerm (ObjectField object field) =
-        simpleTerm 31 [SomeExp object, SomeExp field]
+    toTerm (ObjectField obj field) =
+        simpleTerm 31 [SomeExp obj, SomeExp field]
 
-    toTerm (ExtractField object field) =
-        simpleTerm 31 [SomeExp object, SomeExp field]
+    toTerm (ExtractField obj field) =
+        simpleTerm 31 [SomeExp obj, SomeExp field]
 
+    toTerm (HasFields fields obj) =
+        simpleTerm 32 ([SomeExp obj] ++ map (SomeExp . lift) fields)
+
     toTerm (Coerce value typeName) =
         simpleTerm 51 [SomeExp value, SomeExp typeName]
 
@@ -658,9 +539,12 @@
     toTerm (Ne a b) =
         simpleTerm 18 [SomeExp a, SomeExp b]
 
-    toTerm (Match a b) =
-        simpleTerm 97 [SomeExp a, SomeExp b]
+    toTerm (Not e) =
+        simpleTerm 23 [SomeExp e]
 
+    toTerm (Match str re) =
+        simpleTerm 97 [SomeExp str, SomeExp re]
+
     toTerm (Get table key) =
         simpleTerm 16 [SomeExp table, SomeExp key]
 
@@ -696,18 +580,16 @@
     toTerm (Call f args) =
         simpleTerm 64 ([SomeExp f] ++ args)
 
+    toTerm (Limit n s) =
+        simpleTerm 71 [SomeExp s, SomeExp (lift n)]
 
-simpleTerm :: Int -> [SomeExp] -> State Context A.Value
-simpleTerm termType args = do
-    args' <- mapM toTerm args
-    return $ A.Array $ V.fromList [toJSON termType, toJSON args']
 
-simpleTerm2 :: (Term a) => Int -> [a] -> State Context A.Value
-simpleTerm2 termType args = do
+simpleTerm :: (Term a) => Int -> [a] -> State Context A.Value
+simpleTerm termType args = do
     args' <- mapM toTerm args
     return $ A.Array $ V.fromList [toJSON termType, toJSON args']
 
-termWithOptions :: Int -> [SomeExp] -> Object -> State Context A.Value
+termWithOptions :: (Term a) => Int -> [a] -> Object -> State Context A.Value
 termWithOptions termType args options = do
     args'    <- mapM toTerm args
     options' <- toTerm options
@@ -729,6 +611,7 @@
 
     abs _    = error "Num (Exp a): abs not implemented"
     signum _ = error "Num (Exp a): signum not implemented"
+    negate _ = error "Num (Exp a): negate not implemented"
 
 
 
@@ -774,13 +657,13 @@
     type Simplified (Array Datum) = (Array Datum)
     lift = Constant
 
-instance (Term r) => Lift Exp (Exp a -> Exp r) where
+instance Lift Exp (Exp a -> Exp r) where
     type Simplified (Exp a -> Exp r) = Exp r
     lift f = Function $ do
         v1 <- newVar
         return $ ([v1], f (Var v1))
 
-instance (Term r) => Lift Exp (Exp a -> Exp b -> Exp r) where
+instance Lift Exp (Exp a -> Exp b -> Exp r) where
     type Simplified (Exp a -> Exp b -> Exp r) = Exp r
     lift f = Function $ do
         v1 <- newVar
@@ -794,18 +677,12 @@
 -- used instead of the 'Call' constructor because they provide type safety.
 
 -- | Call an unary function with the given argument.
-call1 :: (Term a, Term r)
-      => (Exp a -> Exp r)
-      -> Exp a
-      -> Exp r
+call1 :: (Exp a -> Exp r) -> Exp a -> Exp r
 call1 f a = Call (lift f) [SomeExp a]
 
 
 -- | Call an binary function with the given arguments.
-call2 :: (Term a, Term b, Term r)
-      => (Exp a -> Exp b -> Exp r)
-      -> Exp a -> Exp b
-      -> Exp r
+call2 :: (Exp a -> Exp b -> Exp r) -> Exp a -> Exp b -> Exp r
 call2 f a b = Call (lift f) [SomeExp a, SomeExp b]
 
 
@@ -819,7 +696,7 @@
 -- arguments can, and often have, different types).
 
 data SomeExp where
-     SomeExp :: (Term a) => Exp a -> SomeExp
+     SomeExp :: Exp a -> SomeExp
 
 instance Term SomeExp where
     toTerm (SomeExp e) = toTerm e
@@ -868,18 +745,19 @@
     parseResponse :: Response -> Parser a
 
 
-responseAtomParser :: (FromRSON a) => Response -> Parser a
+responseAtomParser :: (FromDatum a) => Response -> Parser a
 responseAtomParser r = case (responseType r, V.toList (responseResult r)) of
-    (SuccessAtom, [a]) -> parseRSON a
+    (SuccessAtom, [a]) -> parseWire a >>= parseDatum
     _                  -> fail $ "responseAtomParser: Not a single-element vector " ++ show (responseResult r)
 
-responseSequenceParser :: (FromRSON a) => Response -> Parser (Sequence a)
+responseSequenceParser :: (FromDatum a) => Response -> Parser (Sequence a)
 responseSequenceParser r = case responseType r of
+    SuccessAtom     -> Done    <$> responseAtomParser r
     SuccessSequence -> Done    <$> values
     SuccessPartial  -> Partial <$> pure (responseToken r) <*> values
-    _               -> fail "responseSequenceParser: Unexpected type"
+    rt              -> fail $ "responseSequenceParser: Unexpected type " ++ show rt
   where
-    values = V.mapM parseRSON (responseResult r)
+    values = V.mapM (\x -> parseWire x >>= parseDatum) (responseResult r)
 
 
 
@@ -923,7 +801,7 @@
 
 responseParser :: Token -> Value -> Parser Response
 responseParser token (A.Object o) =
-    Response <$> pure token <*> o .: "t" <*> o .: "r"
+    Response <$> pure token <*> o A..: "t" <*> o A..: "r"
 responseParser _     _          =
     fail "Response: Unexpected JSON value"
 
diff --git a/src/Database/RethinkDB/Types/Datum.hs b/src/Database/RethinkDB/Types/Datum.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/RethinkDB/Types/Datum.hs
@@ -0,0 +1,316 @@
+{-# LANGUAGE DeriveGeneric         #-}
+{-# LANGUAGE TypeFamilies          #-}
+{-# LANGUAGE FlexibleInstances     #-}
+{-# LANGUAGE FlexibleContexts      #-}
+{-# LANGUAGE GADTs                 #-}
+{-# LANGUAGE OverloadedStrings     #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+
+module Database.RethinkDB.Types.Datum where
+
+
+import           Control.Applicative
+import           Control.Monad
+
+import           Data.Text           (Text)
+import           Data.Time
+import           Data.Scientific
+import           System.Locale       (defaultTimeLocale)
+import           Data.Time.Clock.POSIX
+
+import           Data.Aeson          (FromJSON(..), ToJSON(..))
+import           Data.Aeson.Types    (Value, Parser)
+import qualified Data.Aeson          as A
+
+import           Data.Vector         (Vector)
+import qualified Data.Vector         as V
+import           Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as HMS
+
+import           GHC.Generics
+
+
+
+------------------------------------------------------------------------------
+-- | A sumtype covering all the primitive types which can appear in queries
+-- or responses.
+--
+-- It is similar to the aeson 'Value' type, except that RethinkDB has a few
+-- more types (like 'Time'), which have a special encoding in JSON.
+
+data Datum
+    = Null
+    | Bool   !Bool
+    | Number !Double
+    | String !Text
+    | Array  !(Array Datum)
+    | Object !Object
+    | Time   !ZonedTime
+    deriving (Show, Generic)
+
+
+
+------------------------------------------------------------------------------
+-- | Arrays are vectors of 'Datum'.
+
+type Array a = Vector a
+
+
+
+------------------------------------------------------------------------------
+-- | Objects are maps from 'Text' to 'Datum'. Like 'Aeson', we're using
+-- a strict 'HashMap'.
+
+type Object = HashMap Text Datum
+
+
+
+-- | We can't automatically derive 'Eq' because 'ZonedTime' does not have an
+-- instance of 'Eq'. See the 'eqTime' function for why we can compare times.
+instance Eq Datum where
+    (Null    ) == (Null    ) = True
+    (Bool   x) == (Bool   y) = x == y
+    (Number x) == (Number y) = x == y
+    (String x) == (String y) = x == y
+    (Array  x) == (Array  y) = x == y
+    (Object x) == (Object y) = x == y
+    (Time   x) == (Time   y) = (zonedTimeToUTC x) == (zonedTimeToUTC y)
+    _          == _          = False
+
+
+instance ToJSON Datum where
+    toJSON (Null    ) = A.Null
+    toJSON (Bool   x) = toJSON x
+    toJSON (Number x) = toJSON x
+    toJSON (String x) = toJSON x
+    toJSON (Array  x) = toJSON x
+    toJSON (Time   x) = toJSON x
+    toJSON (Object x) = toJSON x
+
+
+instance FromJSON Datum where
+    parseJSON   (A.Null    ) = pure Null
+    parseJSON   (A.Bool   x) = pure $ Bool x
+    parseJSON   (A.Number x) = pure $ Number (realToFrac x)
+    parseJSON v@(A.String x) = (Time <$> parseJSON v) <|> (pure $ String x)
+    parseJSON   (A.Array  x) = Array <$> V.mapM parseJSON x
+    parseJSON   (A.Object x) = do
+        -- HashMap does not provide a mapM, what a shame :(
+        items <- mapM (\(k, v) -> (,) <$> pure k <*> parseJSON v) $ HMS.toList x
+        pure $ Object $ HMS.fromList items
+
+
+
+parseWire :: A.Value -> Parser Datum
+parseWire (A.Null    ) = pure Null
+parseWire (A.Bool   x) = pure $ Bool x
+parseWire (A.Number x) = pure $ Number (realToFrac x)
+parseWire (A.String x) = pure $ String x
+parseWire (A.Array  x) = Array <$> V.mapM parseWire x
+parseWire (A.Object x) = (Time <$> zonedTimeParser x) <|> do
+    -- HashMap does not provide a mapM, what a shame :(
+    items <- mapM (\(k, v) -> (,) <$> pure k <*> parseWire v) $ HMS.toList x
+    return $ Object $ HMS.fromList items
+
+
+zonedTimeParser :: HashMap Text A.Value -> Parser ZonedTime
+zonedTimeParser o = do
+    reqlType <- o A..: "$reql_type$"
+    guard $ reqlType == ("TIME" :: Text)
+
+    -- Parse the timezone using 'parseTime'. This overapproximates the
+    -- possible responses from the server, but better than rolling our
+    -- own timezone parser.
+    tz <- o A..: "timezone" >>= \tz -> case parseTime defaultTimeLocale "%Z" tz of
+        Just d -> pure d
+        _      -> fail "Could not parse TimeZone"
+
+    t <- o A..: "epoch_time" :: Parser Double
+    pure $ utcToZonedTime tz $ posixSecondsToUTCTime $ realToFrac t
+
+
+
+
+------------------------------------------------------------------------------
+-- | Types which can be converted to or from a 'Datum'.
+
+class ToDatum a where
+    toDatum :: a -> Datum
+
+class FromDatum a where
+    parseDatum :: Datum -> Parser a
+
+
+
+(.=) :: ToDatum a => Text -> a -> (Text, Datum)
+k .= v = (k, toDatum v)
+
+(.:) :: FromDatum a => HashMap Text Datum -> Text -> Parser a
+o .: k = maybe (fail $ "key " ++ show k ++ "not found") parseDatum $ HMS.lookup k o
+
+(.:?) :: FromDatum a => HashMap Text Datum -> Text -> Parser (Maybe a)
+o .:? k = maybe (return Nothing) (fmap Just . parseDatum) $ HMS.lookup k o
+
+object :: [(Text, Datum)] -> Datum
+object = Object . HMS.fromList
+
+
+
+------------------------------------------------------------------------------
+-- Datum
+
+instance ToDatum Datum where
+    toDatum = id
+
+instance FromDatum Datum where
+    parseDatum = pure
+
+
+
+------------------------------------------------------------------------------
+-- Bool
+
+instance ToDatum Bool where
+    toDatum = Bool
+
+instance FromDatum Bool where
+    parseDatum (Bool x) = pure x
+    parseDatum _        = fail "Bool"
+
+
+
+------------------------------------------------------------------------------
+-- Double
+
+instance ToDatum Double where
+    toDatum = Number
+
+instance FromDatum Double where
+    parseDatum (Number x) = pure x
+    parseDatum _          = fail "Double"
+
+
+
+------------------------------------------------------------------------------
+-- Int
+
+instance ToDatum Int where
+    toDatum = Number . fromIntegral
+
+instance FromDatum Int where
+    parseDatum (Number x) = pure $ floor x
+    parseDatum _          = fail "Int"
+
+
+
+------------------------------------------------------------------------------
+-- Text
+
+instance ToDatum Text where
+    toDatum = String
+
+instance FromDatum Text where
+    parseDatum (String x) = pure x
+    parseDatum _          = fail "Text"
+
+
+
+------------------------------------------------------------------------------
+-- Array (Vector)
+
+instance (ToDatum a) => ToDatum (Array a) where
+    toDatum = Array . V.map toDatum
+
+instance (FromDatum a) => FromDatum (Array a) where
+    parseDatum (Array v) = V.mapM parseDatum v
+    parseDatum _         = fail "Array"
+
+
+
+------------------------------------------------------------------------------
+-- Object (HashMap Text Datum)
+
+instance ToDatum Object where
+    toDatum = Object
+
+instance FromDatum Object where
+    parseDatum (Object o) = do
+        -- HashMap does not provide a mapM, what a shame :(
+        items <- mapM (\(k, v) -> (,) <$> pure k <*> parseDatum v) $ HMS.toList o
+        pure $ HMS.fromList items
+
+    parseDatum _          = fail "Object"
+
+
+
+------------------------------------------------------------------------------
+-- ZonedTime
+
+instance ToDatum ZonedTime where
+    toDatum = Time
+
+instance FromDatum ZonedTime where
+    parseDatum (Time x) = pure x
+    parseDatum _        = fail "ZonedTime"
+
+
+
+------------------------------------------------------------------------------
+-- UTCTime
+
+instance ToDatum UTCTime where
+    toDatum = Time . utcToZonedTime utc
+
+instance FromDatum UTCTime where
+    parseDatum (Time x) = pure (zonedTimeToUTC x)
+    parseDatum _        = fail "UTCTime"
+
+
+
+------------------------------------------------------------------------------
+-- [a]
+
+instance ToDatum a => ToDatum [a] where
+    toDatum = Array . V.fromList . map toDatum
+
+instance FromDatum a => FromDatum [a] where
+    parseDatum (Array x) = V.toList <$> V.mapM parseDatum x
+    parseDatum _         = fail "[a]"
+
+
+
+------------------------------------------------------------------------------
+-- Maybe a
+
+instance ToDatum a => ToDatum (Maybe a) where
+    toDatum Nothing  = Null
+    toDatum (Just x) = toDatum x
+
+instance FromDatum a => FromDatum (Maybe a) where
+    parseDatum Null = pure Nothing
+    parseDatum d    = Just <$> parseDatum d
+
+
+
+------------------------------------------------------------------------------
+-- Value
+
+instance ToDatum Value where
+    toDatum (A.Null    ) = Null
+    toDatum (A.Bool   x) = Bool x
+    toDatum (A.Number x) = Number $ toRealFloat x
+    toDatum (A.String x) = String x
+    toDatum (A.Array  x) = Array $ V.map toDatum x
+    toDatum (A.Object x) = Object $ fmap toDatum x
+
+instance FromDatum Value where
+    parseDatum (Null    ) = pure A.Null
+    parseDatum (Bool   x) = pure $ A.Bool x
+    parseDatum (Number x) = pure $ A.Number (realToFrac x)
+    parseDatum (String x) = pure $ A.String x
+    parseDatum (Array  x) = A.Array <$> V.mapM parseDatum x
+    parseDatum (Object x) = do
+        -- HashMap does not provide a mapM, what a shame :(
+        items <- mapM (\(k, v) -> (,) <$> pure k <*> parseDatum v) $ HMS.toList x
+        pure $ A.Object $ HMS.fromList items
+    parseDatum (Time   x) = pure $ toJSON x
diff --git a/test/Test.hs b/test/Test.hs
--- a/test/Test.hs
+++ b/test/Test.hs
@@ -60,7 +60,7 @@
 
 
 expectSuccess
-    :: (Term a, Eq (Result a), FromResponse (Result a), Show (Result a))
+    :: (Eq (Result a), FromResponse (Result a), Show (Result a))
     => Handle -> Exp a -> Result a -> IO Bool
 expectSuccess h query value = do
     res <- run h query
