]> Git — Sourcephile - gargantext.git/blob - src/Gargantext/Database/Prelude.hs
[TextFlow] Type rename (records missing)
[gargantext.git] / src / Gargantext / Database / Prelude.hs
1 {-|
2 Module : Gargantext.Database.Prelude
3 Description : Specific Prelude for Database management
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
8 Portability : POSIX
9
10 -}
11
12 {-# LANGUAGE ConstraintKinds #-}
13
14 module Gargantext.Database.Prelude where
15
16 import Control.Exception
17 import Control.Lens (Getter, view)
18 import Control.Monad.Error.Class -- (MonadError(..), Error)
19 import Control.Monad.Except
20 import Control.Monad.Reader
21 import Control.Monad.Trans.Control (MonadBaseControl)
22 import Data.Aeson (Result(Error,Success), fromJSON, FromJSON)
23 import Data.ByteString.Char8 (hPutStrLn)
24 import Data.Either.Extra (Either(Left, Right))
25 import Data.Ini (readIniFile, lookupValue)
26 import Data.Pool (Pool, withResource)
27 import Data.Profunctor.Product.Default (Default)
28 import Data.Text (unpack, pack)
29 import Data.Word (Word16)
30 import Database.PostgreSQL.Simple (Connection, connect)
31 import Database.PostgreSQL.Simple.FromField ( Conversion, ResultError(ConversionFailed), fromField, returnError)
32 import Database.PostgreSQL.Simple.Internal (Field)
33 import Opaleye (Query, Unpackspec, showSqlForPostgres, FromFields, Select, runQuery)
34 import Opaleye.Aggregate (countRows)
35 import System.IO (FilePath)
36 import System.IO (stderr)
37 import Text.Read (read)
38 import qualified Data.ByteString as DB
39 import qualified Data.List as DL
40 import qualified Database.PostgreSQL.Simple as PGS
41
42 import Gargantext.Prelude
43 import Gargantext.Prelude.Config (GargConfig())
44
45 -------------------------------------------------------
46 class HasConnectionPool env where
47 connPool :: Getter env (Pool Connection)
48
49 instance HasConnectionPool (Pool Connection) where
50 connPool = identity
51
52 class HasConfig env where
53 hasConfig :: Getter env GargConfig
54
55 instance HasConfig GargConfig where
56 hasConfig = identity
57
58 -------------------------------------------------------
59 type CmdM' env err m =
60 ( MonadReader env m
61 , MonadError err m
62 , MonadBaseControl IO m
63 )
64
65 type CmdM env err m =
66 ( CmdM' env err m
67 , HasConnectionPool env
68 , HasConfig env
69 )
70
71 type Cmd' env err a = forall m. CmdM' env err m => m a
72
73 type Cmd err a = forall m env. CmdM env err m => m a
74
75 fromInt64ToInt :: Int64 -> Int
76 fromInt64ToInt = fromIntegral
77
78 -- TODO: ideally there should be very few calls to this functions.
79 mkCmd :: (Connection -> IO a) -> Cmd err a
80 mkCmd k = do
81 pool <- view connPool
82 withResource pool (liftBase . k)
83
84 runCmd :: (HasConnectionPool env)
85 => env
86 -> Cmd' env err a
87 -> IO (Either err a)
88 runCmd env m = runExceptT $ runReaderT m env
89
90 runOpaQuery :: Default FromFields fields haskells
91 => Select fields
92 -> Cmd err [haskells]
93 runOpaQuery q = mkCmd $ \c -> runQuery c q
94
95 runCountOpaQuery :: Select a -> Cmd err Int
96 runCountOpaQuery q = do
97 counts <- mkCmd $ \c -> runQuery c $ countRows q
98 -- countRows is guaranteed to return a list with exactly one row so DL.head is safe here
99 pure $ fromInt64ToInt $ DL.head counts
100
101 formatPGSQuery :: PGS.ToRow a => PGS.Query -> a -> Cmd err DB.ByteString
102 formatPGSQuery q a = mkCmd $ \conn -> PGS.formatQuery conn q a
103
104 -- TODO use runPGSQueryDebug everywhere
105 runPGSQuery' :: (PGS.ToRow a, PGS.FromRow b) => PGS.Query -> a -> Cmd err [b]
106 runPGSQuery' q a = mkCmd $ \conn -> PGS.query conn q a
107
108 runPGSQuery :: (MonadError err m, MonadReader env m, MonadBaseControl IO m,
109 PGS.FromRow r, PGS.ToRow q, HasConnectionPool env, HasConfig env)
110 => PGS.Query -> q -> m [r]
111 runPGSQuery q a = mkCmd $ \conn -> catch (PGS.query conn q a) (printError conn)
112 where
113 printError c (SomeException e) = do
114 q' <- PGS.formatQuery c q a
115 hPutStrLn stderr q'
116 throw (SomeException e)
117
118
119 execPGSQuery :: PGS.ToRow a => PGS.Query -> a -> Cmd err Int64
120 execPGSQuery q a = mkCmd $ \conn -> PGS.execute conn q a
121
122 ------------------------------------------------------------------------
123
124 databaseParameters :: FilePath -> IO PGS.ConnectInfo
125 databaseParameters fp = do
126 ini <- readIniFile fp
127 let ini'' = case ini of
128 Left e -> panic (pack $ "No ini file error" <> show e)
129 Right ini' -> ini'
130
131 let val x = case (lookupValue (pack "database") (pack x) ini'') of
132 Left _ -> panic (pack $ "no" <> x)
133 Right p' -> unpack p'
134
135 pure $ PGS.ConnectInfo { PGS.connectHost = val "DB_HOST"
136 , PGS.connectPort = read (val "DB_PORT") :: Word16
137 , PGS.connectUser = val "DB_USER"
138 , PGS.connectPassword = val "DB_PASS"
139 , PGS.connectDatabase = val "DB_NAME"
140 }
141
142 connectGargandb :: FilePath -> IO Connection
143 connectGargandb fp = databaseParameters fp >>= \params -> connect params
144
145 fromField' :: (Typeable b, FromJSON b) => Field -> Maybe DB.ByteString -> Conversion b
146 fromField' field mb = do
147 v <- fromField field mb
148 valueToHyperdata v
149 where
150 valueToHyperdata v = case fromJSON v of
151 Success a -> pure a
152 Error _err -> returnError ConversionFailed field
153 $ DL.intercalate " " [ "cannot parse hyperdata for JSON: "
154 , show v
155 ]
156
157 printSqlOpa :: Default Unpackspec a a => Query a -> IO ()
158 printSqlOpa = putStrLn . maybe "Empty query" identity . showSqlForPostgres
159