Skip to content

Commit 7f7a42c

Browse files
authored
Merge pull request #41 from maksbotan/maksbotan/bolt-v3-transact
version 0.1.7.0: better bolt v3 support
2 parents fe9f956 + 6b5bfae commit 7f7a42c

File tree

7 files changed

+106
-46
lines changed

7 files changed

+106
-46
lines changed

hasbolt.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: hasbolt
2-
version: 0.1.6.3
2+
version: 0.1.7.0
33
synopsis: Haskell driver for Neo4j 3+ (BOLT protocol)
44
description:
55
Haskell driver for Neo4j 3+ (BOLT protocol).

src/Database/Bolt/Connection.hs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ module Database.Bolt.Connection
1010
, queryP, query
1111
, queryP', query'
1212
, queryP_, query_
13+
14+
, sendRawRequest
1315
) where
1416

1517
import Database.Bolt.Connection.Pipe
@@ -102,16 +104,22 @@ pullRecords strict keys = do pipe <- ask
102104
else unsafeInterleaveIO pull
103105
pure (record:rest)
104106

105-
-- |Sends request to database and makes an action
107+
-- | Sends any 'Request' and reads the response.
108+
sendRawRequest :: MonadIO m => HasCallStack => Request -> BoltActionT m Response
109+
sendRawRequest req = do
110+
pipe <- ask
111+
liftE $ do
112+
flush pipe req
113+
status <- fetch pipe
114+
if isSuccess status
115+
then pure status
116+
else do processError pipe
117+
throwError $ ResponseError (mkFailure status)
118+
119+
-- | Sends a query with parameters to database and reads the response.
106120
sendRequest :: MonadIO m => HasCallStack => Text -> Map Text Value -> Map Text Value -> BoltActionT m Response
107121
sendRequest cypher params ext =
108122
do pipe <- ask
109-
liftE $ do
110-
if isNewVersion (pipe_version pipe)
111-
then flush pipe $ RequestRunV3 cypher params ext
112-
else flush pipe $ RequestRun cypher params
113-
status <- fetch pipe
114-
if isSuccess status
115-
then pure status
116-
else do processError pipe
117-
throwError $ ResponseError (mkFailure status)
123+
if isNewVersion (pipe_version pipe)
124+
then sendRawRequest $ RequestRunV3 cypher params ext
125+
else sendRawRequest $ RequestRun cypher params

src/Database/Bolt/Connection/Instances.hs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Database.Bolt.Value.Type
1212
import Control.Monad.Except (MonadError (..))
1313
import Data.Map.Strict (Map, insert, fromList, empty, (!))
1414
import Data.Text (Text)
15+
import GHC.Stack (HasCallStack)
1516

1617
instance ToStructure Request where
1718
toStructure RequestInit{..} = Structure sigInit $ if isHello then [M $ helloMap agent token]
@@ -23,15 +24,18 @@ instance ToStructure Request where
2324
toStructure RequestPullAll = Structure sigPAll []
2425
toStructure RequestDiscardAll = Structure sigDAll []
2526
toStructure RequestGoodbye = Structure sigGBye []
27+
toStructure RequestBegin{..} = Structure sigBegin [M extra]
28+
toStructure RequestCommit = Structure sigCommit []
29+
toStructure RequestRollback = Structure sigRollback []
2630

2731
instance FromStructure Response where
2832
fromStructure Structure{..}
29-
| signature == sigSucc = ResponseSuccess <$> extractMap (head fields)
33+
| signature == sigSucc = ResponseSuccess <$> extractMap fields
3034
| signature == sigRecs = pure $ ResponseRecord (removeExtList fields)
31-
| signature == sigIgn = ResponseIgnored <$> extractMap (head fields)
32-
| signature == sigFail = ResponseFailure <$> extractMap (head fields)
33-
| otherwise = throwError $ Not "Response"
34-
where removeExtList :: [Value] -> [Value]
35+
| signature == sigIgn = pure ResponseIgnored
36+
| signature == sigFail = ResponseFailure <$> extractMap fields
37+
| otherwise = throwError $ Not "Response"
38+
where removeExtList :: HasCallStack => [Value] -> [Value]
3539
removeExtList [L x] = x
3640
removeExtList _ = error "Record must contain only a singleton list"
3741

