Skip to content

Commit

Permalink
version 0.0.0.8: dsl (#16)
Browse files Browse the repository at this point in the history
* initial version of refactoring

* version 0.0.0.8: dsl
  • Loading branch information
Sofya authored and ozzzzz committed May 14, 2018
1 parent ce2a164 commit d0a65e5
Show file tree
Hide file tree
Showing 7 changed files with 333 additions and 4 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

## [0.0.0.8] - 2018-05-14
### Added
- Added `DSL` for `Cypher`.

## [0.0.0.7] - 2018-04-23
### Added
- Added ability to delete nodes by their `BoltId`s.
Expand Down
16 changes: 12 additions & 4 deletions hasbolt-extras.cabal
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: hasbolt-extras
version: 0.0.0.7
version: 0.0.0.8
synopsis: Extras for hasbolt library
description: Extras for hasbolt library
homepage: https://github.com/biocad/hasbolt-extras#readme
Expand Down Expand Up @@ -30,6 +30,7 @@ library
, Database.Bolt.Extras.Query
, Database.Bolt.Extras.Template
, Database.Bolt.Extras.Utils
, Database.Bolt.Extras.DSL
other-modules: Database.Bolt.Extras.Query.Get
, Database.Bolt.Extras.Query.Put
, Database.Bolt.Extras.Query.Set
Expand All @@ -40,14 +41,21 @@ library
, Database.Bolt.Extras.Template.Types
, Database.Bolt.Extras.Template.Instances
, Database.Bolt.Extras.Template.Converters

, Database.Bolt.Extras.DSL.Internal.Types
, Database.Bolt.Extras.DSL.Internal.Language
, Database.Bolt.Extras.DSL.Internal.Executer
, Database.Bolt.Extras.DSL.Internal.Instances

build-depends: base >=4.7 && <5
, text
, hasbolt
, containers
, free
, hasbolt
, lens
, mtl
, neat-interpolation
, template-haskell
, text
, th-lift-instances
, lens
ghc-options: -Wall -O2
default-language: Haskell2010
11 changes: 11 additions & 0 deletions src/Database/Bolt/Extras/DSL.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Database.Bolt.Extras.DSL
(
module Database.Bolt.Extras.DSL.Internal.Types
, module Database.Bolt.Extras.DSL.Internal.Language
, module Database.Bolt.Extras.DSL.Internal.Executer
) where

import Database.Bolt.Extras.DSL.Internal.Types
import Database.Bolt.Extras.DSL.Internal.Language
import Database.Bolt.Extras.DSL.Internal.Executer
import Database.Bolt.Extras.DSL.Internal.Instances ()
48 changes: 48 additions & 0 deletions src/Database/Bolt/Extras/DSL/Internal/Executer.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{-# LANGUAGE OverloadedStrings #-}

module Database.Bolt.Extras.DSL.Internal.Executer
(
formQuery
) where

import Control.Monad.Free (Free (..),
foldFree)
import Control.Monad.Writer (Writer,
execWriter, tell)
import Data.Monoid ((<>))
import Data.Text as T (Text,
intercalate,
unwords)
import Database.Bolt.Extras.DSL.Internal.Instances ()
import Database.Bolt.Extras.DSL.Internal.Types (Expr (..))
import Database.Bolt.Extras.Query.Cypher (ToCypher (..))

-- | Translates 'Expr' to cypher query.
--
execute :: Expr a -> Writer [Text] a
execute (Create s n) = executeHelperS "CREATE " s n
execute (Match s n) = executeHelperS "MATCH " s n
execute (OptionalMatch s n) = executeHelperS "OPTIONAL MATCH " s n
execute (Merge s n) = executeHelperS "MERGE " s n
execute (Where c n) = executeHelperT "WHERE " c n
execute (Set t n) = executeHelperT "SET " t n
execute (Delete t n) = executeHelperT "DELETE " t n
execute (DetachDelete t n) = executeHelperT "DETACH DELETE " t n
execute (Return t n) = executeHelperT "RETURN " t n
execute (Text t n) = tell [t] >> pure n

-- | Helper to translate 'Expr' with 'Selector's
--
executeHelperS :: ToCypher a => Text -> a -> b -> Writer [Text] b
executeHelperS txt s n = tell [txt <> toCypher s] >> pure n

-- | Helper to translate 'Expr' with 'Text's
--
executeHelperT :: Text -> [Text] -> b -> Writer [Text] b
executeHelperT txt t n = tell [txt <> intercalate ", " t] >> pure n

formQueryW :: Free Expr () -> Writer [Text] ()
formQueryW = foldFree execute

formQuery :: Free Expr () -> Text
formQuery = T.unwords . execWriter . formQueryW
84 changes: 84 additions & 0 deletions src/Database/Bolt/Extras/DSL/Internal/Instances.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Database.Bolt.Extras.DSL.Internal.Instances () where

import Control.Monad.Writer (execWriter, tell)
import Data.Text (intercalate)
import Database.Bolt.Extras.DSL.Internal.Types
import Database.Bolt.Extras.Query.Cypher (ToCypher (..))

instance SelectorLike NodeSelector where
withIdentifier idx node = node { nodeIdentifier = Just idx }
withLabel lbl node = node { nodeLabels = lbl : nodeLabels node }
withProp prop node = node { nodeProperties = prop : nodeProperties node }

instance SelectorLike RelSelector where
withIdentifier idx rel = rel { relIdentifier = Just idx }
withLabel lbl rel = rel { relLabel = lbl }
withProp prop rel = rel { relProperties = prop : relProperties rel }

instance ToCypher NodeSelector where
toCypher NodeSelector{..} = execWriter $ do
tell "("
case nodeIdentifier of
Just idx -> tell idx
Nothing -> pure ()
case nodeLabels of
[] -> pure ()
_ -> tell $ toCypher nodeLabels
case nodeProperties of
[] -> pure ()
_ -> do tell "{"
tell $ toCypher nodeProperties
tell "}"
tell ")"

instance ToCypher RelSelector where
toCypher RelSelector{..} = execWriter $ do
tell "["
case relIdentifier of
Just idx -> tell idx
Nothing -> pure ()
case relLabel of
"" -> pure ()
_ -> tell $ toCypher relLabel
case relProperties of
[] -> pure ()
_ -> do tell "{"
tell $ toCypher relProperties
tell "}"
tell "]"

instance ToCypher PathSelector where
toCypher (ps :-!: rs :!->: ns) = execWriter $ do
tell $ toCypher ps
tell "-"
tell $ toCypher rs
tell "->"
tell $ toCypher ns
toCypher (ps :<-!: rs :!-: ns) = execWriter $ do
tell $ toCypher ps
tell "<-"
tell $ toCypher rs
tell "-"
tell $ toCypher ns
toCypher (ps :-!: rs :!-: ns) = execWriter $ do
tell $ toCypher ps
tell "-"
tell $ toCypher rs
tell "-"
tell $ toCypher ns
toCypher (P ns) = execWriter $
tell $ toCypher ns
toCypher (_ :<-!: _ :!->: _) = error "Database.Bolt.Extras.DSL: incorrect path"

instance ToCypher Selector where
toCypher (PS ps) = toCypher ps
toCypher (TS txt) = txt

instance ToCypher Selectors where
toCypher = intercalate ", " . fmap toCypher
68 changes: 68 additions & 0 deletions src/Database/Bolt/Extras/DSL/Internal/Language.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
module Database.Bolt.Extras.DSL.Internal.Language
(
createF
, matchF
, optionalMatchF
, mergeF
, whereF
, setF
, deleteF
, detachDeleteF
, returnF
, textF
) where

import Control.Monad.Free (Free (..), liftF)
import Data.Text (Text)
import Database.Bolt.Extras.DSL.Internal.Types (Cond, Expr (..),
Selectors)

-- | Prepare 'CREATE' query
--
createF :: Selectors -> Free Expr ()
createF sels = liftF (Create sels ())

-- | Prepare 'MATCH' query
--
matchF :: Selectors -> Free Expr ()
matchF sels = liftF (Match sels ())

-- | Prepare 'OPTIONAL MATCH' query
--
optionalMatchF :: Selectors -> Free Expr ()
optionalMatchF sels = liftF (OptionalMatch sels ())

-- | Prepare 'MERGE' query
--
mergeF :: Selectors -> Free Expr ()
mergeF sels = liftF (Merge sels ())

-- | Prepare 'WHERE' query
--
whereF :: Cond -> Free Expr ()
whereF cond = liftF (Where cond ())

-- | Prepare 'SET' query
--
setF :: [Text] -> Free Expr ()
setF txts = liftF (Set txts ())

-- | Prepare 'DELETE' query
--
deleteF :: [Text] -> Free Expr ()
deleteF txts = liftF (Delete txts ())

-- | Prepare 'DETACH DELETE' query
--
detachDeleteF :: [Text] -> Free Expr ()
detachDeleteF txts = liftF (DetachDelete txts ())

-- | Prepare 'RETURN' query
--
returnF :: [Text] -> Free Expr ()
returnF txts = liftF (Return txts ())

-- | Prepare query with custom text
--
textF :: Text -> Free Expr ()
textF txt = liftF (Text txt ())
106 changes: 106 additions & 0 deletions src/Database/Bolt/Extras/DSL/Internal/Types.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeSynonymInstances #-}

module Database.Bolt.Extras.DSL.Internal.Types
(
NodeSelector (..)
, RelSelector (..)
, PathPart (..)
, PathSelector (..)
, Selector (..)
, Selectors
, Cond
, Expr (..)
, SelectorLike (..)
, (#)
, defaultNode
, defaultRel
, toNodeSelector
, toRelSelector
) where

import Data.Map.Strict (toList)
import Data.Text (Text)
import Database.Bolt (Node (..), URelationship (..), Value (..))

-- | Class for Selectors, which can update identifier, labels and props.
--
class SelectorLike a where
withIdentifier :: Text -> a -> a
withLabel :: Text -> a -> a
withProp :: (Text, Value) -> a -> a

-- | Selector for 'Node's.
--
data NodeSelector = NodeSelector { nodeIdentifier :: Maybe Text
, nodeLabels :: [Text]
, nodeProperties :: [(Text, Value)]
}
deriving (Show, Eq)

-- | Selector for 'URelationship's.
--
data RelSelector = RelSelector { relIdentifier :: Maybe Text
, relLabel :: Text
, relProperties :: [(Text, Value)]
}
deriving (Show, Eq)


(#) :: a -> (a -> b) -> b
(#) = flip ($)

-- | Selector for paths.
--
infixl 2 :!->:
infixl 2 :!-:
data PathPart = RelSelector :!->: NodeSelector
| RelSelector :!-: NodeSelector
deriving (Show, Eq)

infixl 1 :-!:
infixl 1 :<-!:
data PathSelector = PathSelector :-!: PathPart
| PathSelector :<-!: PathPart
| P NodeSelector
deriving (Show, Eq)

data Selector = PS PathSelector | TS Text
deriving (Show, Eq)

type Selectors = [Selector]

type Cond = [Text]

-- | Expression in Cypher language.
--
data Expr next = Create Selectors next
| Match Selectors next
| OptionalMatch Selectors next
| Merge Selectors next
| Where Cond next
| Set [Text] next
| Delete [Text] next
| DetachDelete [Text] next
| Return [Text] next
| Text Text next
deriving (Show, Eq, Functor)

defaultNode :: NodeSelector
defaultNode = NodeSelector Nothing [] []

defaultRel :: RelSelector
defaultRel = RelSelector Nothing "" []

toNodeSelector :: Node -> NodeSelector
toNodeSelector Node{..} = defaultNode { nodeLabels = labels
, nodeProperties = filter ((/= N ()) . snd) (toList nodeProps)
}

toRelSelector :: URelationship -> RelSelector
toRelSelector URelationship{..} = defaultRel { relLabel = urelType
, relProperties = toList urelProps
}

0 comments on commit d0a65e5

Please sign in to comment.