-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathKVSSqlite.hs
96 lines (79 loc) · 4.1 KB
/
KVSSqlite.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
{-# LANGUAGE OverloadedStrings #-}
module InterfaceAdapters.KVSSqlite where
import Polysemy
import Data.Aeson (decode, encode)
import Data.Aeson.Types (FromJSON, ToJSON)
import qualified Data.Text.Lazy as T
import Data.Text.Lazy.Encoding
import Database.SQLite.Simple (NamedParam ((:=)))
import qualified Database.SQLite.Simple as SQL
import Database.SQLite.Simple.FromRow
import InterfaceAdapters.Config
import Polysemy.Input (Input, input)
import Polysemy.Trace (Trace, trace)
import UseCases.KVS (KVS (..))
data KeyValueRow = KeyValueRow T.Text T.Text
deriving (Show)
instance FromRow KeyValueRow where
fromRow = KeyValueRow <$> field <*> field
--instance ToRow KeyValueRow where
-- toRow (KeyValueRow key_ val) = toRow (key_, val)
-- | Run a KVStore effect against a SQLite backend. Requires a Config object as input.
runKvsAsSQLite :: (Member (Embed IO) r, Member (Input Config) r, Member Trace r, Show k, Read k, ToJSON v, FromJSON v)
=> Sem (KVS k v : r) a
-> Sem r a
runKvsAsSQLite = interpret $ \case
GetKvs k -> getAction k
ListAllKvs -> listAction
InsertKvs k v -> insertAction k v
DeleteKvs k -> deleteAction k
where
getAction :: (Member (Input Config) r, Member (Embed IO) r, Member Trace r, Show k, FromJSON v) => k -> Sem r (Maybe v)
getAction key = do
trace $ "getAction: " ++ show key
conn <- connectionFrom input
rows <- embed (SQL.queryNamed conn
"SELECT key, value FROM store WHERE key = :key"
[":key" := show key] :: IO [KeyValueRow])
case rows of
[] -> return Nothing
(KeyValueRow _key value):_ -> return $ (decode . encodeUtf8) value
listAction :: (Member (Input Config) r, Member (Embed IO) r, Member Trace r, Read k, FromJSON v) => Sem r [(k, v)]
listAction = do
trace "listAction:"
conn <- connectionFrom input
rows <- embed (SQL.query_ conn "SELECT key, value FROM store" :: IO [KeyValueRow])
let maybeList = map toKV rows
return $ catNestedMaybe maybeList
where
toKV (KeyValueRow key value) = ((read . T.unpack) key, (decode . encodeUtf8) value)
catNestedMaybe [] = []
catNestedMaybe ((key, Just value):xs) = (key, value):catNestedMaybe xs
catNestedMaybe ((_ , Nothing):xs) = catNestedMaybe xs
insertAction :: (Member (Input Config) r, Member (Embed IO) r, Member Trace r, Show k, ToJSON v) => k -> v -> Sem r ()
insertAction key value = do
trace $ "insertAction: " ++ show key ++ " " ++ show (encode value)
let (query, params) = ("INSERT INTO store (key, value) VALUES (:key, :value) "
<> "ON CONFLICT (key) DO UPDATE SET value = excluded.value",
[":key" := show key, ":value" := encodedValue])
where
encodedValue = (decodeUtf8 . encode) value
conn <- connectionFrom input
embed $ SQL.executeNamed conn query params
deleteAction :: (Member (Input Config) r, Member (Embed IO) r, Member Trace r, Show k) => k -> Sem r ()
deleteAction key = do
trace $ "deleteAction: " ++ show key
conn <- connectionFrom input
embed $ SQL.executeNamed conn "DELETE FROM store WHERE key = :key" [":key" := show key]
-- | create a connection based on configuration data, make sure table "store" exists.
connectionFrom :: (Member (Embed IO) r, Member Trace r) => Sem r Config -> Sem r SQL.Connection
connectionFrom c = do
config <- c
trace $ "open connection to: " ++ dbPath config
embed (getConnection (dbPath config))
where
getConnection :: FilePath -> IO SQL.Connection
getConnection dbFile = do
conn <- SQL.open dbFile
SQL.execute_ conn "CREATE TABLE IF NOT EXISTS store (key TEXT PRIMARY KEY, value TEXT)"
return conn