2 Module : Gargantext.Database.Prelude
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
12 {-# LANGUAGE ConstraintKinds #-}
13 {-# LANGUAGE FlexibleContexts #-}
14 {-# LANGUAGE FlexibleInstances #-}
15 {-# LANGUAGE NoImplicitPrelude #-}
16 {-# LANGUAGE OverloadedStrings #-}
17 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
18 {-# LANGUAGE RankNTypes #-}
20 module Gargantext.Database.Prelude where
22 import Control.Exception
23 import Control.Lens (Getter, view)
24 import Control.Monad.Error.Class -- (MonadError(..), Error)
25 import Control.Monad.Except
26 import Control.Monad.Reader
27 import Control.Monad.Trans.Control (MonadBaseControl)
28 import Data.Aeson (Result(Error,Success), fromJSON, FromJSON)
29 import Data.ByteString.Char8 (hPutStrLn)
30 import Data.Either.Extra (Either(Left, Right))
31 import Data.Ini (readIniFile, lookupValue)
32 import Data.Maybe (maybe)
33 import Data.Monoid ((<>))
34 import Data.Pool (Pool, withResource)
35 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 import Database.PostgreSQL.Simple.FromField ( Conversion, ResultError(ConversionFailed), fromField, returnError)
41 import Database.PostgreSQL.Simple.Internal (Field)
42 import Gargantext.Prelude
43 import Opaleye (Query, Unpackspec, showSqlForPostgres, FromFields, Select, runQuery)
44 import Opaleye.Aggregate (countRows)
45 import System.IO (FilePath)
46 import System.IO (stderr)
47 import Text.Read (read)
48 import qualified Data.ByteString as DB
49 import qualified Data.List as DL
50 import qualified Database.PostgreSQL.Simple as PGS
53 class HasConnectionPool env where
54 connPool :: Getter env (Pool Connection)
56 instance HasConnectionPool (Pool Connection) where
59 type CmdM' env err m =
62 , MonadBaseControl IO m
67 , HasConnectionPool env
70 type Cmd' env err a = forall m. CmdM' env err m => m a
72 type Cmd err a = forall m env. CmdM env err m => m a
74 fromInt64ToInt :: Int64 -> Int
75 fromInt64ToInt = fromIntegral
77 -- TODO: ideally there should be very few calls to this functions.
78 mkCmd :: (Connection -> IO a) -> Cmd err a
81 withResource pool (liftBase . k)
83 runCmd :: (HasConnectionPool env)
84 => env -> Cmd' env err a
86 runCmd env m = runExceptT $ runReaderT m env
88 runOpaQuery :: Default FromFields fields haskells
89 => Select fields -> Cmd err [haskells]
90 runOpaQuery q = mkCmd $ \c -> runQuery c q
92 runCountOpaQuery :: Select a -> Cmd err Int
93 runCountOpaQuery q = do
94 counts <- mkCmd $ \c -> runQuery c $ countRows q
95 -- countRows is guaranteed to return a list with exactly one row so DL.head is safe here
96 pure $ fromInt64ToInt $ DL.head counts
98 formatPGSQuery :: PGS.ToRow a => PGS.Query -> a -> Cmd err DB.ByteString
99 formatPGSQuery q a = mkCmd $ \conn -> PGS.formatQuery conn q a
101 -- TODO use runPGSQueryDebug everywhere
102 runPGSQuery' :: (PGS.ToRow a, PGS.FromRow b) => PGS.Query -> a -> Cmd err [b]
103 runPGSQuery' q a = mkCmd $ \conn -> PGS.query conn q a
105 runPGSQuery :: (MonadError err m, MonadReader env m, MonadBaseControl IO m,
106 PGS.FromRow r, PGS.ToRow q, HasConnectionPool env)
107 => PGS.Query -> q -> m [r]
108 runPGSQuery q a = mkCmd $ \conn -> catch (PGS.query conn q a) (printError conn)
110 printError c (SomeException e) = do
111 q' <- PGS.formatQuery c q a
113 throw (SomeException e)
116 execPGSQuery :: PGS.ToRow a => PGS.Query -> a -> Cmd err Int64
117 execPGSQuery q a = mkCmd $ \conn -> PGS.execute conn q a
119 ------------------------------------------------------------------------
121 databaseParameters :: FilePath -> IO PGS.ConnectInfo
122 databaseParameters fp = do
123 ini <- readIniFile fp
124 let ini'' = case ini of
125 Left e -> panic (pack $ "No ini file error" <> show e)
128 let val x = case (lookupValue (pack "django") (pack x) ini'') of
129 Left _ -> panic (pack $ "no" <> x)
130 Right p' -> unpack p'
132 pure $ PGS.ConnectInfo { PGS.connectHost = val "DB_HOST"
133 , PGS.connectPort = read (val "DB_PORT") :: Word16
134 , PGS.connectUser = val "DB_USER"
135 , PGS.connectPassword = val "DB_PASS"
136 , PGS.connectDatabase = val "DB_NAME"
139 connectGargandb :: FilePath -> IO Connection
140 connectGargandb fp = databaseParameters fp >>= \params -> connect params
142 fromField' :: (Typeable b, FromJSON b) => Field -> Maybe DB.ByteString -> Conversion b
143 fromField' field mb = do
144 v <- fromField field mb
147 valueToHyperdata v = case fromJSON v of
149 Error _err -> returnError ConversionFailed field
150 $ DL.intercalate " " [ "cannot parse hyperdata for JSON: "
154 printSqlOpa :: Default Unpackspec a a => Query a -> IO ()
155 printSqlOpa = putStrLn . maybe "Empty query" identity . showSqlForPostgres