diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,5 @@
+# Revision history for monetdb-mapi
+
+## 0.1.0.0  -- YYYY-mm-dd
+
+* First version. Released on an unsuspecting world.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2016, Moritz Bruder
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Moritz Bruder nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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/monetdb-mapi.cabal b/monetdb-mapi.cabal
new file mode 100644
--- /dev/null
+++ b/monetdb-mapi.cabal
@@ -0,0 +1,28 @@
+name:                monetdb-mapi
+version:             0.1.0.0
+synopsis:            Mid-level bindings for the MonetDB API (mapi)
+-- description:         
+license:             BSD3
+license-file:        LICENSE
+author:              Moritz Bruder
+maintainer:          muesli4@gmail.com
+-- copyright:           
+category:            Database
+build-type:          Simple
+extra-source-files:  ChangeLog.md
+cabal-version:       >=1.10
+
+source-repository head
+  type:              git
+  location:          git://github.com/muesli4/monetdb-mapi.git
+
+library
+  exposed-modules:     Database.MonetDB.Mapi
+  -- other-modules:       
+  -- other-extensions:    
+  build-depends:       base >=4.8 && <4.10,
+                       bindings-monetdb-mapi ==0.1.*
+
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+
diff --git a/src/Database/MonetDB/Mapi.hs b/src/Database/MonetDB/Mapi.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/MonetDB/Mapi.hs
@@ -0,0 +1,199 @@
+module Database.MonetDB.Mapi
+    (
+    -- * Error handling
+      MapiError
+    , errorType
+    , ErrorType(..)
+
+    -- * Connection parameters
+    , Lang(..)
+    , ConInfo(..)
+    , emptyConInfo
+    , defConInfo
+
+    -- * Connection handling
+    , Connection(..)
+    , connect
+    , disconnect
+    , withConnection
+
+    -- * Queries
+    , quickQuery
+    , quickQuery_
+    ) where
+
+import           Control.Monad
+import           Data.Typeable
+import           Foreign.C.String
+import           Foreign.C.Types
+import           Foreign.Concurrent    as C
+import           Foreign.ForeignPtr
+import           Foreign.Ptr
+import qualified Bindings.MonetDB.Mapi as B
+import qualified Control.Exception     as E
+
+-- | Some IO actions might throw 'MapiError'.
+data MapiError = MapiError
+               { errorType    :: ErrorType
+               }
+               deriving Show
+
+instance E.Exception MapiError where
+    toException                       = E.SomeException
+    fromException (E.SomeException e) = cast e
+
+data ErrorType = Default String
+               | Timeout
+               | Server String
+               deriving Show
+
+-- | The language to be used for a connection.
+data Lang = Sql
+          | Mil
+          | Mal
+          | XQuery
+
+fromLang :: Lang -> String
+fromLang l = case l of
+    Sql    -> "sql"
+    Mil    -> "mil"
+    Mal    -> "mal"
+    XQuery -> "xquery"
+
+newtype Connection = Connection B.Mapi
+
+optCString :: Maybe String -> IO CString
+optCString = maybe (return nullPtr) newCString
+
+throwOnMapiError :: B.Mapi -> IO ()
+throwOnMapiError mapi = do
+    e <- B.mapi_error mapi
+    -- Encountered MERROR
+    if e == B.cMERROR
+    then do
+        cstr <- B.mapi_error_str mapi
+        print cstr
+        s <- peekCString cstr
+        E.throwIO $ MapiError $ Default s
+    else when (e == B.cMTIMEOUT) $ E.throwIO $ MapiError Timeout
+
+throwOnMapiHdlError :: B.Mapi -> B.MapiHdl -> IO ()
+throwOnMapiHdlError mapi hdl = do
+    e <- B.mapi_error mapi
+    when (e == B.cMSERVER) $
+        getServerErrorMsg hdl >>= E.throwIO . MapiError . Server
+
+getServerErrorMsg :: B.MapiHdl -> IO String
+getServerErrorMsg hdl = do
+    cstr <- B.mapi_result_error hdl
+    peekCString cstr
+
+throwOnError :: B.Mapi -> B.MapiHdl -> IO ()
+throwOnError mapi hdl = do
+    throwOnMapiError mapi
+    throwOnMapiHdlError mapi hdl
+
+type Database = String
+
+data ConInfo = ConInfo
+             { ciOptHost  :: Maybe String
+             , ciOptPort  :: Maybe Int
+             , ciUsername :: Maybe String
+             , ciPassword :: Maybe String
+             , ciDbName   :: Database
+             }
+
+-- | It is strongly advised to use 'withConnection', which is exception-safe.
+connect :: ConInfo -> Lang -> IO Connection
+connect (ConInfo mHost mPort mUser mPass dbName) lang = do
+    host <- optCString mHost
+    let port = maybe 0 fromIntegral mPort
+    user <- optCString mUser
+    pass <- optCString mPass
+    langStr <- newCString (fromLang lang)
+    db <- newCString dbName
+    mapi <- B.mapi_connect host port user pass langStr db
+    throwOnMapiError mapi
+    pure $ Connection mapi
+
+disconnect :: Connection -> IO ()
+disconnect (Connection mapi) = B.mapi_disconnect mapi
+
+-- | A 'ConInfo' with all optional fields omitted.
+emptyConInfo :: Database -> ConInfo
+emptyConInfo = ConInfo Nothing Nothing Nothing Nothing
+
+-- | A 'ConInfo' with common default settings, mostly for testing purposes.
+defConInfo :: Database -> ConInfo
+defConInfo = ConInfo Nothing Nothing (Just "monetdb") (Just "monetdb")
+
+-- | Opens a connection, runs the action and closes the connection, even when
+-- an exception has been thrown.
+--
+-- >>> withConnection (defConInfo "test") Mal $ \c -> quickQuery c "io.print(42, 23);"
+-- [["42", "23"]]
+--
+withConnection :: ConInfo -> Lang -> (Connection -> IO a) -> IO a
+withConnection ci l f = E.bracket (connect ci l) disconnect f
+
+
+-- TODO rework Result and Query, that mechanism is terrible
+
+type QueryForeignPtr = ForeignPtr ()
+
+newtype Query = Query QueryForeignPtr
+
+query :: Connection -> String -> IO Query
+query (Connection mapi) qStr = do
+    qCStr <- newCString qStr
+    mapiHdl <- B.mapi_query mapi qCStr
+    throwOnError mapi mapiHdl
+    q <- C.newForeignPtr mapiHdl (B.mapi_close_handle mapiHdl)
+    pure $ Query q
+
+newtype Result = Result QueryForeignPtr
+
+execute :: Query -> IO Result
+execute (Query fMapiHdl) = do
+    withForeignPtr fMapiHdl $ \mapiHdl -> do
+        B.mapi_execute mapiHdl
+        -- TODO check for errors
+    return $ Result fMapiHdl
+
+fetchRow :: Result -> IO (Maybe [String])
+fetchRow (Result fMapiHdl) = 
+    withForeignPtr fMapiHdl $ \mapiHdl -> do
+        nFields <- B.mapi_fetch_row mapiHdl
+        if nFields == 0
+        then return Nothing
+        else do
+            -- TODO mapi_fetch_field returns 0 on error
+            let fetchField i = do
+                    pc <- B.mapi_fetch_field mapiHdl i
+                    sz <- B.mapi_fetch_field_len mapiHdl i
+                    peekCStringLen (pc, fromIntegral sz)
+            Just <$> mapM fetchField [0 .. pred nFields]
+
+fetchRows :: Result -> IO [[String]]
+fetchRows res = go
+  where
+    go = do
+        mRow <- fetchRow res
+        maybe (return []) (\row -> (row :) <$> go) mRow
+
+fieldNames :: Result -> IO [String]
+fieldNames (Result fMapiHdl) = withForeignPtr fMapiHdl $ \mapiHdl -> do
+    nFields <- B.mapi_get_field_count mapiHdl
+    -- FIXME may return 0
+    fieldCStrs <- mapM (B.mapi_get_name mapiHdl) [0 .. pred nFields]
+    mapM peekCString fieldCStrs
+
+-- | Run a query and fetch the result rows.
+quickQuery :: Connection -> String -> IO [[String]]
+quickQuery c s = do
+    q <- query c s
+    r <- execute q
+    fetchRows r
+
+quickQuery_ :: Connection -> String -> IO ()
+quickQuery_ c s = query c s >>= void . execute