@@ -46,8 +50,8 @@ isFailure (ResponseFailure _) = True
4650
isFailure _ = False
4751

4852
isIgnored :: Response -> Bool
49-
isIgnored (ResponseIgnored _) = True
50-
isIgnored _ = False
53+
isIgnored ResponseIgnored = True
54+
isIgnored _ = False
5155

5256
isRecord :: Response -> Bool
5357
isRecord (ResponseRecord _) = True
@@ -67,7 +71,7 @@ createRun :: Text -> Request
6771
createRun stmt = RequestRun stmt empty
6872

6973

70-
helloMap :: Text -> AuthToken -> Map Text Value
74+
helloMap :: Text -> AuthToken -> Map Text Value
7175
helloMap a = insert "user_agent" (T a) . tokenMap
7276

7377
tokenMap :: AuthToken -> Map Text Value
@@ -76,8 +80,8 @@ tokenMap at = fromList [ "scheme" =: scheme at
7680
, "credentials" =: credentials at
7781
]
7882

79-
extractMap :: MonadError UnpackError m => Value -> m (Map Text Value)
80-
extractMap (M mp) = pure mp
83+
extractMap :: MonadError UnpackError m => [Value] -> m (Map Text Value)
84+
extractMap [M mp] = pure mp
8185
extractMap _ = throwError NotDict
8286

8387
mkFailure :: Response -> ResponseError

src/Database/Bolt/Connection/Type.hs

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -122,24 +122,35 @@ data AuthToken = AuthToken { scheme :: Text
122122

123123
data Response = ResponseSuccess { succMap :: Map Text Value }
124124
| ResponseRecord { recsList :: [Value] }
125-
| ResponseIgnored { ignoreMap :: Map Text Value }
125+
| ResponseIgnored
126126
| ResponseFailure { failMap :: Map Text Value }
127127
deriving (Eq, Show)
128128

129-
data Request = RequestInit { agent :: Text
130-
, token :: AuthToken
131-
, isHello :: Bool
132-
}
133-
| RequestRun { statement :: Text
134-
, parameters :: Map Text Value
135-
}
136-
| RequestRunV3 { statement :: Text
137-
, parameters :: Map Text Value
138-
, extra :: Map Text Value
139-
}
129+
data Request = RequestInit
130+
{ agent :: Text
131+
, token :: AuthToken
132+
, isHello :: Bool
133+
}
134+
| RequestRun
135+
{ statement :: Text
136+
, parameters :: Map Text Value
137+
}
138+
| RequestRunV3
139+
{ statement :: Text
140+
, parameters :: Map Text Value
141+
, extra :: Map Text Value
142+
}
140143
| RequestAckFailure
141144
| RequestReset
142145
| RequestDiscardAll
143146
| RequestPullAll
144147
| RequestGoodbye
148+
-- | Introduced in v3.
149+
| RequestBegin
150+
{ extra :: Map Text Value
151+
}
152+
-- | Introduced in v3.
153+
| RequestCommit
154+
-- | Introduced in v3.
155+
| RequestRollback
145156
deriving (Eq, Show)

src/Database/Bolt/Transaction.hs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,17 @@ module Database.Bolt.Transaction
44
) where
55

66
import Control.Monad ( void )
7+
import Control.Monad.Reader ( ask )
78
import Control.Monad.Trans ( MonadIO(..) )
89
import Control.Monad.Except ( MonadError(..) )
910

