diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,2 @@
+Copyright (c) 2015 Elsen Inc.
+All rights reserved.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,195 @@
+<p align="center">
+  <a href="http://elsen.co">
+    <img src="https://elsen.co/img/apple-touch-icon-144x144.png"/>
+  </a>
+</p>
+
+pgstream
+========
+
+Streaming of Postgres through the binary protocol into Haskell. Uses attoparsec
+and some hand-written kernels for array extraction. Results are streamed into
+vectors or batched into serial or parallel
+[Conduit](https://hackage.haskell.org/package/conduit-1.2.4/docs/Data-Conduit.html)
+pipelines for stream composition.
+
+[![Build Status](https://travis-ci.org/elsen-trading/pgstream.svg)](https://travis-ci.org/elsen-trading/pgstream)
+
+Installation
+------------
+
+```bash
+$ cabal install pg_stream.cabal
+```
+
+Usage
+-----
+
+**Connections**
+
+Connections to Postgres are established with the ``connect`` function yielding
+the connection object.
+
+```haskell
+connect :: ConnSettings -> IO PQ.Connection
+```
+
+Connections are specified by a ConnSettings.
+
+```haskell
+creds :: ConnSettings
+creds = ConnSettings {_host = "localhost", _dbname="invest", _user="dbadmin"}
+```
+
+Connections are pooled per process. Connection pooling is specified by three
+parameters.
+
+* **Stripes**: Stripe count. The number of distinct sub-pools to maintain. The smallest acceptable value is 1.
+* **Keep Alive**: Amount of time for which an unused resource is kept open. The smallest acceptable value is 0.5 seconds.
+* **Affinity**: Maximum number of resources to keep open per stripe. The smallest acceptable value is 1.
+
+The default settings are:
+
+```haskell
+defaultPoolSettings :: PoolSettings
+defaultPoolSettings = PoolSettings { _stripes = 1, _keepalive = 10, _affinity = 10 }
+```
+
+
+**Queries**
+
+Queries are executed using ``query`` for statements that yield result sets or by
+``execute`` for queries that return a status code.
+
+```haskell
+query :: (FromRow r, ToSQL a) => PQ.Connection -> Query -> a -> IO [r]
+execute :: (ToSQL a) => PQ.Connection -> Query -> a -> IO ()
+```
+
+For example:
+
+```haskell
+run :: IO [Row]
+run = do
+  conn <- connect creds
+  query conn sample args
+```
+
+SQL queries are constructed via quasiquoter (``[sql| ... |]``) which generates a
+``Query`` (newtype around a bytestring). Values and SQL fragments can be spliced
+into this template as arguments.
+
+```haskell
+{-# LANGUAGE QuasiQuotes #-}
+
+sample :: Query
+sample = [sql|
+    SELECT
+      deltas.sid AS sid,
+      EXTRACT(EPOCH FROM deltas.day) AS day,
+      (ohlcs :: float4[])
+    FROM deltas
+    INNER JOIN security_groupings ON deltas.sid = security_groupings.sid
+    INNER JOIN currentprice ON (
+      deltas.sid = currentprice.sid
+      AND deltas.DAY = currentprice.DAY
+      AND currentprice.val BETWEEN 0 AND 500
+    )
+    WHERE security_groupings.name = 'SP900'
+    AND deltas.day BETWEEN TO_TIMESTAMP({1}) AND TO_TIMESTAMP({2})
+    ORDER BY deltas.sid,
+             deltas.DAY ASC
+    {3}
+    ;
+|]
+```
+
+**Arguments**
+
+If the types of arguments are constrained by inference then no annotations are
+necessary. Otherwise annotations are needed to refine the Num/String instances
+into concrete types so they can be serialized and sent to Postgres.
+
+```haskell
+args :: (Int, Int, SQL)
+args = ( 1335855600 , 1336374000 , "LIMIT 100000")
+```
+
+The conversion from Haskell to Postgres types is defined by the
+FromField/ToField typeclasses with the mapping given by.
+
+| Postgres      | Haskell       |
+| ------------- |---------------|
+| int2          | Int8          |
+| int4          | Int32         |
+| int8          | Int64         |
+| float4        | Float         |
+| float8        | Double        |
+| numeric       | Scientific    |
+| uuid          | UUID          |
+| char          | Char          |
+| text          | Text          |
+| date          | Day           |
+| bytea         | ByteString    |
+| bool          | Bool          |
+| int4[]        | Vector Int32  |
+| float4[]      | Vector Float  |
+| money         | Fixed E3      |
+| null a        | Maybe a       |
+
+If the result set type is given as ``Maybe a`` then any missing value are
+manifest as ``Nothing``  values. And all concrete values are ``Just``.
+Effectively makes errors from null values used in unchecked logic
+unrepresentable as any function which consumes a potentially nullable field is
+forced by the type system to handle both cases.
+
+**Streaming**
+
+```haskell
+stream :: (FromRow r, ToSQL a, MonadBaseControl IO m, MonadIO m) =>
+     PQ.Connection       -- ^ Connection
+  -> Query               -- ^ Query
+  -> a                   -- ^ Query arguments
+  -> Int                 -- ^ Batch size
+  -> C.Source m [r]      -- ^ Source conduit
+```
+
+Parallel streams can be composed together Software Transactional Memory (STM)
+threads to synchronize the polling.
+
+```haskell
+import Database.PostgreSQL.Stream.Parallel
+
+parallelStream ::
+  PQ.Connection
+  -> (PQ.Connection -> Source (ResourceT IO) a)  -- Source
+  -> Sink a (ResourceT IO) ()                    -- Sink
+  -> IO ()
+```
+
+Development
+-----------
+
+```bash
+$ cabal sandbox init
+$ cabal install --only-dependencies
+$ cabal build
+```
+
+To attach to the Elsen compute engine:
+
+```bash
+$ cabal sandbox add-source path_to_tree
+```
+
+Documentation
+--------------
+
+```bash
+$ cabal haddock
+```
+
+Legal
+-----
+
+Copyright (c) 2015 Elsen Inc. All rights reserved.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/cbits/array_conversion.c b/cbits/array_conversion.c
new file mode 100644
--- /dev/null
+++ b/cbits/array_conversion.c
@@ -0,0 +1,149 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <math.h>
+#include <arpa/inet.h>
+#include <postgresql/libpq-fe.h>
+
+#define INT4OID      23
+#define FLOAT4OID    700
+#define FLOAT8OID    701
+#define NUMERICOID   1700
+
+// XXX: Check endianness problems on OS X? Production is invariable Linux so
+// probably shouldn't matter.
+
+// int4[]
+struct array_int4 {
+    int32_t ndim;
+    int32_t _ign;
+    Oid elemtype;
+    
+    int32_t size;
+    int32_t index;
+    int32_t data;
+};
+
+// float4[]
+struct array_float {
+    int32_t ndim;
+    int32_t _ign;
+    Oid elemtype;
+    
+    int32_t size;
+    int32_t index;
+    float data;
+};
+
+// float4[]
+struct array_double {
+    int32_t ndim;
+    int32_t _ign;
+    Oid elemtype;
+    
+    int32_t size;
+    int32_t index;
+    double data;
+};
+
+
+// Postgres binary uses network byte order, we have to swap the bytes for the
+// floating point values.
+static inline float swap( const float inFloat )
+{
+    float retVal;
+    char *in = ( char * ) & inFloat;
+    char *out = ( char * ) & retVal;
+    
+    out[0] = in[3];
+    out[1] = in[2];
+    out[2] = in[1];
+    out[3] = in[0];
+    
+    return retVal;
+}
+
+#define INT_SEP -1
+
+int extract_int4_array (char *raw_array, int32_t *values, int size)
+{
+    struct array_int4 *array = (struct array_int4 *) raw_array;
+    int32_t *val = &(array->data);
+    int32_t ival;
+    
+    if (ntohl(array->ndim) != 1 || ntohl(array->elemtype) != INT4OID) {
+        return -1;
+    }
+    int array_elements = ntohl(array->size);
+    
+    int n = 0;
+    val = &(array->data);
+    for (int i = 0; i < array_elements; ++i) {
+        ival = ntohl (*val);
+        if (ival != INT_SEP) {
+            ++val;
+            values[n] = ntohl(*val);
+            n += 1;
+        }
+        
+        ++val;
+    }
+    
+    return size;
+}
+
+#define FLOAT_SEP 6.0e-45
+
+int extract_float_array(char *raw_array, float *values, int size)
+{
+    struct array_float *array = (struct array_float *) raw_array;
+    float *val = &(array->data);
+    int32_t ival;
+    
+    if (ntohl(array->ndim) != 1 || ntohl(array->elemtype) != FLOAT4OID) {
+        return -1;
+    }
+    int array_elements = ntohl(array->size);
+    
+    int n = 0;
+    val = &(array->data);
+    for (int i = 0; i < array_elements; ++i) {
+        ival = swap(*val);
+        if (ival != FLOAT_SEP) {
+            ++val;
+            values[n] = swap(*val);
+            n += 1;
+        }
+        ++val;
+    }
+    
+    return size;
+}
+
+#define DOUBLE_SEP 6.0e-45
+
+int extract_double_array(char *raw_array, double *values, int size)
+{
+    struct array_double *array = (struct array_double *) raw_array;
+    double *val = &(array->data);
+    int32_t ival;
+    
+    if (ntohl(array->ndim) != 1 || ntohl(array->elemtype) != FLOAT8OID) {
+        return -1;
+    }
+    int array_elements = ntohl(array->size);
+    
+    int n = 0;
+    val = &(array->data);
+    for (int i = 0; i < array_elements; ++i) {
+        ival = swap(*val);
+        if (ival != DOUBLE_SEP) {
+            ++val;
+            values[n] = swap(*val);
+            n += 1;
+        }
+        ++val;
+    }
+    
+    return size;
+}
diff --git a/cbits/array_conversion.h b/cbits/array_conversion.h
new file mode 100644
--- /dev/null
+++ b/cbits/array_conversion.h
@@ -0,0 +1,9 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <arpa/inet.h>
+#include <postgresql/libpq-fe.h>
+
+int extract_int4_array (char *raw_array, int32_t *values, int size);
+int extract_float_array (char *raw_array, float *values, int size);
+int extract_double_array (char *raw_array, double *values, int size);
diff --git a/pgstream.cabal b/pgstream.cabal
new file mode 100644
--- /dev/null
+++ b/pgstream.cabal
@@ -0,0 +1,100 @@
+name:                pgstream
+version:             0.1.0.3
+license:             BSD3
+copyright:           Copyright 2015 Elsen Inc.
+category:            Finance
+description:         Elsen Accelerated Computing Engine
+synopsis:            Elsen Accelerated Computing Engine
+license-file:        LICENSE
+author:              Elsen Inc.
+maintainer:          info@elsen.co
+build-type:          Simple
+extra-source-files:  README.md
+cabal-version:       >=1.10
+extra-source-files:  
+  cbits/array_conversion.h
+
+Source-Repository head
+    Type: git
+    Location: git@github.com:elsen-trading/pgstream.git
+
+-- Compiler flags
+flag optimized
+  manual: True
+  default: True
+  description: Compile with optimizations enabled.
+
+flag j
+  manual: True
+  default: False
+  description: Compile in parallel.
+
+-- Executable
+library 
+  exposed-modules:
+    Database.PostgreSQL.Stream
+    Database.PostgreSQL.Stream.Parallel
+    Database.PostgreSQL.Stream.Connection
+    Database.PostgreSQL.Stream.Types
+    Database.PostgreSQL.Stream.FromRow
+    Database.PostgreSQL.Stream.QueryBuilder
+
+  build-depends:       
+    base                 >= 4.6   && <4.10,
+
+    -- Standard library
+    text                 >= 1.1   && <1.2,
+    bytestring           >= 0.10  && <0.11,
+    attoparsec           >= 0.12  && <0.13,
+    vector               >= 0.10  && <0.11,
+    uuid                 >= 1.3   && <1.4,
+    scientific           >= 0.3   && <0.4, 
+    stringsearch         >= 0.3   && <0.4,
+    time                 >= 1.4   && <1.6,
+    deepseq              >= 1.3   && <1.5,
+
+    -- Streaming
+    conduit              >= 1.2.4 && <1.3,
+    conduit-extra        >= 1.1   && <1.2,
+    resource-pool        >= 0.2   && <0.3,
+    blaze-builder        >= 0.4   && <0.5, 
+    resourcet            >= 1.1   && <1.2,
+
+    -- SQL
+    postgresql-libpq     >= 0.9   && <0.10,
+    postgresql-binary    >= 0.5   && <0.6,
+
+    -- Parallelism
+    async                >= 2.0   && <2.1,
+    stm                  >= 2.2   && <2.5,
+    parallel             >= 3.2   && <3.3,
+    stm-conduit          >= 2.5   && <2.6,
+    stm-chans            >= 2.0   && <3.1,
+
+    -- Text
+    template-haskell     >= 2.8   && <3.0,
+
+    -- Control flow
+    mtl                  >= 2.2   && <3.0,
+    transformers         >= 0.4   && <0.5
+
+    -- Testing
+    -- criterion            -any
+
+  if flag(j) && impl(ghc >= 7.8)
+    ghc-options: -j3
+
+  C-sources:
+    cbits/array_conversion.c
+
+  cc-options:         -std=c99 -O2 -fPIC
+  Include-dirs:       cbits
+  ghc-options:
+    -O2
+    -funbox-strict-fields
+    "-with-rtsopts=-N"
+    -rtsopts
+    -threaded 
+
+  default-language:    Haskell2010
+  hs-source-dirs:      src
diff --git a/src/Database/PostgreSQL/Stream.hs b/src/Database/PostgreSQL/Stream.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/PostgreSQL/Stream.hs
@@ -0,0 +1,281 @@
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Database.PostgreSQL.Stream (
+  -- ** SQL query ( result set )
+  query,
+  query_,
+
+  -- ** SQL query ( no result )
+  execute,
+  execute_,
+
+  -- ** SQL query ( cursor )
+  stream,
+  stream_,
+
+  -- ** Transacations
+  commit,
+  rollback,
+
+  -- ** Database pool
+  PoolSettings(..),
+  pgPool,
+  withPgConnection,
+
+  -- ** LibPQ Rexports
+  PQ.Connection,
+  PQ.ExecStatus(..),
+  PQ.ConnStatus(..),
+
+  -- ** Connection initialization
+  ConnSettings(..),
+  connect,
+  connect_alt,
+
+  -- ** SQL quasiquoter
+  sql,
+
+  -- ** SQL formatting
+  fmtSQL,
+  fmtQuery,
+  printSQL,
+
+  --ver,
+  module Database.PostgreSQL.Stream.Types
+) where
+
+import Database.PostgreSQL.Stream.Types
+import Database.PostgreSQL.Stream.FromRow
+import Database.PostgreSQL.Stream.QueryBuilder
+import Database.PostgreSQL.Stream.Connection
+
+import qualified PostgreSQLBinary.Decoder as PD
+import qualified Database.PostgreSQL.LibPQ as PQ
+
+import Data.Int
+import Data.Text
+import Data.Pool
+import Data.Monoid
+import Data.ByteString (ByteString)
+import Data.ByteString.Char8 as B8
+import qualified Data.Vector.Storable as V
+import Data.UUID.V4(nextRandom)
+import Data.UUID(toASCIIBytes, toWords)
+
+import Control.Exception as E
+import Control.Monad.Trans
+import Control.Applicative
+import Control.Monad.Trans.Resource (ResourceT, MonadBaseControl)
+
+import qualified Data.Conduit as C
+import qualified Data.Conduit.List as CL
+
+import Data.Version (showVersion)
+import qualified Paths_pgstream as Paths
+
+ver :: String
+ver = showVersion Paths.version
+
+-------------------------------------------------------------------------------
+-- Query
+-------------------------------------------------------------------------------
+
+-- Execute SQL query with arguments.
+query :: (FromRow r, ToSQL q) => PQ.Connection -> Query -> q -> IO [r]
+query conn q args = do
+  -- Formatted query
+  let query = fmtQuery q args
+  -- Execute the query
+  result <- PQ.execParams conn query [] PQ.Binary
+  case result of
+    Nothing -> throw (QueryError "Query execution error." q)
+    Just pqres -> do
+      status <- PQ.resultStatus pqres
+      onError pqres q
+      parseRows q pqres
+
+-- Execute SQL query without arguments.
+query_ :: (FromRow r) => PQ.Connection -> Query -> IO [r]
+query_ conn q = do
+  -- Execute the query
+  result <- PQ.execParams conn (fromQuery q) [] PQ.Binary
+  case result of
+    Nothing -> throw (QueryError "Query execution error." q)
+    Just pqres -> do
+      status <- PQ.resultStatus pqres
+      onError pqres q
+      parseRows q pqres
+
+-------------------------------------------------------------------------------
+-- Execute
+-------------------------------------------------------------------------------
+
+execute :: (ToSQL q) => PQ.Connection -> Query -> q -> IO PQ.ExecStatus
+execute conn q args = do
+  -- Formatted query
+  let query = fmtQuery q args
+  -- Execute the query
+  result <- PQ.execParams conn query [] PQ.Binary
+  case result of
+    Nothing -> throw (QueryError "Query execution error." q)
+    Just pqres -> do
+      status <- PQ.resultStatus pqres
+      onError pqres q
+      return status
+
+execute_ :: PQ.Connection -> Query -> IO PQ.ExecStatus
+execute_ conn q = do
+  -- Execute the query
+  result <- PQ.execParams conn (fromQuery q) [] PQ.Binary
+  case result of
+    Nothing -> throw (QueryError "Query execution error." q)
+    Just pqres -> do
+      status <- PQ.resultStatus pqres
+      onError pqres q
+      return status
+
+-------------------------------------------------------------------------------
+-- Error Handling
+-------------------------------------------------------------------------------
+
+onError :: PQ.Result -> Query -> IO ()
+onError pqres q = do
+  status <- PQ.resultStatus pqres
+  case status of
+    PQ.FatalError -> do
+      res <- PQ.resultErrorMessage pqres
+      case res of
+        Nothing -> return () -- should never happen
+        Just err -> throw (QueryError (B8.unpack err) q)
+    _ -> return ()
+
+-------------------------------------------------------------------------------
+-- Transaction
+-------------------------------------------------------------------------------
+
+data IsolationLevel
+  = DefaultIsolationLevel
+  | ReadCommitted
+  | RepeatableRead
+  | Serializable
+  deriving (Show, Eq, Ord, Enum, Bounded)
+
+data ReadWriteMode
+  = DefaultReadWriteMode
+  | ReadWrite
+  | ReadOnly
+   deriving (Show, Eq, Ord, Enum, Bounded)
+
+data TransactionMode = TransactionMode
+  { isolationLevel :: !IsolationLevel
+  , readWriteMode  :: !ReadWriteMode
+  } deriving (Show, Eq)
+
+beginMode :: TransactionMode -> PQ.Connection -> IO PQ.ExecStatus
+beginMode mode conn = do
+  let begin_query = (Query (mconcat ["BEGIN", isolevel, readmode, ";"]))
+  execute_ conn begin_query
+
+  where
+    isolevel =
+      case isolationLevel mode of
+        DefaultIsolationLevel -> ""
+        ReadCommitted  -> " ISOLATION LEVEL READ COMMITTED"
+        RepeatableRead -> " ISOLATION LEVEL REPEATABLE READ"
+        Serializable   -> " ISOLATION LEVEL SERIALIZABLE"
+    readmode =
+      case readWriteMode mode of
+        DefaultReadWriteMode -> ""
+        ReadWrite -> " READ WRITE"
+        ReadOnly  -> " READ ONLY"
+
+defaultTransactionMode :: TransactionMode
+defaultTransactionMode = TransactionMode
+                           defaultIsolationLevel
+                           defaultReadWriteMode
+
+defaultIsolationLevel ::IsolationLevel
+defaultIsolationLevel = DefaultIsolationLevel
+
+defaultReadWriteMode :: ReadWriteMode
+defaultReadWriteMode = DefaultReadWriteMode
+
+-- | Rollback a transaction.
+rollback :: PQ.Connection -> IO ()
+rollback conn = execute_ conn "ABORT" >> return ()
+
+-- | Commit a transaction.
+commit :: PQ.Connection -> IO ()
+commit conn = execute_ conn "COMMIT" >> return ()
+
+-- | Begin a transaction.
+begin :: PQ.Connection -> IO PQ.ExecStatus
+begin = beginMode defaultTransactionMode
+
+withTransactionMode :: TransactionMode -> PQ.Connection -> IO a -> IO a
+withTransactionMode mode conn act =
+  mask $ \restore -> do
+    beginMode mode conn
+    r <- restore act `E.onException` rollback conn
+    commit conn
+    return r
+
+-------------------------------------------------------------------------------
+-- Streaming Queries
+-------------------------------------------------------------------------------
+
+newCursor :: IO (Identifier)
+newCursor = do
+  uid <- nextRandom
+  let (a,b,c,d) = toWords uid
+  let bshow = B8.pack . show
+  return $ Identifier ("cursor" <> bshow a <> bshow b <> bshow c <> bshow d)
+
+stream :: (FromRow r, ToSQL a, MonadBaseControl IO m, MonadIO m) =>
+     PQ.Connection       -- ^ Connection
+  -> Query               -- ^ Query
+  -> a                   -- ^ Query arguments
+  -> Int                 -- ^ Batch size
+  -> C.Source m [r]      -- ^ Source conduit
+stream conn q args n = do
+  -- Generate a new cursor from a uuid
+  cursor_name <- liftIO $ newCursor
+  liftIO $ beginMode defaultTransactionMode conn
+
+  let subsql = fmtQuery q args
+  let cursor_query = Query (mconcat [ "DECLARE {1} NO SCROLL CURSOR FOR ", subsql ])
+
+  -- Execute the cursor setup
+  liftIO $ execute conn cursor_query (Only cursor_name)
+
+  let fetch_cursor = [sql|FETCH FORWARD {1} FROM {2};|]
+
+  res <- C.tryC (loop conn fetch_cursor cursor_name n)
+  case res of
+    Left (err :: QueryError) -> liftIO $ do
+      -- On SQL exception, rollback the transaction abort the conduit
+      rollback conn
+      throwIO err
+      -- On SQL success, commit
+    Right _ -> liftIO $ commit conn
+
+  where
+   loop conn q cursor_name n = do
+      res <- liftIO $ query conn q (n, cursor_name)
+      case res of
+        [] -> return ()
+        _  -> C.yield res >> loop conn q cursor_name n
+
+stream_ :: (FromRow r, MonadBaseControl IO m, MonadIO m) =>
+     PQ.Connection      -- ^ Connection
+  -> Query              -- ^ Query
+  -> Int                -- ^ Batch size
+  -> C.Source m [r]     -- ^ Source conduit
+stream_ conn q n = stream conn q () n
+
+printSQL :: Query -> IO ()
+printSQL (Query bs) = B8.putStrLn bs
diff --git a/src/Database/PostgreSQL/Stream/Connection.hs b/src/Database/PostgreSQL/Stream/Connection.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/PostgreSQL/Stream/Connection.hs
@@ -0,0 +1,71 @@
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Database.PostgreSQL.Stream.Connection (
+  PoolSettings(..),
+  ConnSettings(..),
+  defaultPoolSettings,
+
+  pgPool,
+  pgPoolSettings,
+  withPgConnection,
+
+  connect,
+  connect_alt,
+) where
+
+import Data.Monoid
+import Data.Pool
+import Control.Applicative
+import Data.Time.Clock (NominalDiffTime)
+
+import Data.ByteString (ByteString)
+import qualified Database.PostgreSQL.LibPQ as PQ
+
+-------------------------------------------------------------------------------
+-- Connection Pools
+-------------------------------------------------------------------------------
+
+data PoolSettings = PoolSettings
+  { _stripes   :: Int             -- ^ Stripe count. The number of distinct sub-pools to maintain. The smallest acceptable value is 1.
+  , _keepalive :: NominalDiffTime -- ^ Amount of time for which an unused resource is kept open. The smallest acceptable value is 0.5 seconds.
+  , _affinity  :: Int             -- ^ Maximum number of resources to keep open per stripe. The smallest acceptable value is 1.
+  } deriving (Eq, Ord, Show)
+
+defaultPoolSettings :: PoolSettings
+defaultPoolSettings = PoolSettings { _stripes = 1, _keepalive = 10, _affinity = 10 }
+
+pgPool :: PQ.Connection -> IO (Pool PQ.Connection)
+pgPool conn = createPool (pure conn) PQ.finish 1 10 10
+
+pgPoolSettings :: PoolSettings -> PQ.Connection -> IO (Pool PQ.Connection)
+pgPoolSettings PoolSettings{..} conn = createPool (pure conn) PQ.finish _stripes _keepalive _affinity
+
+withPgConnection :: PQ.Connection -> (PQ.Connection -> IO b) -> IO b
+withPgConnection conn action = do
+  pool <- pgPool conn
+  withResource pool action
+
+data ConnSettings = ConnSettings
+  { _host     :: ByteString
+  , _dbname   :: ByteString
+  , _user     :: ByteString
+  , _password :: Maybe ByteString
+  } deriving (Eq, Ord, Show, Read)
+
+_connect :: ByteString -> IO (Either PQ.ConnStatus PQ.Connection)
+_connect connstr = do
+  conn <- PQ.connectdb connstr
+  rc <- PQ.status conn
+  case rc of
+    PQ.ConnectionOk -> return (Right conn)
+    _               -> return (Left rc)
+
+connect_alt :: ByteString -> IO (Either PQ.ConnStatus PQ.Connection)
+connect_alt = _connect
+
+connect :: ConnSettings -> IO (Either PQ.ConnStatus PQ.Connection)
+connect (ConnSettings host db user Nothing) = _connect $
+  mconcat [ "dbname=" <> db , " host=" <> host , " user=" <> user ]
+connect (ConnSettings host db user (Just password)) =_connect $
+  mconcat [ "dbname=" <> db , " host=" <> host , " user=" <> user, " password=" <> password ]
diff --git a/src/Database/PostgreSQL/Stream/FromRow.hs b/src/Database/PostgreSQL/Stream/FromRow.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/PostgreSQL/Stream/FromRow.hs
@@ -0,0 +1,409 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+module Database.PostgreSQL.Stream.FromRow (
+  FromRow(..),
+  FromField(..),
+  Row(..),
+  field,
+
+  runRowParser,
+  parseRows,
+) where
+
+import Database.PostgreSQL.Stream.Types
+
+import Data.Int
+
+import Control.Applicative
+import Control.Exception
+import Control.Monad (when)
+import Control.Monad.Trans
+import Control.Monad.Trans.State
+import Control.Monad.Trans.Reader
+
+import Data.UUID (UUID)
+import Data.Word (Word8)
+import Data.Time.Clock
+import Data.Time.LocalTime
+import Data.Fixed
+import Data.Time.Calendar
+import Data.Scientific (Scientific)
+import Data.ByteString (ByteString)
+import qualified Data.ByteString.Lazy as BL
+import Data.ByteString.Internal (toForeignPtr)
+import qualified Data.Vector.Storable as V
+import qualified Data.Vector.Storable.Mutable as VM
+
+import Data.Text (Text)
+import qualified Data.Text.Lazy as TL
+
+import qualified PostgreSQLBinary.Decoder as PD
+import qualified Database.PostgreSQL.LibPQ as PQ
+
+import Unsafe.Coerce
+import System.IO.Unsafe
+
+import Foreign.C
+import Foreign.Ptr
+import Foreign.ForeignPtr.Safe
+
+-------------------------------------------------------------------------------
+-- FFI
+-------------------------------------------------------------------------------
+
+foreign import ccall unsafe "array_conversion.h extract_int4_array"
+  extract_int4_array :: Ptr Word8 -> Ptr CInt -> CInt -> IO Int
+foreign import ccall unsafe "array_conversion.h extract_float_array"
+  extract_numeric_array :: Ptr Word8 -> Ptr CInt -> CInt -> IO Int
+
+type ExFun = Ptr Word8 -> Ptr CInt -> CInt -> IO Int
+
+-------------------------------------------------------------------------------
+-- Rows
+-------------------------------------------------------------------------------
+
+class FromRow a where
+    fromRow :: RowParser a
+
+instance (FromField a) => FromRow (Only a) where
+    fromRow = Only <$> field
+
+instance (FromField a, FromField b) => FromRow (a,b) where
+    fromRow = (,) <$> field <*> field
+
+instance (FromField a, FromField b, FromField c) => FromRow (a,b,c) where
+    fromRow = (,,) <$> field <*> field <*> field
+
+instance (FromField a, FromField b, FromField c, FromField d) => FromRow (a,b,c,d) where
+    fromRow = (,,,) <$> field <*> field <*> field <*> field
+
+instance (FromField a, FromField b, FromField c, FromField d, FromField e) => FromRow (a,b,c,d,e) where
+    fromRow = (,,,,) <$> field <*> field <*> field <*> field <*> field
+
+instance (FromField a, FromField b, FromField c, FromField d, FromField e, FromField f) => FromRow (a,b,c,d,e,f) where
+    fromRow = (,,,,,) <$> field <*> field <*> field <*> field <*> field <*> field
+
+instance (FromField a, FromField b, FromField c, FromField d, FromField e, FromField f, FromField g) => FromRow (a,b,c,d,e,f,g) where
+    fromRow = (,,,,,,) <$> field <*> field <*> field <*> field <*> field <*> field <*> field
+
+-------------------------------------------------------------------------------
+-- FieldConversion
+-------------------------------------------------------------------------------
+
+data Row = Row
+  { row        :: {-# UNPACK #-} !PQ.Row
+  , rowresult  :: !PQ.Result
+  }
+
+type FieldParser a = (PQ.Oid, Int, Maybe ByteString) -> a
+
+class FromField a where
+    -- Unchecked type conversion from raw postgres bytestring
+    fromField :: (PQ.Oid, Int, Maybe ByteString) -> a
+
+-- int2
+instance FromField Int8 where
+    fromField (ty, length, Just bs) = case PD.int bs of { Right x -> x }
+    fromField _ = throw $ ConversionError "Excepted non-null int2"
+
+-- int4
+instance FromField Int16 where
+    fromField (ty, length, Just bs) = case PD.int bs of { Right x -> x }
+    fromField _ = throw $ ConversionError "Excepted non-null int4"
+
+-- int4
+instance FromField Int32 where
+    fromField (ty, length, Just bs) = case PD.int bs of { Right x -> x }
+    fromField _ = throw $ ConversionError "Excepted non-null int4"
+
+-- int8
+instance FromField Int64 where
+    fromField (ty, length, Just bs) = case PD.int bs of { Right x -> x }
+    fromField _ = throw $ ConversionError "Excepted non-null int8"
+
+-- int8
+instance FromField Int where
+    fromField (ty, length, Just bs) = case PD.int bs of { Right x -> x }
+    fromField _ = throw $ ConversionError "Excepted non-null int4"
+
+-- float4
+instance FromField Float where
+    fromField (ty, length, Just bs) = case PD.float4 bs of { Right x -> x }
+    fromField _ = throw $ ConversionError "Excepted non-null float4"
+
+-- float8
+instance FromField Double where
+    fromField (ty, length, Just bs) = case PD.float8 bs of { Right x -> x }
+    fromField _ = throw $ ConversionError "Excepted non-null float8"
+
+-- integer
+instance FromField Integer where
+    fromField (ty, length, Just bs) = case PD.int bs of { Right x -> x }
+    fromField _ = throw $ ConversionError "Excepted non-null integer"
+
+-- numeric
+instance FromField Scientific where
+    fromField (ty, length, Just bs) = case PD.numeric bs of { Right x -> x }
+    fromField _ = throw $ ConversionError "Excepted non-null numeric"
+
+-- uuid
+instance FromField UUID where
+    fromField (ty, length, Just bs) = case PD.uuid bs of { Right x -> x }
+    fromField _ = throw $ ConversionError "Excepted non-null uuid"
+
+-- char
+instance FromField Char where
+    fromField (ty, length, Just bs) = case PD.char bs of { Right x -> x }
+    fromField _ = throw $ ConversionError "Excepted non-null char"
+
+-- text
+instance FromField Text where
+    fromField (ty, length, Just bs) = case PD.text bs of
+      Left x -> throw $ ConversionError "Malformed bytestring."
+      Right x -> x
+    fromField _ = throw $ ConversionError "Excepted non-null text"
+
+instance FromField TL.Text where
+    fromField (ty, length, Just bs) = case PD.text bs of
+      Left x -> throw $ ConversionError "Malformed bytestring."
+      Right x -> TL.fromStrict x
+    fromField _ = throw $ ConversionError "Excepted non-null text"
+
+-- bytea
+instance FromField ByteString where
+    fromField (ty, length, Just bs) = case PD.bytea bs of { Right x -> x }
+    fromField _ = throw $ ConversionError "Excepted non-null bytea"
+
+instance FromField BL.ByteString where
+    fromField (ty, length, Just bs) = case PD.bytea bs of { Right x -> BL.fromStrict x }
+    fromField _ = throw $ ConversionError "Excepted non-null bytea"
+
+-- bool
+instance FromField Bool where
+    fromField (ty, length, Just bs) = case PD.bool bs of { Right x -> x }
+    fromField _ = throw $ ConversionError "Excepted non-null bool"
+
+-- date
+instance FromField Day where
+    fromField (ty, length, Just bs) = case PD.date bs of { Right x -> x }
+    fromField _ = throw $ ConversionError "Excepted non-null date"
+
+-- time
+instance FromField TimeOfDay where
+    fromField (ty, length, Just bs) = case PD.time True bs of { Right x -> x }
+    fromField _ = throw $ ConversionError "Excepted non-null date"
+
+instance FromField (TimeOfDay, TimeZone) where
+    fromField (ty, length, Just bs) = case PD.timetz True bs of { Right x -> x }
+    fromField _ = throw $ ConversionError "Excepted non-null date"
+
+instance FromField UTCTime where
+    fromField (ty, length, Just bs) = case PD.timestamptz True bs of { Right x -> x }
+    fromField _ = throw $ ConversionError "Excepted non-null date"
+
+instance FromField NominalDiffTime where
+    fromField (ty, length, Just bs) = case PD.int bs of { Right x -> (fromIntegral (x :: Int)) }
+    fromField _ = throw $ ConversionError "Excepted non-null date"
+
+instance FromField DiffTime where
+    fromField (ty, length, Just bs) = case PD.interval True bs of { Right x -> x }
+    fromField _ = throw $ ConversionError "Excepted non-null date"
+
+instance FromField LocalTime where
+    fromField (ty, length, Just bs) = case PD.timestamp True bs of { Right x -> x }
+    fromField _ = throw $ ConversionError "Excepted non-null date"
+
+-- money
+instance FromField (Fixed E3) where
+    fromField (ty, length, Just bs) = case PD.int bs of { Right x -> fromIntegral (x :: Int) / 100 }
+    fromField _ = throw $ ConversionError "Excepted non-null money"
+
+instance FromField (Fixed E2) where
+    fromField (ty, length, Just bs) = case PD.int bs of { Right x -> fromIntegral (x :: Int) / 100 }
+    fromField _ = throw $ ConversionError "Excepted non-null money"
+
+-- nullable
+instance FromField a => FromField (Maybe a) where
+    fromField (_, _, Nothing) = Nothing
+    fromField x = Just (fromField x)
+
+-- int4[]
+instance FromField (V.Vector Int32) where
+    fromField (ty, arrlength, Just bs) = unsafeDupablePerformIO $ int4vector bs arrlength
+    fromField _ = throw $ ConversionError "Excepted non-null int4[]"
+
+-- float4[]
+instance FromField (V.Vector Float) where
+    fromField (ty, arrlength, Just bs) = unsafeDupablePerformIO $ float32vector bs arrlength
+    fromField _ = throw $ ConversionError "Excepted non-null float4[]"
+
+-- Converter that is independent of the given length and type
+converter :: Show e => (ByteString -> Either e a) -> (PQ.Oid, Int, Maybe ByteString) -> a
+converter f (_, _, Just bs) = case f bs of
+  Left err -> throw $ ConversionError (show err)
+  Right val -> val
+converter f (_, _, Nothing) = throw $ ConversionError "Excepted non-null"
+
+-------------------------------------------------------------------------------
+-- Byte Shuffling
+-------------------------------------------------------------------------------
+
+--  * Postgres varlena array has the following internal structure:
+--    7  *    <vl_len_>     - standard varlena header word
+--    8  *    <ndim>        - number of dimensions of the array
+--    9  *    <dataoffset>  - offset to stored data, or 0 if no nulls bitmap
+--   10  *    <elemtype>    - element type OID
+--   11  *    <dimensions>  - length of each array axis (C array of int)
+--   12  *    <lower bnds>  - lower boundary of each dimension (C array of int)
+--   13  *    <null bitmap> - bitmap showing locations of nulls (OPTIONAL)
+--   14  *    <actual data> - whatever is the stored data
+
+-- Binary cursor overhead is 20 bytes.
+calculateSize :: PQ.Oid -> Int -> Int
+calculateSize ty len =
+  case ty of
+    PQ.Oid 16   -> 1                  -- bool
+    PQ.Oid 21   -> 2                  -- int2
+    PQ.Oid 23   -> 4                  -- int4
+    PQ.Oid 20   -> 8                  -- int8
+    PQ.Oid 700  -> 4                  -- float4
+    PQ.Oid 701  -> 8                  -- float8
+    PQ.Oid 25   -> -1                 -- text
+    PQ.Oid 2950 -> -1                 -- uuid
+    PQ.Oid 1007 -> (len - 20) `div` 8 -- int4[]
+    PQ.Oid 1016 -> (len - 20) `div` 8 -- int8[]
+    PQ.Oid 1021 -> (len - 20) `div` 8 -- float4[]
+    _ -> error $ "Size not yet implemented" ++ (show ty)
+
+--    16   -> bool
+--    17   -> bytea
+--    18   -> char
+--    19   -> name
+--    20   -> int8
+--    21   -> int2
+--    23   -> int4
+--    24   -> regproc
+--    25   -> text
+--    26   -> oid
+--    27   -> tid
+--    28   -> xid
+--    29   -> cid
+--    700  -> float4
+--    701  -> float8
+--    702  -> abstime
+--    703  -> reltime
+--    704  -> tinterval
+--    705  -> unknown
+--    790  -> money
+--    1042 -> bpchar
+--    1043 -> varchar
+--    1082 -> date
+--    1083 -> time
+--    1114 -> timestamp
+--    1184 -> timestamptz
+--    1186 -> interval
+--    1266 -> timetz
+--    1560 -> bit
+--    1562 -> varbit
+--    1700 -> numeric
+--    2249 -> record
+--    2278 -> void
+--    2950 -> uuid
+
+-------------------------------------------------------------------------------
+-- Vector Extraction
+-------------------------------------------------------------------------------
+
+{-# INLINE extractor #-}
+extractor :: ExFun -> ByteString -> Int -> IO (V.Vector a)
+extractor f bs len = do
+  vec <- VM.new len
+  let (fptr, _, _) = toForeignPtr bs
+  let (aptr, _) = VM.unsafeToForeignPtr0 vec
+
+  rc <- withForeignPtr fptr $ \iptr ->
+          withForeignPtr aptr $ \optr ->
+            f iptr optr (fromIntegral len)
+
+  -- XXX better error handling
+  when (rc /= len) $ throwIO $ ConversionError "Extraction kernel malfunctioned."
+  ovec <- V.unsafeFreeze vec
+  -- CInt and Int look the same in memory, so coercion is safe
+  return (unsafeCoerce ovec)
+
+{-# NOINLINE float32vector #-}
+float32vector :: ByteString -> Int -> IO (V.Vector Float)
+float32vector = extractor extract_numeric_array
+
+{-# NOINLINE int4vector #-}
+int4vector :: ByteString -> Int -> IO (V.Vector Int32)
+int4vector = extractor extract_int4_array
+
+-------------------------------------------------------------------------------
+-- Field Conversion
+-------------------------------------------------------------------------------
+
+-- | Result set field
+field :: FromField a => RowParser a
+field = fieldWith fromField
+
+newtype RowParser a = RP { unRP :: ReaderT Row (State PQ.Column) a }
+   deriving ( Functor, Applicative, Monad )
+
+runRowParser :: RowParser a -> Row -> a
+runRowParser parser rw = evalState (runReaderT (unRP parser) rw) 0
+
+fieldWith :: FieldParser a -> RowParser a
+fieldWith fieldP = RP $ do
+    Row{..} <- ask
+    column <- lift get
+    lift (put (column + 1))
+    let
+        -- !ncols = PQ.nfields rowresult
+        !result = rowresult
+        !typeOid = unsafeDupablePerformIO $ PQ.ftype result column
+        !len = unsafeDupablePerformIO $ PQ.getlength result row column
+        !buffer = unsafeDupablePerformIO $ PQ.getvalue result row column
+    return $ fieldP (typeOid, calculateSize typeOid len, buffer)
+
+-------------------------------------------------------------------------------
+-- Result Sets
+-------------------------------------------------------------------------------
+
+rowLoop :: (Ord n, Num n) => n -> n -> (n -> IO a) -> IO [a]
+rowLoop lo hi m = loop hi []
+  where
+    loop !n !as
+      | n < lo = return as
+      | otherwise = do
+           a <- m n
+           loop (n-1) (a:as)
+
+parseRows :: FromRow r => Query -> PQ.Result -> IO [r]
+parseRows q result = do
+  status <- liftIO $ PQ.resultStatus result
+  case status of
+    PQ.EmptyQuery ->
+       liftIO $ throwIO $ QueryError "query: Empty query" q
+
+    PQ.CommandOk -> do
+       liftIO $ throwIO $ QueryError "query resulted in a command response" q
+
+    PQ.TuplesOk -> do
+        nrows <- liftIO $ PQ.ntuples result
+        --ncols <- liftIO $ PQ.nfields result
+        xs <- rowLoop 0 (nrows-1) $ \row -> do
+            let rw = Row row result
+            {-coltype <- liftIO $ PQ.ftype result c-}
+            {-len <- liftIO $ PQ.getlength result row c-}
+            {-size <- liftIO $ PQ.fsize result c-}
+            {-buffer <- liftIO $ PQ.getvalue result row c-}
+            return $ runRowParser fromRow rw
+        return xs
+
+    _ -> do
+       err <- liftIO $ PQ.resultErrorMessage result
+       liftIO $ throwIO $ QueryError (show err) q
diff --git a/src/Database/PostgreSQL/Stream/Parallel.hs b/src/Database/PostgreSQL/Stream/Parallel.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/PostgreSQL/Stream/Parallel.hs
@@ -0,0 +1,34 @@
+module Database.PostgreSQL.Stream.Parallel (
+  parallelStream,
+) where
+
+import Control.Monad.Trans
+import Control.Concurrent
+import Control.Concurrent.STM
+
+import Data.Conduit
+import Data.Conduit.TMChan
+import Data.Conduit.TQueue
+import Control.Monad.Trans.Resource (runResourceT, ResourceT)
+
+import Database.PostgreSQL.Stream.Connection
+import qualified Database.PostgreSQL.LibPQ as PQ
+
+parallelStream ::
+  PQ.Connection
+  -> (PQ.Connection -> Source (ResourceT IO) a)  -- Source
+  -> Sink a (ResourceT IO) ()                    -- Sink
+  -> IO ()
+parallelStream conn producer consumer = do
+  chan <- atomically $ newTBMChan 32
+  withPgConnection conn $ \c -> do
+
+    tid <- forkIO . runResourceT
+         $ producer c
+        $$ sinkTBMChan chan True
+
+    res <- runResourceT
+         $ sourceTBMChan chan
+        $$ consumer
+
+    print res
diff --git a/src/Database/PostgreSQL/Stream/QueryBuilder.hs b/src/Database/PostgreSQL/Stream/QueryBuilder.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/PostgreSQL/Stream/QueryBuilder.hs
@@ -0,0 +1,187 @@
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE TypeSynonymInstances #-}
+
+module Database.PostgreSQL.Stream.QueryBuilder (
+  -- ** Quasiquoter
+   sql,
+
+  -- ** Query formatting
+  fmtQuery,
+  fmtSQL,
+
+  -- ** Typeclasses
+  ToSQL(..),
+  ToField(..),
+) where
+
+import Database.PostgreSQL.Stream.Types
+
+import Data.Int
+import Data.Monoid
+import Data.UUID as UUID
+import Data.Text (Text)
+import Data.Text.Encoding (encodeUtf8)
+
+import Language.Haskell.TH
+import Language.Haskell.TH.Quote
+
+import Data.ByteString
+import Data.ByteString.Search
+import Data.ByteString.Lazy (toStrict)
+import qualified Data.ByteString.Char8 as B8
+
+-------------------------------------------------------------------------------
+-- Arguments
+-------------------------------------------------------------------------------
+
+class ToField a where
+  toField :: a -> Action
+
+instance ToField Int where
+  toField = Plain . B8.pack . show
+
+instance ToField Int32 where
+  toField = Plain . B8.pack . show
+
+instance ToField Float where
+  toField = Plain . B8.pack . show
+
+instance ToField Double where
+  toField = Plain . B8.pack . show
+
+instance ToField ByteString where
+  toField = Plain
+
+instance ToField Integer where
+  toField = Plain . B8.pack . show
+
+instance ToField Char where
+  toField = Plain . inQuotes . B8.pack . show
+
+instance ToField String where
+  toField = Plain . inQuotes . B8.pack
+
+instance ToField Text where
+  toField = Plain . inQuotes . encodeUtf8
+
+-- SQL Identifier
+instance ToField Identifier where
+  toField = Plain . unIdentifier
+
+-- SQL Expression
+instance ToField SQL where
+  toField = Plain . unSQL
+
+-- Subquery (without substuttion, discards parameters)
+instance ToField Query where
+  toField (Query a) = Plain a
+
+instance ToField UUID where
+  toField = Plain . inQuotes . UUID.toASCIIBytes
+
+instance ToField Null where
+  toField _ = Plain "null"
+
+instance (ToField a) => ToField (Only a) where
+  toField (Only a)  = toField a
+
+instance (ToField a) => ToField (Maybe a) where
+  toField Nothing  = toField Null
+  toField (Just a) = toField a
+
+instance ToField Bool where
+    toField True  = Plain "true"
+    toField False = Plain "false"
+
+instance ToField Action where
+  toField = id
+
+inQuotes :: ByteString -> ByteString
+inQuotes x = "\'" <> x <> "\'"
+
+-------------------------------------------------------------------------------
+-- ToSQL
+-------------------------------------------------------------------------------
+
+class ToSQL a where
+  toSQL :: a -> (ByteString -> ByteString)
+
+instance ToSQL () where
+  toSQL _ = runFormatter []
+
+instance (ToField a) => ToSQL (Only a) where
+  toSQL (Only a) = runFormatter [toField a]
+
+instance (ToField a) => ToSQL [a] where
+  toSQL a = runFormatter (fmap toField a)
+
+instance (ToField a) => ToSQL (Maybe a) where
+  toSQL Nothing = runFormatter []
+  toSQL (Just a) = runFormatter [toField a]
+
+instance (ToField a, ToField b) => ToSQL (a,b) where
+  toSQL (a,b) = runFormatter [toField a, toField b]
+
+instance (ToField a, ToField b, ToField c) => ToSQL (a,b,c) where
+  toSQL (a,b,c) = runFormatter [toField a, toField b, toField c]
+
+instance (ToField a, ToField b, ToField c, ToField d) => ToSQL (a,b,c,d) where
+  toSQL (a,b,c,d) = runFormatter [toField a, toField b, toField c, toField d]
+
+instance (ToField a, ToField b, ToField c, ToField d, ToField e) => ToSQL (a,b,c,d,e) where
+  toSQL (a,b,c,d,e) = runFormatter [toField a, toField b, toField c, toField d, toField e]
+
+instance (ToField a, ToField b, ToField c, ToField d, ToField e, ToField f) => ToSQL (a,b,c,d,e,f) where
+  toSQL (a,b,c,d,e,f) = runFormatter [toField a, toField b, toField c, toField d, toField e, toField f]
+
+instance (ToField a, ToField b, ToField c, ToField d, ToField e, ToField f, ToField g) => ToSQL (a,b,c,d,e,f,g) where
+  toSQL (a,b,c,d,e,f,g) = runFormatter [toField a, toField b, toField c, toField d, toField e, toField f, toField g]
+
+instance (ToField a, ToField b, ToField c, ToField d, ToField e, ToField f, ToField g, ToField h) => ToSQL (a,b,c,d,e,f,g,h) where
+  toSQL (a,b,c,d,e,f,g,h) = runFormatter [toField a, toField b, toField c, toField d, toField e, toField f, toField g, toField h]
+
+instance (ToField a, ToField b, ToField c, ToField d, ToField e, ToField f, ToField g, ToField h, ToField i) => ToSQL (a,b,c,d,e,f,g,h,i) where
+  toSQL (a,b,c,d,e,f,g,h,i) = runFormatter [toField a, toField b, toField c, toField d, toField e, toField f, toField g, toField h, toField i]
+
+-------------------------------------------------------------------------------
+-- Formatter
+-------------------------------------------------------------------------------
+
+render :: Action -> ByteString
+render (Plain x) = x
+render (Escape x) = error "Not implemented"
+render (EscapeIdentifier x) = error "Not implemented"
+
+sql :: QuasiQuoter
+sql = QuasiQuoter
+  { quotePat  = error "Patterns are not supported"
+  , quoteType = error "Types are not supported"
+  , quoteExp  = sqlExp
+  , quoteDec  = error "Declarations are not supported"
+  }
+
+sqlExp :: String -> Q Exp
+sqlExp = stringE
+
+-- Run the substitutions over a bytestring
+runFormatter :: [Action] -> ByteString -> ByteString
+runFormatter args input = loop args 1 input
+  where
+    loop (x:xs) i s = loop xs (i+1) $ toStrict (replace ("{" <> ix i <> "}") (render x) s)
+    loop [] _ s = s
+
+    ix :: Int -> ByteString
+    ix = B8.pack . show
+
+-------------------------------------------------------------------------------
+-- query
+-------------------------------------------------------------------------------
+
+fmtQuery :: ToSQL a => Query -> a -> ByteString
+fmtQuery q args = toSQL args (fromQuery q)
+
+fmtSQL :: ToSQL a => Query -> a -> SQL
+fmtSQL q args = SQLExpr $ toSQL args (fromQuery q)
diff --git a/src/Database/PostgreSQL/Stream/Types.hs b/src/Database/PostgreSQL/Stream/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/PostgreSQL/Stream/Types.hs
@@ -0,0 +1,88 @@
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+module Database.PostgreSQL.Stream.Types (
+  -- * Core types
+  Identifier(..),
+  SQL(..),
+  Only(..),
+  Null(..),
+  Action(..),
+  Query(..),
+
+  -- * Exceptions
+  QueryError(..),
+  ConversionError(..),
+) where
+
+import Data.Monoid
+import Data.String
+import Data.Typeable
+import Data.ByteString as B
+import Data.ByteString.Char8 as B8
+
+import Control.Exception
+
+-------------------------------------------------------------------------------
+-- Core Types
+-------------------------------------------------------------------------------
+
+-- | Literal SQL logic spliced in as a subexpression
+newtype SQL = SQLExpr { unSQL :: ByteString }
+  deriving (Eq, Ord, Show, IsString, Monoid)
+
+-- | Newtype for a singular result set or argument value.
+newtype Only a = Only { unOnly :: a }
+  deriving (Eq, Ord, Read, Show, Typeable, Functor)
+
+-- | Literal SQL identifier (i.e. table field names), spliced into the SQL query
+-- unquoted.
+newtype Identifier = Identifier { unIdentifier :: ByteString }
+  deriving (Eq, Ord, Show, IsString, Monoid)
+
+-- | SQL Null type
+data Null = Null
+
+-- | SQL Query subexpression
+data Action
+  = Plain ByteString
+  | Escape ByteString
+  | EscapeIdentifier ByteString
+  deriving (Eq, Typeable, Show)
+
+-- | SQL Query
+newtype Query = Query { fromQuery :: ByteString }
+  deriving (Eq, Ord, Typeable)
+
+instance Show Query where
+  show = show . fromQuery
+
+instance IsString Query where
+  fromString s = Query (B8.pack s)
+
+instance Monoid Query where
+  mempty = Query B.empty
+  mappend (Query a) (Query b) = Query (B.append a b)
+  {-# INLINE mappend #-}
+  mconcat xs = Query (B.concat (fmap fromQuery xs))
+
+-------------------------------------------------------------------------------
+-- Failure
+-------------------------------------------------------------------------------
+
+data QueryError = QueryError
+  { qeMessage :: String
+  , qeQuery :: Query
+  } deriving (Eq, Show, Typeable)
+
+instance Exception QueryError
+
+data ConversionError
+  = ConversionError { ceMessage :: String }  -- ^ Bytestring is malformed
+  | UnexpectedNull { ceMessage :: String }   -- ^ Unexpected null value
+  | Incompatible { ceMessage :: String }     -- ^ Type is incompatible with data
+  deriving (Eq, Show, Typeable)
+
+instance Exception ConversionError
