Databases in Haskell - HDBC

Table of Contents

  • Index
  • Repository
  • 1 Databases - HDBC

    1.1 Overview

    HDBC is a library that provides a common abstraction or interface to different database engines like sqlite, mysql and postgres. The communication to each database engine is handled by the database driver.

    Documentation:

    Haskell Database Connectivity library Database.HDBC
    Sqlite Driver HDBC-sqlite3: Sqlite v3 driver for HDBC
    Postgresql Driver Database.HDBC.PostgreSQL
    MySQL Driver HDBC-mysql: MySQL driver for HDBC
       

    Getting a database to play

    # Download sample database dump 
    $ curl -L -o zotero.sql https://github.com/caiorss/zhserver/raw/master/database/zotero-test.sql
    
    $ du -h zotero.sql 
    3,0M    zotero.sql
    3,0M    total
    
    $ file zotero.sql 
    zotero.sql: UTF-8 Unicode text, with very long lines, with LF, NEL line terminators
    
    # Make the database 
    $ cat zotero.sql | sqlite3 zotero.sqlite
    
    # Check the file type 
    $ file zotero.sqlite 
    zotero.sqlite: SQLite 3.x database, last written using SQLite version 3016002
    
    sqlite> 
    sqlite> .tables
    annotations                itemNotes                
    baseFieldMappings          itemSeeAlso              
    baseFieldMappingsCombined  itemTags                 
    charsets                   itemTypeCreatorTypes     
    collectionItems            itemTypeFields           
    collections                itemTypeFieldsCombined   
    creatorData                itemTypes                
    creatorTypes               itemTypesCombined        
    creators                   items                    
    customBaseFieldMappings    libraries                
    customFields               proxies                  
    customItemTypeFields       proxyHosts               
    customItemTypes            relations            
       ... ... ... ... ...
    
    
    sqlite> .headers on
    sqlite> .mode column
    
    
    sqlite> SELECT * FROM tags LIMIT 10 ;
    tagID       name        type        dateAdded            dateModified         clientDateModified   libraryID   key       
    ----------  ----------  ----------  -------------------  -------------------  -------------------  ----------  ----------
    2           fp          0           2016-11-07 22:03:05  2017-02-08 20:10:06  2017-02-08 20:10:06              TCG4NFU2  
    3           fsharp      0           2016-11-07 22:03:09  2016-11-07 22:03:23  2016-11-07 22:03:23              ZWMIIEA9  
    4           doc         0           2016-11-07 22:03:11  2016-11-07 22:03:29  2016-11-07 22:03:29              B4SGSEZR  
    5           overview    0           2016-11-07 22:03:25  2016-11-07 22:03:25  2016-11-07 22:03:25              DP9WBHZJ  
    6           haskell     0           2016-11-07 22:03:44  2017-02-08 20:10:31  2017-02-08 20:10:31              FSZTVA3J  
    7           tutorial    0           2016-11-07 22:03:47  2016-11-07 22:03:47  2016-11-07 22:03:47              Z2KJVEKI  
    8           quickref    0           2016-11-07 22:06:24  2016-11-07 22:07:34  2016-11-07 22:07:34              P67M4AB5  
    9           linux       0           2016-11-07 22:06:26  2016-11-07 22:07:30  2016-11-07 22:07:30              JQR7943E  
    10          c++         0           2016-11-07 22:15:22  2017-02-08 20:09:44  2017-02-08 20:09:44              VG2ZRGWM  
    11          numerical   0           2016-11-07 22:20:56  2016-11-07 22:24:03  2016-11-07 22:24:03              P859CH9I  
    sqlite> 
    
    
    sqlite> SELECT * FROM collections LIMIT 10 ;
    collectionID  collectionName  parentCollectionID  dateAdded            dateModified         clientDateModified   libraryID   key       
    ------------  --------------  ------------------  -------------------  -------------------  -------------------  ----------  ----------
    2             Haskell                             2016-11-07 21:50:38  2016-11-07 23:13:55  2016-11-07 23:13:55              PMHEFIBA  
    3             Haskell doc     2                   2016-11-07 21:50:57  2016-11-07 21:51:18  2016-11-07 21:51:18              CCCGVG4V  
    5             Haskell Tutori  2                   2016-11-07 21:53:30  2016-11-07 21:54:32  2016-11-07 21:54:32              FRE2DHFB  
    6             Fsharp                              2016-11-07 21:59:56  2016-11-07 22:01:43  2016-11-07 22:01:43              BICWZD7P  
    7             Linux                               2016-11-07 22:05:05  2016-11-07 22:07:05  2016-11-07 22:07:05              TIWU5EPF  
    8             C++/CPP                             2016-11-07 22:11:57  2016-11-07 22:42:28  2016-11-07 22:42:28              42H6QC5D  
    sqlite>
    

    1.2 Install the Database Drivers

    # Go outside a project 
    $ cd ~
    
    # Install database drivers 
    $ stack install HDBC
    $ stack install HDBC-sqlite3
    $ stack install HDBC-postgresql
    
    # Run Haskell REPL
    $ stack ghci
    

    1.3 Examples

    1.3.1 Example - Simple Database Query

    Connect to sqlite3 database

    import Database.HDBC
    import Database.HDBC.Sqlite3 
    
    > conn <- connectSqlite3 "zotero.sqlite" 
    conn :: Connection
    >
    

    Database Metadata

    -- Driver name
    --
    > hdbcDriverName conn
    "sqlite3"
    it :: String
    > :t hdbcDriverName conn
    hdbcDriverName conn :: String
    > 
    
    
    -- Version of database server
    --
    > dbServerVer conn
    "3.14.1"
    it :: String
    > 
    
    > :t dbServerVer conn
    dbServerVer conn :: String
    >
    

    Tables metadata

    > :t getTables 
    getTables :: IConnection conn => conn -> IO [String]
    > 
    
    > getTables conn 
    ["annotations","baseFieldMappings","baseFieldMappingsCombined","charsets", ...]
    
    
    -- Print all tables available 
    --
    > getTables conn >>= mapM_ putStrLn 
    annotations
    baseFieldMappings
    baseFieldMappingsCombined
    charsets
    collectionItems
    collections
    creatorData
    creatorTypes
    creators
    customBaseFieldMappings
    customFields
    customItemTypeFields
    customItemTypes
    deletedItems
    fieldFormats
    fields
    fieldsCombined
    fileTypeMimeType
    ...
    tags
    transactionLog
    transactionSets
    transactions
    translatorCache
    users
    version
    zoteroDummyTable
    
    
    -- Show specific table metadata  
    --
    --
    > describeTable conn "tags"
    [("tagID",SqlColDesc {colType = SqlIntegerT, colSize = Nothing,
    colOctetLength = Nothing, colDecDigits = Nothing, colNullable = Nothing})...]
    
    > describeTable conn "tags" >>= mapM_ print
    
    ("tagID",SqlColDesc {colType = SqlIntegerT, colSize = Nothing, colOctetLength = Nothing, colDecDigits = Nothing, colNullable = Nothing})
    ("name",SqlColDesc {colType = SqlVarCharT, colSize = Nothing, colOctetLength = Nothing, colDecDigits = Nothing, colNullable = Nothing})
    ("type",SqlColDesc {colType = SqlIntegerT, colSize = Nothing, colOctetLength = Nothing, colDecDigits = Nothing, colNullable = Nothing})
    ("dateAdded",SqlColDesc {colType = SqlUnknownT "timestamp", colSize = Nothing, colOctetLength = Nothing, colDecDigits = Nothing, colNullable = Nothing})
    ("dateModified",SqlColDesc {colType = SqlUnknownT "timestamp", colSize = Nothing, colOctetLength = Nothing, colDecDigits = Nothing, colNullable = Nothing})
    ("clientDateModified",SqlColDesc {colType = SqlUnknownT "timestamp", colSize = Nothing, colOctetLength = Nothing, colDecDigits = Nothing, colNullable = Nothing})
    ("libraryID",SqlColDesc {colType = SqlIntegerT, colSize = Nothing, colOctetLength = Nothing, colDecDigits = Nothing, colNullable = Nothing})
    ("key",SqlColDesc {colType = SqlVarCharT, colSize = Nothing, colOctetLength = Nothing, colDecDigits = Nothing, colNullable = Nothing})
    it :: ()
    >
    

    Quick query

    > :t quickQuery
    quickQuery
      :: IConnection conn =>
         conn -> String -> [SqlValue] -> IO [[SqlValue]]
    >
    
    
    -- Output: formatted manually in order to fit in the screen. 
    --
    --
    > quickQuery conn "SELECT tagID, name FROM tags WHERE tagID > 80 LIMIT 15" []
    
    [[SqlInt64 81,SqlByteString "excel"],
     [SqlInt64 82,SqlByteString "spreadsheet"],
     [SqlInt64 83,SqlByteString "vba"],
     [SqlInt64 84,SqlByteString "code"],
     [SqlInt64 85,SqlByteString "thermodynamics"],
     [SqlInt64 86,SqlByteString "mechanical"],
     [SqlInt64 87,SqlByteString "energy"],
     [SqlInt64 88,SqlByteString "catalog"],
     [SqlInt64 89,SqlByteString "parts"],
     [SqlInt64 90,SqlByteString "MVC"],
     [SqlInt64 91,SqlByteString "hydraulics"],
     [SqlInt64 92,SqlByteString "manufacturer"],
     [SqlInt64 93,SqlByteString "design"],
     [SqlInt64 94,SqlByteString "manual"],
     [SqlInt64 95,SqlByteString "cable"]]
    it :: [[SqlValue]]
    
    
    > quickQuery conn "SELECT tagID, name FROM tags WHERE tagID > 80 LIMIT 15" [] >>= mapM_ print
    [SqlInt64 81,SqlByteString "excel"]
    [SqlInt64 82,SqlByteString "spreadsheet"]
    [SqlInt64 83,SqlByteString "vba"]
    [SqlInt64 84,SqlByteString "code"]
    [SqlInt64 85,SqlByteString "thermodynamics"]
    [SqlInt64 86,SqlByteString "mechanical"]
    [SqlInt64 87,SqlByteString "energy"]
    [SqlInt64 88,SqlByteString "catalog"]
    [SqlInt64 89,SqlByteString "parts"]
    [SqlInt64 90,SqlByteString "MVC"]
    [SqlInt64 91,SqlByteString "hydraulics"]
    [SqlInt64 92,SqlByteString "manufacturer"]
    [SqlInt64 93,SqlByteString "design"]
    [SqlInt64 94,SqlByteString "manual"]
    [SqlInt64 95,SqlByteString "cable"]
    it :: ()
    >
    

    Query

    let query = unlines $ [
          "SELECT fieldName, value FROM itemDataValues, itemData, fields WHERE", 
          "itemDataValues.valueID = itemData.valueID", 
          "and itemData.itemID = 5805",
          "and fields.fieldID = itemData.fieldID"
          ]
    
    > putStrLn query 
    SELECT fieldName, value FROM itemDataValues, itemData, fields WHERE
    itemDataValues.valueID = itemData.valueID
    and itemData.itemID = 5805
    and fields.fieldID = itemData.fieldID
    
    it :: ()
    > 
    
    > :t prepare 
    prepare :: IConnection conn => conn -> String -> IO Statement
    > 
    
    > stmt <- prepare conn query 
    stmt :: Statement
    > 
    
    > 
    > :t stmt 
    stmt :: Statement
    > 
    
    
    > :t execute
    execute :: Statement -> [SqlValue] -> IO Integer
    > 
    > nrows <- execute stmt []
    nrows :: Integer
    > nrows
    0
    it :: Integer
    > 
    
    > getColumnNames stmt
    ["fieldName","value"]
    it :: [String]
    > 
    
    -- ========= fetchAllRows =============== -
    
    
    > rows <- fetchAllRows stmt -- Output changed to fit on the screen.
    rows :: [[SqlValue]]
    > rows
    [[SqlByteString "url",SqlByteString "http://www.eliza.ch/doc/wadler92essence_of_FP.pdf"]
      ,[SqlByteString "place",SqlByteString "Albuquerque, NM, USA"],
       [SqlByteString "publisher",SqlByteString "ACM"],
       [SqlByteString "pages",SqlByteString "1-14"],
       [SqlByteString "date",SqlByteString "1992-00-00 1992"],
       [SqlByteString "DOI",SqlByteString "10.1145/143165.143169"],
       [SqlByteString "accessDate",SqlByteString "2016-04-04 20:05:57"],
       [SqlByteString "language",SqlByteString "en"],
       [SqlByteString "title",SqlByteString "The essence of functional programming"],
       [SqlByteString "proceedingsTitle",SqlByteString "POPL '92 Proceedings of the 19th ACM SIGPLAN-SIGACT symposium on Principles of programming languages"]]
    it :: [[SqlValue]]
    
    > 
    > mapM_ print rows
    [SqlByteString "url",SqlByteString "http://www.eliza.ch/doc/wadler92essence_of_FP.pdf"]
    [SqlByteString "place",SqlByteString "Albuquerque, NM, USA"]
    [SqlByteString "publisher",SqlByteString "ACM"]
    [SqlByteString "pages",SqlByteString "1-14"]
    [SqlByteString "date",SqlByteString "1992-00-00 1992"]
    [SqlByteString "DOI",SqlByteString "10.1145/143165.143169"]
    [SqlByteString "accessDate",SqlByteString "2016-04-04 20:05:57"]
    [SqlByteString "language",SqlByteString "en"]
    [SqlByteString "title",SqlByteString "The essence of functional programming"]
    [SqlByteString "proceedingsTitle",SqlByteString "POPL '92 Proceedings of the 19th ACM SIGPLAN-SIGACT symposium on Principles of programming languages"]
    it :: ()
    > 
    
    > let sqlRowToString = map (fromSql :: SqlValue -> String)
    - 
    sqlRowToString :: [SqlValue] -> [String]
    > 
    > mapM_ print $ map sqlRowToString rows 
    ["url","http://www.eliza.ch/doc/wadler92essence_of_FP.pdf"]
    ["place","Albuquerque, NM, USA"]
    ["publisher","ACM"]
    ["pages","1-14"]
    ["date","1992-00-00 1992"]
    ["DOI","10.1145/143165.143169"]
    ["accessDate","2016-04-04 20:05:57"]
    ["language","en"]
    ["title","The essence of functional programming"]
    ["proceedingsTitle","POPL '92 Proceedings of the 19th ACM SIGPLAN-SIGACT symposium on Principles of programming languages"]
    it :: ()
    > 
    
    > let sqlRowToLine row = unwords $ map (fromSql :: SqlValue -> String) row
    - 
    sqlRowToLine :: [SqlValue] -> String
    >
    
    > mapM_ putStrLn $ map sqlRowToLine rows 
    url http://www.eliza.ch/doc/wadler92essence_of_FP.pdf
    place Albuquerque, NM, USA
    publisher ACM
    pages 1-14
    date 1992-00-00 1992
    DOI 10.1145/143165.143169
    accessDate 2016-04-04 20:05:57
    language en
    title The essence of functional programming
    proceedingsTitle POPL '92 Proceedings of the 19th ACM SIGPLAN-SIGACT symposium on Principles of programming languages
    it :: ()
    > 
    > 
    
    
    > fetchAllRows stmt 
    []
    it :: [[SqlValue]]
    > 
    
    
    -- ================  fetchAllRowsAL  ======================= ---
    
    > stmt <- prepare conn query 
    stmt :: Statement
    
    > execute stmt []
    0
    it :: Integer
    > 
    
    > 
    > rows <- fetchAllRowsAL stmt
    rows :: [[(String, SqlValue)]]
    
    > mapM_ print rows
    [("fieldName",SqlByteString "url"),("value",SqlByteString "http://www.eliza.ch/doc/wadler92essence_of_FP.pdf")]
    [("fieldName",SqlByteString "place"),("value",SqlByteString "Albuquerque, NM, USA")]
    [("fieldName",SqlByteString "publisher"),("value",SqlByteString "ACM")]
    [("fieldName",SqlByteString "pages"),("value",SqlByteString "1-14")]
    [("fieldName",SqlByteString "date"),("value",SqlByteString "1992-00-00 1992")]
    [("fieldName",SqlByteString "DOI"),("value",SqlByteString "10.1145/143165.143169")]
    [("fieldName",SqlByteString "accessDate"),("value",SqlByteString "2016-04-04 20:05:57")]
    [("fieldName",SqlByteString "language"),("value",SqlByteString "en")]
    [("fieldName",SqlByteString "title"),("value",SqlByteString "The essence of functional programming")]
    [("fieldName",SqlByteString "proceedingsTitle"),("value",SqlByteString "POPL '92 Proceedings of the 19th ACM SIGPLAN-SIGACT symposium on Principles of programming languages")]
    it :: ()
    > 
    
    -- =============== fetchAllRowsMap ================== --
    
    > stmt <- prepare conn query 
    stmt :: Statement
    
    > execute stmt []
    0
    
    > rows <- fetchAllRowsMap stmt
    rows :: [containers-0.5.7.1:Data.Map.Base.Map String SqlValue]
    > 
    > mapM_ print rows
    fromList [("fieldName",SqlByteString "url"),("value",SqlByteString "http://www.eliza.ch/doc/wadler92essence_of_FP.pdf")]
    fromList [("fieldName",SqlByteString "place"),("value",SqlByteString "Albuquerque, NM, USA")]
    fromList [("fieldName",SqlByteString "publisher"),("value",SqlByteString "ACM")]
    fromList [("fieldName",SqlByteString "pages"),("value",SqlByteString "1-14")]
    fromList [("fieldName",SqlByteString "date"),("value",SqlByteString "1992-00-00 1992")]
    fromList [("fieldName",SqlByteString "DOI"),("value",SqlByteString "10.1145/143165.143169")]
    fromList [("fieldName",SqlByteString "accessDate"),("value",SqlByteString "2016-04-04 20:05:57")]
    fromList [("fieldName",SqlByteString "language"),("value",SqlByteString "en")]
    fromList [("fieldName",SqlByteString "title"),("value",SqlByteString "The essence of functional programming")]
    fromList [("fieldName",SqlByteString "proceedingsTitle"),("value",SqlByteString "POPL '92 Proceedings of the 19th ACM SIGPLAN-SIGACT symposium on Principles of programming languages")]
    it :: ()
    >
    

    Query Item by ID

    :{
    queryItemByID ::  IConnection conn => Int -> conn -> IO [[SqlValue]]
    queryItemByID itemID conn = do
      stmt <- prepare conn query
      execute stmt [toSql itemID]
      rows <- fetchAllRows stmt 
      return rows 
      where
        query = unlines $ [ "SELECT fieldName, value FROM itemDataValues, itemData, fields WHERE", 
                            "itemDataValues.valueID = itemData.valueID", 
                            "and itemData.itemID = ?",
                            "and fields.fieldID = itemData.fieldID"
                          ]
    :}
    
    queryItemByID :: IConnection conn => Int -> conn -> IO [[SqlValue]]
    
    > queryItemByID 5569 conn >>= mapM_ print
    [SqlByteString "title",SqlByteString "From Stochastic Calculus to Mathematical Finance-Kabanov.pdf"]
    it :: ()
    > 
    
    > queryItemByID 5790 conn >>= mapM_ print
    [SqlByteString "url",SqlByteString "http://twanvl.nl/blog/haskell/building-pipes-with-monad-transformers"]
    [SqlByteString "accessDate",SqlByteString "2016-04-04 16:45:25"]
    [SqlByteString "title",SqlByteString "Building pipes with monad transformers"]
    it :: ()
    > 
    
    > queryItemByID 5805 conn >>= mapM_ print
    [SqlByteString "url",SqlByteString "http://www.eliza.ch/doc/wadler92essence_of_FP.pdf"]
    [SqlByteString "place",SqlByteString "Albuquerque, NM, USA"]
    [SqlByteString "publisher",SqlByteString "ACM"]
    [SqlByteString "pages",SqlByteString "1-14"]
    [SqlByteString "date",SqlByteString "1992-00-00 1992"]
    [SqlByteString "DOI",SqlByteString "10.1145/143165.143169"]
    [SqlByteString "accessDate",SqlByteString "2016-04-04 20:05:57"]
    [SqlByteString "language",SqlByteString "en"]
    [SqlByteString "title",SqlByteString "The essence of functional programming"]
    [SqlByteString "proceedingsTitle",SqlByteString "POPL '92 Proceedings of the 19th ACM SIGPLAN-SIGACT symposium on Principles of programming languages"]
    it :: ()
    >
    

    Disconnect

    > :t disconnect 
    disconnect :: IConnection conn => conn -> IO ()
    > 
    
    > disconnect conn
    it :: ()
    >
    

    1.3.2 Example - Datbase query with ReaderT monad transformer.

    Note: The type of the monad is ReaderT r m without the parameter of return value a.

      Signature Description
         
    ReaderT r m a   Encapsulates a function or computation (a -> m b) where m is a monad.
    ReaderT (r -> m a) -> ReaderT r m a  
         
    runReaderT ReaderT r m a -> r -> m a Run computation and get its value.
         
         
    return a -> (ReaderT r m) a Monad return function.
    (>>=) a -> (ReaderT r m) b -> (ReaderT r m) b Monad bind function.
         
         
    fmap (a -> b) -> (ReaderT r m) a -> (ReaderT r m) b Apply a function (a -> b) to the result of computation Reader r a.
         
         
    ask m r Read environment or configuration.
         
         
    local (r -> r) -> m a -> m a Modify environment by applying a function to it.
         
         
    liftIO MonadIO m => IO a -> m a  
    liftIO IO a -> (Reader r m) a  
         
         

    Example: This example will need the database and the haskell packages shown in Databases - HDBC package

    import Control.Monad (mapM_, forM_)
    import Control.Monad.Reader 
    
    import qualified Database.HDBC as HDBC
    import Database.HDBC.Sqlite3 
    
    -- In file add the lines
    {-# LANGUAGE OverloadedStrings #-}
    {-# LANGUAGE RankNTypes #-}
    {-# LANGUAGE FlexibleContexts #-}
    
    -- In the REPL
    :set -XOverloadedStrings
    :set -XRankNTypes
    
    :{
    fromSqlToInt :: HDBC.SqlValue -> Int
    fromSqlToInt  sv = HDBC.fromSql sv  
    :}
    
    :{ 
    fromSqlToString :: HDBC.SqlValue -> String
    fromSqlToString sv = HDBC.fromSql sv 
    :}
    
    :{
    {- | Convert number to HDBC value -}
    fromIntToHDBC :: Int -> HDBC.SqlValue
    fromIntToHDBC n = HDBC.SqlInt64 $ fromIntegral n                 
    :}
    
     {- | Convert string to HDBC string -}                  
    fromStrToHDBC s = HDBC.SqlString s                 
    
    
    conn <- connectSqlite3 "zotero.sqlite" 
    
    
    :{
    queryItemByIDRaw ::  HDBC.IConnection conn => Int -> conn -> IO [(String, String)]
    queryItemByIDRaw itemID conn = do
      stmt <- HDBC.prepare conn query
      HDBC.execute stmt [HDBC.toSql itemID]
      rows <- HDBC.fetchAllRows stmt 
      return $ map projection rows 
      where
        projection row = (fromSqlToString $ row !! 0, fromSqlToString $ row !! 1)
    
        query = unlines $ [ "SELECT fieldName, value FROM itemDataValues, itemData, fields WHERE", 
                            "itemDataValues.valueID = itemData.valueID", 
                            "and itemData.itemID = ?",
                            "and fields.fieldID = itemData.fieldID"
                          ]
    :}
    
    
    > :t queryItemByIDRaw 10 conn
    queryItemByIDRaw 10 conn :: IO [(String, String)]
    > 
    > queryItemByIDRaw 10 conn
    [("url","http://fsharp.org/specs/language-spec/4.0/FSharpSpec-4.0-latest.pdf"),("accessDate","2016-11-07 22:00:06"),("title","FSharpSpec-4.0-latest.pdf")]
    it :: [(String, String)]
    
    > queryItemByIDRaw 10 conn >>= mapM_ print
    ("url","http://fsharp.org/specs/language-spec/4.0/FSharpSpec-4.0-latest.pdf")
    ("accessDate","2016-11-07 22:00:06")
    ("title","FSharpSpec-4.0-latest.pdf")
    it :: ()
    > 
    > queryItemByIDRaw 16 conn >>= mapM_ print
    ("url","http://www.digilife.be/quickreferences/QRC/LINUX%20Admin%20Quick%20Reference.pdf")
    ("accessDate","2016-11-07 22:06:48")
    ("title","linux_quickref.PDF - LINUX Admin Quick Reference.pdf")
    it :: ()
    
    
    {- | Transform this computation to ReaderT monad with database connection as the 
         implicit parameter or configuration. 
     -}
    :{
    queryItemByID1 ::  HDBC.IConnection conn => Int -> ReaderT conn IO [(String, String)]
    queryItemByID1 itemID = ReaderT $ \ conn -> do                      
      stmt <- liftIO $ HDBC.prepare conn query
      liftIO $ HDBC.execute stmt [HDBC.toSql itemID]
      rows <- liftIO $ HDBC.fetchAllRows stmt 
      return $ map projection rows 
      where
        projection row = (fromSqlToString $ row !! 0, fromSqlToString $ row !! 1)
    
        query = unlines $ [ "SELECT fieldName, value FROM itemDataValues, itemData, fields WHERE", 
                            "itemDataValues.valueID = itemData.valueID", 
                            "and itemData.itemID = ?",
                            "and fields.fieldID = itemData.fieldID"
                          ]
    :}
    
    
    > :t runReaderT
    runReaderT :: ReaderT r m a -> r -> m a
    
    > :t queryItemByID1 10
    queryItemByID1 10
      :: IConnection conn => ReaderT conn IO [(String, String)]
    > 
    
    > :t runReaderT (queryItemByID1 10) conn 
    runReaderT (queryItemByID1 10) conn :: IO [(String, String)]
    > 
    
    > runReaderT (queryItemByID1 10) conn
    [("url","http://fsharp.org/specs/language-spec/4.0/FSharpSpec-4.0-latest.pdf"),("accessDate","2016-11-07 22:00:06"),("title","FSharpSpec-4.0-latest.pdf")]
    it :: [(String, String)]
    > 
    
    > runReaderT (queryItemByID1 10) conn >>= mapM_ print
    ("url","http://fsharp.org/specs/language-spec/4.0/FSharpSpec-4.0-latest.pdf")
    ("accessDate","2016-11-07 22:00:06")
    ("title","FSharpSpec-4.0-latest.pdf")
    it :: ()
    > 
    > 
    
    -- =======================================================================      
    {- |  Variation 2 - Simplify - ReaderT conn IO [(String, String)]  -}
    
    {- | Database Connection -> DbConn a = ReaderT conn IO a = conn -> IO a -}
    type DBConn a = forall conn. (HDBC.IConnection conn) =>  ReaderT conn IO a
    
    
    :{
    queryItemByID2 :: Int -> DBConn [(String, String)]
    queryItemByID2 itemID = ReaderT $ \ conn -> do                      
      stmt <- liftIO $ HDBC.prepare conn query
      liftIO $ HDBC.execute stmt [HDBC.toSql itemID]
      rows <- liftIO $ HDBC.fetchAllRows stmt 
      return $ map projection rows 
      where
        projection row = (fromSqlToString $ row !! 0, fromSqlToString $ row !! 1)                    
        query = unlines $ [ "SELECT fieldName, value FROM itemDataValues, itemData, fields WHERE", 
                            "itemDataValues.valueID = itemData.valueID", 
                            "and itemData.itemID = ?",
                            "and fields.fieldID = itemData.fieldID"
                          ]
    :}
    
    
    > :t queryItemByID2 10
    queryItemByID2 10
      :: IConnection conn => ReaderT conn IO [(String, String)]
    > 
    
    > runReaderT (queryItemByID2 10) conn
    [("url","http://fsharp.org/specs/language-spec/4.0/FSharpSpec-4.0-latest.pdf"),("accessDate","2016-11-07 22:00:06"),("title","FSharpSpec-4.0-latest.pdf")]
    it :: [(String, String)]
    > 
    > runReaderT (queryItemByID2 10) conn >>= mapM_ print
    ("url","http://fsharp.org/specs/language-spec/4.0/FSharpSpec-4.0-latest.pdf")
    ("accessDate","2016-11-07 22:00:06")
    ("title","FSharpSpec-4.0-latest.pdf")
    it :: ()
    > 
    
    
    -- =======================================================================      
    {- |  Variation 3 - It can be simplified and generalized even further. -}
    
    type SQL = String
    type DBConn a = forall conn. (HDBC.IConnection conn) =>  ReaderT conn IO a
    
    :{    
    sqlQueryAll :: SQL -> [HDBC.SqlValue] -> ([HDBC.SqlValue] -> b) -> DBConn [b]     
    sqlQueryAll sql sqlvals projection = do
      con     <- ask 
      stmt    <- liftIO $ HDBC.prepare con sql
      liftIO  $  HDBC.execute stmt sqlvals  
      rows    <- liftIO $ HDBC.fetchAllRows stmt
      return (fmap projection rows)
    :}
    
    
    
    :{
    queryItemByID3 :: Int -> DBConn [(String, String)]
    queryItemByID3 itemID = sqlQueryAll query [fromIntToHDBC itemID] projection
      where
        projection row = (fromSqlToString $ row !! 0, fromSqlToString $ row !! 1)                    
        query = unlines $ [ "SELECT fieldName, value FROM itemDataValues, itemData, fields WHERE", 
                            "itemDataValues.valueID = itemData.valueID", 
                            "and itemData.itemID = ?",
                            "and fields.fieldID = itemData.fieldID"
                          ]
    :}
    
    
    > runReaderT (queryItemByID3 10) conn
    [("url","http://fsharp.org/specs/language-spec/4.0/FSharpSpec-4.0-latest.pdf"),("accessDate","2016-11-07 22:00:06"),("title","FSharpSpec-4.0-latest.pdf")]
    it :: [(String, String)]
    > 
    > runReaderT (queryItemByID3 10) conn >>= mapM_ print
    ("url","http://fsharp.org/specs/language-spec/4.0/FSharpSpec-4.0-latest.pdf")
    ("accessDate","2016-11-07 22:00:06")
    ("title","FSharpSpec-4.0-latest.pdf")
    it :: ()
    > 
    >
    
    -- | Get all tags associated with a given item
    :{
    getItemTags :: Int -> DBConn [(Int, String)]
    getItemTags itemID = do  
      sqlQueryAll sql [fromIntToHDBC itemID] projection
      where
        sql = unlines ["SELECT  tags.tagID, tags.name",
                       "FROM    itemTags, tags", 
                       "WHERE   itemTags.tagID = tags.tagID", 
                       "AND     itemID = ?"
                       ]
    
        projection xs = (fromSqlToInt (xs !! 0), fromSqlToString (xs !! 1))
    :}
    
    
    > runReaderT (getItemTags 12) conn
    [(2,"fp"),(3,"fsharp"),(4,"doc"),(5,"overview")]
    it :: [(Int, String)]
    > 
    > runReaderT (getItemTags 13) conn
    [(2,"fp"),(3,"fsharp"),(4,"doc")]
    it :: [(Int, String)]
    > 
    
    
    :{
    showItem :: Int -> DBConn ()
    showItem itemID = do
      itemData <- queryItemByID3 itemID  
      tags   <- getItemTags itemID
    
      -- liftIO :: IO () => DBConn ()          
      liftIO $ do
        putStrLn "Item Data"
        putStrLn "---------------------------"             
        forM_ itemData print
        putStrLn "Item Tags"
        putStrLn "---------------------------"
        forM_ tags print     
    :}   
    
    > runReaderT (showItem 12) conn
    Item Data
    ---------------------------
    ("url","http://tomasp.net/articles/fsharp-i-introduction/article.pdf")
    ("accessDate","2016-11-07 22:01:04")
    ("title","F# Language Overview")
    Item Tags
    ---------------------------
    (2,"fp")
    (3,"fsharp")
    (4,"doc")
    (5,"overview")
    it :: ()
    > 
    > 
    
    > runReaderT (showItem 13) conn
    Item Data
    ---------------------------
    ("url","http://fsharp.org/specs/language-spec/4.0/FSharpSpec-4.0-latest.pdf")
    ("accessDate","2016-11-07 22:00:06")
    ("title","The F# 4.0 Language Specification")
    Item Tags
    ---------------------------
    (2,"fp")
    (3,"fsharp")
    (4,"doc")
    it :
    

    Author: nobody

    Created: 2018-05-07 Mon 10:11

    Emacs 25.3.1 (Org mode 8.2.10)

    Validate