packages feed

postgresql-simple-bind-0.1.0.0: src/Database/PostgreSQL/Simple/Bind/Util.hs

{-# LANGUAGE DataKinds                  #-}
{-# LANGUAGE DeriveGeneric              #-}
{-# LANGUAGE EmptyDataDecls             #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE GADTs                      #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE QuasiQuotes                #-}
{-# LANGUAGE TemplateHaskell            #-}
{-# LANGUAGE TypeFamilies               #-}
{-# LANGUAGE TypeOperators              #-}
{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE FlexibleInstances          #-} 

{-|
  Module:      Database.PostgreSQL.Simple.Bind.Util
  Copyright:   (c) 2016 Al Zohali
  License:     GPL3
  Maintainer:  Al Zohali <zohl@fmap.me>
  Stability:   experimental
 
  Misc auxiliary functions.
-}


module Database.PostgreSQL.Simple.Bind.Util (
    unwrapRow
  , unwrapColumn
  , Options(..)
  , defaultOptions
  , getFunctionDeclaration    
  , generateBindingsModule
  , mkFunctionName
  ) where


import Data.Text                    (Text, append, pack, breakOnEnd, dropEnd)
import Data.List
import Data.Maybe                   (isNothing)
import Database.PostgreSQL.Simple
import Database.PostgreSQL.Simple.Types
import Database.PostgreSQL.Simple.Bind.Representation
import Text.Heredoc
import qualified Data.ByteString.Char8 as BS


-- | Options that specify how to construct the function binding.
data Options = Options {
      nameModifier :: String -> String
      -- ^ Function applied to the function's name.

    , schemaModifier :: String -> String
      -- ^ Function applied to the function's schema name.
  }
{-|
  Default 'Options':
  
   @
   'Options' {
     'nameModifier' = id
     'schemaModifier' = ""
   }
   @
-}
defaultOptions = Options {
      nameModifier = id
    , schemaModifier = (\_ -> "")
  }

-- | Given 'Options' and PostgreSQL function representation construct the name of the corresponding haskell function.
mkFunctionName :: Options -> PGFunction -> String
mkFunctionName opt (PGFunction schema name _ _) = concat $ [
    (schemaModifier opt) schema
  , (nameModifier opt) name
  ]


-- | Remove 'Only' constructor.
unwrapColumn :: [Only a] -> [a]
unwrapColumn = map (\(Only x) -> x)

-- | Remove list and 'Only' constructors.
unwrapRow :: [Only a] -> a
unwrapRow = head . unwrapColumn


-- | Fetch function declaration that can be passed to 'bindFunction'.
getFunctionDeclaration :: Connection -> Text -> IO Text
getFunctionDeclaration conn name = unwrapRow <$> query conn sql (Only name) where
  sql = Query $ BS.pack $ [str| 
      | select 'function '
      |     || p.proname
      |     || '('||pg_catalog.pg_get_function_arguments(p.oid)||')'
      |     || ' returns '||pg_catalog.pg_get_function_result(p.oid)
      | from pg_catalog.pg_proc p
      |      left join pg_catalog.pg_namespace n on n.oid = p.pronamespace
      | where p.proname ~ ('^('|| ? ||')$')
      |   and not p.proisagg
      |   and not p.proiswindow
      |   and p.prorettype != ('pg_catalog.trigger'::pg_catalog.regtype)
      | limit 1;
      |]


-- | Given full path to options, module name and functions' names generate complete module.
generateBindingsModule :: Connection -> Text -> Text -> [Text] -> IO Text
generateBindingsModule conn opt name ns = do
  ds <- mapM (getFunctionDeclaration conn) ns
  let fs = parsePGFunction <$> ds

  let (optPath', optName) = breakOnEnd "." opt
  let optPath = dropEnd 1 optPath'

  let mkList xs = foldl1' append $ ("    " :) $ intersperse "\n  , " xs

  return $ foldl1' append $ [ 
      [str| -- This module was automatically generated. Do not edit it.
      |
      |{-# LANGUAGE DataKinds         #-}
      |{-# LANGUAGE OverloadedStrings #-}
      |{-# LANGUAGE TemplateHaskell   #-}
      |{-# LANGUAGE TypeFamilies      #-}
      |
      |]
    , "module ", name, " where "
    , [str|
      |
      |import Database.PostgreSQL.Simple.Bind (bindFunction)
      |]
    , "import ", optPath, " (", optName, ")"
    , [str|
      |import Database.PostgreSQL.Simple.Bind.Types()
      |
      |concat <$> mapM (bindFunction |], optName, [str|) [
      |]
    , (mkList $ map (("\"" `append`) . (`append` "\""))  ds)
    , "\n  ]"]