packages feed

hpqtypes-1.4.0: examples/Catalog.hs

{-# LANGUAGE FlexibleContexts, OverloadedStrings, RecordWildCards
  , ScopedTypeVariables, TypeFamilies #-}
{-# OPTIONS_GHC -Wall #-}

import Control.Arrow (second)
import Control.Monad
import Control.Monad.Base
import Control.Monad.Catch
import Data.Function
import Data.Int
import Data.Monoid
import Data.Monoid.Utils
import Database.PostgreSQL.PQTypes
import Database.PostgreSQL.PQTypes.Internal.Utils (mread)
import System.Console.Readline
import System.Environment
import qualified Data.ByteString.Char8 as BS

-- | Generic 'putStrLn'.
printLn :: MonadBase IO m => String -> m ()
printLn = liftBase . putStrLn

-- | Get connection string from command line argument.
getConnSettings :: IO ConnectionSettings
getConnSettings = do
  args <- getArgs
  case args of
    [conninfo] -> return def { csConnInfo = BS.pack conninfo }
    _ -> do
      prog <- getProgName
      error $ "Usage:" <+> prog <+> "<connection info>"

----------------------------------------

-- | Representation of a book.
data Book = Book {
  bookID       :: Int64
, bookName     :: String
, bookYear     :: Int32
} deriving (Read, Show)

-- | Intermediate representation of 'Book'.
type instance CompositeRow Book = (Int64, String, Int32)

instance PQFormat Book where
  pqFormat _ = "%book_"

instance CompositeFromSQL Book where
  toComposite (bid, name, year) = Book {
    bookID = bid
  , bookName = name
  , bookYear = year
  }

withCatalog :: ConnectionSettings -> IO () -> IO ()
withCatalog cs = bracket_ createStructure dropStructure
  where
    -- | Create needed tables and types.
    createStructure = runDBT (simpleSource cs) def $ do
      printLn "Creating tables..."
      runSQL_ $ mconcat [
          "CREATE TABLE authors_ ("
        , "  id BIGSERIAL NOT NULL"
        , ", name TEXT NOT NULL"
        , ", PRIMARY KEY (id)"
        , ")"
        ]
      runSQL_ $ mconcat [
          "CREATE TABLE books_ ("
        , "  id BIGSERIAL NOT NULL"
        , ", name TEXT NOT NULL"
        , ", year INTEGER NOT NULL"
        , ", author_id BIGINT NOT NULL"
        , ", PRIMARY KEY (id)"
        , ", FOREIGN KEY (author_id) REFERENCES authors_ (id)"
        , ")"
        ]
      runSQL_ $ mconcat [
          "CREATE TYPE book_ AS ("
        , "  id BIGINT"
        , ", name TEXT"
        , ", year INTEGER"
        , ")"
        ]
    -- | Drop previously created database structures.
    dropStructure = runDBT (simpleSource cs) def $ do
      printLn "Dropping tables..."
      runSQL_ "DROP TYPE book_"
      runSQL_ "DROP TABLE books_"
      runSQL_ "DROP TABLE authors_"

----------------------------------------

processCommand :: ConnectionSource -> String -> IO ()
processCommand cs cmd = case parse cmd of
  -- | Display authors.
  ("authors", "") -> runDBT cs def $ do
    runSQL_ "SELECT * FROM authors_ ORDER BY name"
    mapDB_ $ \(aid::Int64, name) -> printLn $ show aid <> ":" <+> name
  -- | Display books.
  ("books", "") -> runDBT cs def $ do
    runSQL_ "SELECT a.name, ARRAY(SELECT (b.id, b.name, b.year)::book_ FROM books_ b WHERE b.author_id = a.id) FROM authors_ a ORDER BY a.name"
    mapDB_ $ \(author, CompositeArray1 (books::[Book])) -> do
      printLn $ author <> ":"
      forM_ books $ \book -> printLn $ "*" <+> show book
  -- | Insert an author.
  ("insert_author", mname) -> case mread mname of
    Just (name::String) -> runDBT cs def . runQuery_ $
      "INSERT INTO authors_ (name) VALUES (" <?> name <+> ")"
    Nothing -> printLn $ "Invalid name"
  -- | Insert a book.
  ("insert_book", mbook) -> case mread mbook of
    Just record -> runDBT cs def . runQuery_ $ rawSQL
      "INSERT INTO books_ (name, year, author_id) VALUES ($1, $2, $3)"
      (record::(String, Int32, Int64))
    Nothing -> printLn $ "Invalid book record"
  -- | Handle unknown commands.
  _ -> printLn $ "Unknown command:" <+> cmd
  where
    parse = second (drop 1) . break (==' ')

-- | Example chain of commands:
--
-- > insert_author "John Doe"
-- > insert_author "Mary Jane"
-- > authors
-- > insert_book ("The Sunset", 2006, 1)
-- > insert_book ("Waterfall", 2011, 2)
-- > insert_book ("The Sunrise", 2013, 1)
-- > books
--
-- If you want to check out exceptions in action,
-- try inserting a book with invalid author id.
main :: IO ()
main = do
  cs <- getConnSettings
  withCatalog cs $ do
    pool <- poolSource (cs { csComposites = ["book_"] }) 1 10 4
    fix $ \next -> readline "> " >>= maybe (printLn "") (\cmd -> do
      when (cmd /= "quit") $ do
        processCommand pool cmd
        addHistory cmd
        next
      )