packages feed

postgres-tmp (empty) → 0.1.0.0

raw patch · 4 files changed

+156/−0 lines, 4 filesdep +basedep +bytestringdep +postgresql-simplesetup-changed

Dependencies added: base, bytestring, postgresql-simple, text

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Moritz Kiefer (c) 2016++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 Kiefer 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.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ postgres-tmp.cabal view
@@ -0,0 +1,29 @@+name:                postgres-tmp+version:             0.1.0.0+synopsis:            Create a temporary database that is deleted after performing some operation+description:         Please see README.md+homepage:            https://github.com/cocreature/postgres-tmp#readme+license:             BSD3+license-file:        LICENSE+author:              Moritz Kiefer+maintainer:          moritz.kiefer@purelyfunctional.org+copyright:           2016+category:            Unknown+build-type:          Simple+-- extra-source-files:+cabal-version:       >=1.10+tested-with:         GHC == 7.8.4, GHC == 7.10.3, GHC == 8.0.1++library+  hs-source-dirs:      src+  exposed-modules:     Database.PostgreSQL.Tmp+  build-depends:       base >= 4.7 && < 5+                     , bytestring >= 0.10 && < 0.11+                     , postgresql-simple >= 0.5 && < 0.6+                     , text >= 1.2 && < 1.3+  default-language:    Haskell2010+  ghc-options:         -Wall++source-repository head+  type:     git+  location: https://github.com/cocreature/postgres-tmp
+ src/Database/PostgreSQL/Tmp.hs view
@@ -0,0 +1,95 @@+{-| Create temporary postgresql databases.++The main usecase for this are tests where you don’t want to assume that a certain database exists.+-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Database.PostgreSQL.Tmp +  (withTmpDB+  ,withTmpDB'+  ,newRole+  ,newDB+  ,defaultDB) where++import           Control.Applicative (pure)+import           Control.Exception+import           Data.ByteString (ByteString)+import           Data.Coerce+import           Data.Int+import           Data.Monoid+import qualified Data.Text as T+import           Database.PostgreSQL.Simple+import           Database.PostgreSQL.Simple.Types++-- | Connection string for the @postgres@ database owned by the+-- @postgres@ user+defaultDB :: ByteString+defaultDB = "dbname='postgres' user='postgres'"++-- | The data necessary to connect to the temporary database+data DBInfo =+  DBInfo {dbName :: T.Text+         ,roleName :: T.Text} deriving (Show,Read,Eq,Ord)++-- | Convenience wrapper for 'withTmpDB'' using 'defaultDB'+withTmpDB :: (DBInfo -> IO a) -> IO a+withTmpDB = withTmpDB' defaultDB++-- | Create a temporary database and a temporary role that the+-- callback can operate on. After the action has finished the database+-- and the role are destroyed.+--+-- This function assumes that the connection string points to a+-- database containing the tables called @pg_roles@ and @pg_database@+-- and that the user has the @CREATEDB@ and @CREATEROLE@ privileges.+withTmpDB' :: ByteString -> (DBInfo -> IO a) -> IO a+withTmpDB' conStr f =+  bracket (connectPostgreSQL conStr) close $+    \conn ->+       bracket (newRole conn) (dropRole conn) $ \role -> do+       bracket (newDB conn role) (dropDatabase conn) $ \db -> do+         f (DBInfo {dbName = db, roleName = role})++-- | Create a new role that does not already exist and return its name.+--+-- The new role does not have a password and has the @CREATEDB@+-- privilege. The database that the connection points to is assumed to+-- contain a table called @pg_roles@ with a @rolname@ column.+newRole :: Connection -> IO T.Text+newRole conn =+  do (roles :: [Only T.Text]) <- query_ conn "SELECT rolname FROM pg_roles"+     let newName = freshName "tmp" (coerce roles)+     _ <- execute conn "CREATE USER ? WITH CREATEDB" (Only (Identifier newName))+     pure newName++-- | Drop the role.+dropRole :: Connection -> T.Text -> IO Int64+dropRole conn name = execute conn "DROP ROLE ?" (Only (Identifier name))++-- | Create a new database that is owned by the user.+newDB :: Connection -> T.Text -> IO T.Text+newDB conn role =+  do (dbNames :: [Only T.Text]) <- query_ conn "SELECT datname FROM pg_database"+     let newName = freshName "tmp" (coerce dbNames)+     _ <- execute conn "CREATE DATABASE ? OWNER ?" (Identifier newName,Identifier role)+     pure newName++-- | Drop the database.+dropDatabase :: Connection -> T.Text -> IO Int64+dropDatabase conn name =+  execute conn "DROP DATABASE ?" (Only (Identifier name))++-- | Create a fresh name that is not in the list of already existing names.+--+-- The fresh name is generated by appending a number to the supplied+-- template.+freshName :: T.Text -> [T.Text] -> T.Text+freshName template existingNames = loop 0+-- We could use a Set here to speed up the lookup, however the+-- construction of that Set is linear as well so it would only pay off+-- if at least one of the lookups fails.+  where loop :: Int -> T.Text+        loop i =+          if (template <> T.pack (show i)) `elem` existingNames+             then loop (i + 1)+             else (template <> T.pack (show i))