Utils.hs 4.62 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
{-|
Module      : Gargantext.Database.Util
Description : 
Copyright   : (c) CNRS, 2017-Present
License     : AGPL + CECILL v3
Maintainer  : team@gargantext.org
Stability   : experimental
Portability : POSIX

Here is a longer description of this module, containing some
commentary with @some markup@.
-}

14
{-# LANGUAGE ConstraintKinds   #-}
15 16
{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE NoImplicitPrelude #-}
17
{-# LANGUAGE OverloadedStrings #-}
18
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
19
{-# LANGUAGE RankNTypes                 #-}
20

21
module Gargantext.Database.Utils where
22

23 24 25 26
import Data.ByteString.Char8 (hPutStrLn)
import System.IO (stderr)
import Control.Exception 
import Control.Monad.Error.Class -- (MonadError(..), Error)
27
import Control.Lens (Getter, view)
28
import Control.Monad.Reader
29
import Control.Monad.Except
30
import Data.Aeson (Result(Error,Success), fromJSON, FromJSON)
31 32
import Data.Either.Extra (Either(Left, Right))
import Data.Ini (readIniFile, lookupValue)
33 34 35
import Data.Maybe (maybe)
import Data.Monoid ((<>))
import Data.Profunctor.Product.Default (Default)
36
import Data.Text (unpack, pack)
37
import Data.Typeable (Typeable)
38
import Data.Word (Word16)
39
import Database.PostgreSQL.Simple (Connection, connect)
40 41
import Database.PostgreSQL.Simple.FromField ( Conversion, ResultError(ConversionFailed), fromField, returnError)
import Database.PostgreSQL.Simple.Internal  (Field)
42
import Gargantext.Prelude
43
import Opaleye (Query, Unpackspec, showSqlForPostgres, FromFields, Select, runQuery)
44 45
import System.IO (FilePath)
import Text.Read (read)
46 47
import qualified Data.ByteString      as DB
import qualified Database.PostgreSQL.Simple as PGS
48

49 50
class HasConnection env where
  connection :: Getter env Connection
51

52 53
instance HasConnection Connection where
  connection = identity
54

55
type CmdM' env err m =
56 57 58 59
  ( MonadReader env m
  , MonadError err m
  , MonadIO m
  )
60

61 62 63 64 65 66 67
type CmdM env err m =
  ( CmdM' env err m
  , HasConnection env
  )

type Cmd' env err a = forall m. CmdM' env err m => m a

68 69 70 71 72 73 74 75
type Cmd err a = forall m env. CmdM env err m => m a

-- TODO: ideally there should be very few calls to this functions.
mkCmd :: (Connection -> IO a) -> Cmd err a
mkCmd k = do
  conn <- view connection
  liftIO $ k conn

76
runCmd :: (HasConnection env) => env
77 78 79
       -> Cmd' env err a
       -> IO (Either err a)
runCmd env m = runExceptT $ runReaderT m env
80

81 82
runOpaQuery :: Default FromFields fields haskells => Select fields -> Cmd err [haskells]
runOpaQuery q = mkCmd $ \c -> runQuery c q
83

84 85
formatPGSQuery :: PGS.ToRow a => PGS.Query -> a -> Cmd err DB.ByteString
formatPGSQuery q a = mkCmd $ \conn -> PGS.formatQuery conn q a
86

87 88 89 90 91 92 93 94 95 96 97 98 99 100
-- TODO use runPGSQueryDebug everywhere
runPGSQuery' :: (PGS.ToRow a, PGS.FromRow b) => PGS.Query -> a -> Cmd err [b]
runPGSQuery' q a = mkCmd $ \conn -> PGS.query conn q a

runPGSQuery :: (MonadError err m, MonadReader env m,
                PGS.FromRow r, PGS.ToRow q, MonadIO m, HasConnection env)
                => PGS.Query -> q -> m [r]
runPGSQuery q a = mkCmd $ \conn -> catch (PGS.query conn q a) (printError conn)
  where
    printError c (SomeException e) = do
      q' <- PGS.formatQuery c q a
      hPutStrLn stderr q'
      throw (SomeException e)

101

102 103 104
execPGSQuery :: PGS.ToRow a => PGS.Query -> a -> Cmd err Int64
execPGSQuery q a = mkCmd $ \conn -> PGS.execute conn q a

105
------------------------------------------------------------------------
106 107 108 109 110 111 112 113 114 115 116 117

databaseParameters :: FilePath -> IO PGS.ConnectInfo
databaseParameters fp = do
  ini         <- readIniFile fp
  let ini'' = case ini of
        Left e     -> panic (pack $ "No ini file error" <> show e)
        Right ini' -> ini'

  let val x = case (lookupValue (pack "django") (pack x) ini'') of
        Left _ -> panic (pack $ "no" <> x)
        Right p' -> unpack p'

Alexandre Delanoë's avatar
Alexandre Delanoë committed
118 119 120 121 122 123
  pure $ PGS.ConnectInfo { PGS.connectHost     = val       "DB_HOST"
                         , PGS.connectPort     = read (val "DB_PORT") :: Word16
                         , PGS.connectUser     = val       "DB_USER"
                         , PGS.connectPassword = val       "DB_PASS"
                         , PGS.connectDatabase = val       "DB_NAME"
                         }
124

125
connectGargandb :: FilePath -> IO Connection
126
connectGargandb fp = databaseParameters fp >>= \params -> connect params
127

128 129 130 131 132 133 134 135 136
fromField' :: (Typeable b, FromJSON b) => Field -> Maybe DB.ByteString -> Conversion b
fromField' field mb = do
    v <- fromField field mb
    valueToHyperdata v
      where
          valueToHyperdata v = case fromJSON v of
             Success a  -> pure a
             Error _err -> returnError ConversionFailed field "cannot parse hyperdata"

137 138 139
printSqlOpa :: Default Unpackspec a a => Query a -> IO ()
printSqlOpa = putStrLn . maybe "Empty query" identity . showSqlForPostgres