1011
import Database.Bolt.Connection ( BoltActionT
11-
, query'
12+
, query', sendRawRequest
1213
)
14+
import Database.Bolt.Connection.Type ( Request(..)
15+
, pipe_version
16+
)
17+
import Database.Bolt.Value.Helpers ( isNewVersion )
1318

1419
-- |Runs a sequence of actions as transaction. All queries would be rolled back
1520
-- in case of any exception inside the block.
@@ -22,10 +27,22 @@ transact actions = do
2227
pure result
2328

2429
txBegin :: MonadIO m => BoltActionT m ()
25-
txBegin = void $ query' "BEGIN"
30+
txBegin = do
31+
pipe <- ask
32+
if isNewVersion $ pipe_version pipe
33+
then void $ sendRawRequest $ RequestBegin mempty
34+
else void $ query' "BEGIN"
2635

2736
txCommit :: MonadIO m => BoltActionT m ()
28-
txCommit = void $ query' "COMMIT"
37+
txCommit = do
38+
pipe <- ask
39+
if isNewVersion $ pipe_version pipe
40+
then void $ sendRawRequest RequestCommit
41+
else void $ query' "COMMIT"
2942

3043
txRollback :: MonadIO m => BoltActionT m ()
31-
txRollback = void $ query' "ROLLBACK"
44+
txRollback = do
45+
pipe <- ask
46+
if isNewVersion $ pipe_version pipe
47+
then void $ sendRawRequest RequestRollback
48+
else void $ query' "ROLLBACK"

src/Database/Bolt/Value/Helpers.hs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -160,27 +160,47 @@ sigPath :: Word8
160160
sigPath = 80
161161

162162
-- == BOLT requests signatures
163+
-- https://neo4j.com/docs/bolt/current/bolt/message/
163164

165+
-- @INIT@ in v1 & v2, @HELLO@ in v3.
164166
sigInit :: Word8
165-
sigInit = 1
167+
sigInit = 0x01
166168

169+
-- @RUN@.
167170
sigRun :: Word8
168-
sigRun = 16
171+
sigRun = 0x10
169172

173+
-- @ACK_FAILURE@, removed in v3.
170174
sigAFail :: Word8
171-
sigAFail = 14
175+
sigAFail = 0x0e
172176

177+
-- @RESET@
173178
sigReset :: Word8
174-
sigReset = 15
179+
sigReset = 0x0f
175180

181+
-- @DISCARD_ALL@ in v1 & v2, @DISCARD@ in v3.
176182
sigDAll :: Word8
177-
sigDAll = 47
183+
sigDAll = 0x2f
178184

185+
-- @PULL_ALL@ in v1 & v2, @PULL@ in v3.
179186
sigPAll :: Word8
180-
sigPAll = 63
187+
sigPAll = 0x3f
181188

182-
sigGBye :: Word8
183-
sigGBye = 2
189+
-- @GOODBYE@, introduced in v3.
190+
sigGBye :: Word8
191+
sigGBye = 0x02
192+
193+
-- @BEGIN@, introduced in v3.
194+
sigBegin :: Word8
195+
sigBegin = 0x11
196+
197+
-- @COMMIT@, introduced in v3.
198+
sigCommit :: Word8
199+
sigCommit = 0x12
200+
201+
-- @ROLLBACK@, introduced in v3.
202+
sigRollback :: Word8
203+
sigRollback = 0x13
184204

185205
-- == BOLT responses signatures
186206

src/Database/Bolt/Value/Type.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ data Structure = Structure { signature :: Word8
6565

6666
-- |Generalizes all datatypes that can be deserialized from 'Structure's.
6767
class FromStructure a where
68-
fromStructure :: MonadError UnpackError m => Structure -> m a
68+
fromStructure :: (HasCallStack, MonadError UnpackError m) => Structure -> m a
6969

7070
-- |Generalizes all datatypes that can be serialized to 'Structure's.
7171
class ToStructure a where

0 commit comments

Comments
 (0)