persistent-relational-record: relational-record on persisten backends.

[ bsd3, database, library ] [ Propose Tags ]

Please see the README on Github at https://github.com/himura/persistent-relational-record#readme


[Skip to Readme]
Versions [faq] 0.1.0.0, 0.1.1.0, 0.1.2.0, 0.3.0
Change log ChangeLog.md
Dependencies array, base (>=4.9 && <5), conduit (>=1.3.0), containers, mtl, names-th, persistable-record (>=0.6), persistent (>=2.5), relational-query (>=0.12.0.0), resourcet (>=1.1), template-haskell (>=2.11), text (>=0.8) [details]
License BSD-3-Clause
Copyright 2015 Takahiro Himura
Author Takahiro HIMURA
Maintainer taka@himura.jp
Category Database
Home page http://github.com/himura/persistent-relational-record
Source repo head: git clone https://github.com/himura/persistent-relational-record
Uploaded by TakahiroHimura at Wed Mar 20 01:32:29 UTC 2019
Distributions NixOS:0.3.0
Downloads 1007 total (84 in the last 30 days)
Rating (no votes yet) [estimated by rule of succession]
Your Rating
  • λ
  • λ
  • λ
Status Hackage Matrix CI
Docs available [build log]
Last success reported on 2019-03-20 [all 1 reports]

Modules

[Index] [Quick Jump]

Flags

NameDescriptionDefaultType
test-examplesDisabledAutomatic

Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info

Downloads

Maintainer's Corner

For package maintainers and hackage trustees


Readme for persistent-relational-record-0.3.0

[back to package description]

persistent-relational-record

Travis Hackage-Deps

About

persistent-relational-record build a bridge between Haskell Relational Record and Persistent. It uses the persistent entities definition instead of obtaining schema from DB at compilation time.

Getting Started

If you already define an entities in persistent's manner, then you are almost ready to use this module. The entities definition in the style of persistent-relational-record are shown below:

Model.hs:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}

module Model where

import Data.Text (Text)
import Database.Persist.Relational
import Database.Persist.TH
import GHC.Generics

share [mkPersist sqlSettings, mkMigrate "migrateAll", mkHrr, deriveGenericForEntityId] [persistLowerCase|
User
    email       Text
    name        Text

    UniqueUserEmail email
    deriving Show Eq Generic

UserGroup
    name        Text
    pageUrl     Text Maybe

    UniqueUserGroupName  name
    deriving Show Eq Generic

Membership
    userId      UserId
    userGroupId UserGroupId

    UniqueImageTag userId userGroupId
    deriving Show Eq Generic
|]

The main difference from the persistent version is that mkHrr are added to the 1st argument of the share function. mkHrr generates various instances and helper functions from the entities definition to cooperate with HRR.

NOTE: deriveGenericForEntityId is also appended to the list in above example. Currently, persistent does not generate Generic instances for EntityID types (UserId, UserGroupId, and MembershipId in above example). Haskell Relational Record rely on the Generic instance of record types, so persistent-relational-record provides deriveGenericForEntityId for convenience to derive Generic instance via StandaloneDeriving

Now, you can build queries in manner of HRR:

{-# LANGUAGE OverloadedLabels #-}

module Query where

import Data.Text (Text)
import Database.Persist (Entity)
import Database.Relational

import Model

-- ^ query users by UserGroup name list
--
-- @
-- SELECT
--   user.*
-- FROM user
-- INNER JOIN (
--   -- built by userIdFromUserGroupNameList
--   SELECT
--     user_id
--   FROM membership
--   INNER JOIN userGroup ON userGroup.id = membership.userGroup_id
--   WHERE userGroup.name IN (<<userGroupNames>>)
--   GROUP BY membership.user_id
--   HAVING COUNT(membership.user_id) = <<length userGroupNames>>
-- ) uid
-- ON user.id = uid.user_id
-- @
selectUserByUserGroupNameList
    -> [Text] -- ^ list of UserGroup name
    -> Relation () (Entity User)
selectUserByUserGroupNameList userGroupNames =
    relation $ do
        user <- query userTable
        userId <- query $ userIdFromUserGroupNameList userGroupNames
        on $ #id user .=. userId
        return user

-- ^ query user_id by UserGroup name list
--
-- @
-- SELECT
--   user_id
-- FROM membership
-- INNER JOIN userGroup ON userGroup.id = membership.userGroup_id
-- WHERE userGroup.name IN (<<userGroupNames>>)
-- GROUP BY membership.user_id
-- HAVING COUNT(membership.user_id) = <<length userGroupNames>>
-- @
userIdFromUserGroupNameList
    :: [Text] -- ^ list of userGroup name
    -> Relation () UserId
userIdFromUserGroupNameList userGroupNames =
    aggregateRelation $ do
        membership <- query membershipTable
        userGroup <- query userGroupTable
        on $ #id userGroup .=. #userGroupId membership
        wheres $ #name userGroup `in'` values userGroupNames
        g <- groupBy $ #userId membership
        let c = count $ (#userGroupId membership)
        having $ c .=. value (fromIntegral . length $ userGroupNames)
        return g

Finally, we can execute a query by runQuery:

{-# LANGUAGE OverloadedStrings #-}

import Control.Monad.IO.Class
import Control.Monad.Logger
import Control.Monad.Trans.Resource
import Data.Conduit
import qualified Data.Conduit.List as CL
import Database.Persist.MySQL
import Database.Persist.Relational
import Database.Relational

import Model
import Query

sample :: SqlPersistT (LoggingT IO) [Entity User]
sample = runResourceT . runConduit $ runQuery (relationalQuery $ selectUserByUserGroupNameList True ["tokyo", "haskell"]) () .| CL.consume

main :: IO ()
main = runStderrLoggingT $ withMySQLPool defaultConnectInfo 10 $ runSqlPool $ do
    mapM_ (liftIO . print) =<< sample

runQuery run the HRR Query and gives the result as conduit Source.

For a full runnable example, see examples directory.