packages feed

DSH-0.10.0.0: examples/Example03.hs

{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE GADTs                 #-}
{-# LANGUAGE MonadComprehensions   #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE RebindableSyntax      #-}
{-# LANGUAGE TemplateHaskell       #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE UndecidableInstances  #-}
{-# LANGUAGE ViewPatterns          #-}

-- | A number of more complex DSH examples based on the TPC-H
-- benchmark schema. A data generator is available at
-- <http://www.tpc.org/tpch/>. Note that DSH currently does not
-- support temporal and decimal types. Those have to be mapped to
-- epoch integers and doubles, respectively. The script
-- 'examples/dshify-tpch.sql' modifies a PostgreSQL TPCH database
-- accordingly.
module Main where

import qualified Prelude as P
import Database.DSH
import Database.DSH.Compiler

import Database.HDBC.PostgreSQL

-- | We declare a flat record type that maps to the relational
-- schema. Names of record selectors should match attribute names in
-- the schema. Note that record selectors should be listed in
-- alphabetical order.
data LineItem = LineItem
    { l_comment       :: Text
    , l_commitdate    :: Integer
    , l_discount      :: Double
    , l_extendedprice :: Double
    , l_linenumber    :: Integer
    , l_linestatus    :: Text
    , l_orderkey      :: Integer
    , l_partkey       :: Integer
    , l_quantity      :: Double
    , l_receiptdate   :: Integer
    , l_returnflag    :: Text
    , l_shipdate      :: Integer
    , l_shipinstruct  :: Text
    , l_shipmode      :: Text
    , l_suppkey       :: Integer
    , l_tax           :: Double
    }
    deriving (Show)

-- A bit of Template Haskell code automatically derives instances and
-- infrastructure to use the record type in DSH queries. For each
-- record selector, we derive a variant that can be used in
-- queries. For example, we get a lifted record selector 
-- 'l_commentQ :: Q LineItem -> Q Text'.
deriveDSH ''LineItem
deriveTA ''LineItem
generateTableSelectors ''LineItem

-- | The 'table' combinator implements a database table scan.
lineitems :: Q [LineItem]
lineitems = table "lineitem" $ TableHints [Key ["l_orderkey", "l_linenumber"]] NonEmpty

data Order = Order
    { o_clerk         :: Text
    , o_comment       :: Text
    , o_custkey       :: Integer
    , o_orderdate     :: Integer
    , o_orderkey      :: Integer
    , o_orderpriority :: Text
    , o_orderstatus   :: Text
    , o_shippriority  :: Integer
    , o_totalprice    :: Double
    }
    deriving (Show)

deriveDSH ''Order
deriveTA ''Order
generateTableSelectors ''Order

orders :: Q [Order]
orders = table "orders" $ TableHints [Key ["o_orderkey"]] NonEmpty

--------------------------------------------------------------------------------

-- Select all lineitems that were shipped before a given date
ordersBefore :: Q Integer -> Q [LineItem]
ordersBefore date = [ li | li <- lineitems , l_shipdateQ li <= date ]

fst9 :: (QA a, QA b, QA c, QA d, QA e, QA f, QA g, QA h, QA i) => Q (a, b, c, d, e, f, g, h, i) -> Q a
fst9 (view -> (a, _, _, _, _, _, _, _, _)) = a

-- Compute the revenue of a single lineitem
revenue :: Q LineItem -> Q Double
revenue li = l_extendedpriceQ li * (1 - l_discountQ li)

-- TPC-H benchmark query Q1
q1 :: Q Integer 
   -> Q [((Text, Text), Double, Double, Double, Double, Double, Double, Double, Integer)]
q1 maxDate = sortWith fst9 $ 
     [ tup9
	  k
	  (sum $ map l_quantityQ lis)
	  (sum $ map l_extendedpriceQ lis)
	  (sum $ map revenue lis)
	  (sum $ map (\li -> revenue li * (1 + l_taxQ li)) lis)
	  (avg $ map l_quantityQ lis)
	  (avg $ map l_extendedpriceQ lis)
	  (avg $ map l_discountQ lis)
	  (length lis)
      | (view -> (k, lis)) <- groupWithKey (\li -> pair (l_returnflagQ li) (l_linestatusQ li)) 
                              $ ordersBefore maxDate
      ]

--------------------------------------------------------------------------------

data Range = Range { start :: Integer, end :: Integer }

inside :: Q Integer -> Range -> Q Bool
inside d range = d >= toQ (start range) && d < toQ (end range)

lineItemsOf :: Q Order -> Q [LineItem]
lineItemsOf o = [ l | l <- lineitems,
                      l_orderkeyQ l == o_orderkeyQ o ]

-- Has at least one of the orders' items been delivered late?
hasLateItem :: Q Order -> Q Bool
hasLateItem o =
  or [ l_commitdateQ l < l_receiptdateQ l | l <- lineItemsOf o ]

-- Compute the number of delayed orders per priority level in the
-- given quarter (TPC-H benchmark query Q4).
q4 :: Range -> Q [(Text, Integer)]
q4 quarter = sortWith fst
  [ pair priority (length delays)
  | (view -> (priority, delays)) <- groupWithKey o_orderpriorityQ delayedOrders ]
  where
    delayedOrders = [ o | o <- orders
                    , o_orderdateQ o `inside` quarter
                    , hasLateItem o
                    ]

--------------------------------------------------------------------------------

getConn :: IO Connection
getConn = connectPostgreSQL "user = 'au' password = 'foobar' host = 'localhost' dbname = 'tpchsmall'"

execQ :: (Show a,QA a) => Q a -> IO ()
execQ q = getConn P.>>= \conn -> (runQ conn q P.>>= P.print) P.>> disconnect conn

main :: IO ()
main = sequence_  [ execQ $ q1 904663977
                  -- Compute Q4 for a three-month interval
                  , execQ (q4 $ Range 741540777 749489577)
                  ]