hasbolt-extras-0.0.0.6: src/Database/Bolt/Extras/Query/Get.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RecordWildCards #-}
module Database.Bolt.Extras.Query.Get
( NodeGetter (..)
, GraphGetRequest
, GraphGetResponse
, RelGetter (..)
, getGraph
, nodeAsText
, condIdAsText
) where
import Control.Monad.IO.Class (MonadIO)
import Data.Map.Strict (Map, keys, mapWithKey,
toList, (!))
import qualified Data.Text as T (Text, concat, empty,
intercalate, pack)
import Database.Bolt (BoltActionT, Node (..),
Record, RecordValue (..),
Relationship (..),
URelationship (..), exact,
query)
import Database.Bolt.Extras.Graph (Graph (..))
import Database.Bolt.Extras.Persisted (BoltId)
import Database.Bolt.Extras.Query.Cypher (ToCypher (..))
import Database.Bolt.Extras.Query.Utils (NodeName)
import Database.Bolt.Extras.Template.Types (Label, Property)
import NeatInterpolation (text)
import Text.Printf (printf)
-- | Helper to find 'Node's.
--
data NodeGetter = NodeGetter { boltIdN :: Maybe BoltId
, labelsN :: Maybe [Label]
, propsN :: Maybe [Property]
} deriving (Show)
-- | Helper to find 'URelationship's.
--
data RelGetter = RelGetter { labelR :: Maybe Label
, propsR :: Maybe [Property]
} deriving (Show)
-- | The combinations of 'Getter's to load graph from the database.
--
type GraphGetRequest = Graph NodeName NodeGetter RelGetter
-- | The graph of 'Node's and 'URelationship's which we got from the database using 'GraphGetRequest'.
--
type GraphGetResponse = Graph NodeName Node URelationship
-- | For the given 'GraphGetRequest' find all graphs, which match it.
-- This function creates single cypher query and performs it.
--
getGraph :: (MonadIO m) => [T.Text] -> GraphGetRequest -> BoltActionT m [GraphGetResponse]
getGraph customConds requestGraph = do
response <- query (formQuery customConds nodeVars edgesVars vertices rels)
mapM (\i -> do
nodes <- sequence $ mapOnlyKey (fmap (!! i) . flip exactValues response) vertices
edges <- sequence $ mapOnlyKey (fmap (makeU . (!! i)) . flip exactValues response . namesToText) rels
return (Graph nodes edges)) [0.. length response - 1]
where
vertices :: Map NodeName NodeGetter
vertices = _vertices requestGraph
rels :: Map (NodeName, NodeName) RelGetter
rels = _relations requestGraph
nodeVars :: [T.Text]
nodeVars = keys vertices
edgesVars :: [T.Text]
edgesVars = map (\k -> T.concat [fst k, "0", snd k]) (keys rels)
exactValues :: (MonadIO m, RecordValue a) => T.Text -> [Record] -> BoltActionT m [a]
exactValues var = mapM (exact . (! var))
makeU :: Relationship -> URelationship
makeU Relationship{..} = URelationship relIdentity relType relProps
namesToText :: (NodeName, NodeName) -> T.Text
namesToText (nameA, nameB) = T.concat [nameA, "0", nameB]
mapOnlyKey :: (k -> b) -> Map k a -> Map k b
mapOnlyKey f = mapWithKey (\k _ -> f k)
-- | This function creates cypher query, which is used for getting graph from the database.
--
formQuery :: [T.Text] -> [T.Text] -> [T.Text] -> Map NodeName NodeGetter -> Map (NodeName, NodeName) RelGetter -> T.Text
formQuery customConds returnNodes returnEdges vertices rels =
[text|MATCH $completeRequest
$conditionsQ
RETURN $completeResponse|]
where
nodes = nodeAsText <$> toList vertices
conditionsId = intercalateAnd . filter (/= "\n") $ fmap condIdAsText (toList vertices)
customConditions = intercalateAnd customConds
conditions = intercalateAnd . filter (/= T.empty) $ [conditionsId, customConditions]
conditionsQ = if conditions == T.empty then "" else T.concat ["WHERE ", conditions]
edges = fmap (relationshipAsText vertices) (toList rels)
completeRequest = T.intercalate "," $ nodes ++ edges
completeResponse = T.intercalate "," $ returnNodes ++ returnEdges
intercalateAnd :: [T.Text] -> T.Text
intercalateAnd = T.intercalate " AND "
condIdAsText :: (NodeName, NodeGetter) -> T.Text
condIdAsText (name, sel) = [text|$boltIdNR|]
where
boltIdNR = maybeNull (T.pack . printf "ID(%s)=%d" name) (boltIdN sel)
nodeAsText :: (NodeName, NodeGetter) -> T.Text
nodeAsText (name, sel) = [text|($name $labels $propsQ)|]
where
labels = maybeNull toCypher (labelsN sel)
propsQ = maybeNull (\props -> T.concat ["{", toCypher props, "}"]) (propsN sel)
relationshipAsText :: Map NodeName NodeGetter -> ((NodeName, NodeName), RelGetter) -> T.Text
relationshipAsText vertices ((begNodeName, endNodeName), uRel) =
[text|($begNodeName $begNodeLabels $begNodeProps)-[$name $typeQ $propsQ]-($endNodeName $endNodeLabels $endNodeProps)|]
where
name = T.concat [begNodeName, "0", endNodeName]
typeQ = maybeNull toCypher (labelR uRel)
begNode = vertices ! begNodeName
begNodeLabels = maybeNull toCypher $ labelsN begNode
begNodeProps = maybeNull (\props -> T.concat ["{", toCypher props, "}"]) (propsN begNode)
endNode = vertices ! endNodeName
endNodeLabels = maybeNull toCypher $ labelsN endNode
endNodeProps = maybeNull (\props -> T.concat ["{", toCypher props, "}"]) (propsN endNode)
propsQ = maybeNull (\props -> T.concat ["{", toCypher props, "}"]) (propsR uRel)
maybeNull :: (a -> T.Text) -> Maybe a -> T.Text
maybeNull = maybe ""