packages feed

algebra-sql-0.3.0.0: src/Database/Algebra/Table/Lang.hs

{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE GADTs                #-}
{-# LANGUAGE RankNTypes           #-}
{-# LANGUAGE TemplateHaskell      #-}
{-# LANGUAGE TypeSynonymInstances #-}

-- | A representation of table algebra operators over multiset
-- relations.
module Database.Algebra.Table.Lang where

import           Data.Aeson
import           Data.Aeson.TH
import           Data.Decimal
import           Text.PrettyPrint.ANSI.Leijen ((<+>), (<>))
import qualified Text.PrettyPrint.ANSI.Leijen as P
import           Text.Printf

import qualified Data.Text                    as T
import qualified Data.Time.Calendar           as C

import           Database.Algebra.Dag         (Operator, opChildren,
                                               replaceOpChild)
import           Database.Algebra.Dag.Common

-- | Sorting rows in a direction
data SortDir = Asc
             | Desc
    deriving (Eq, Ord, Read, Show)

data AggrType = Avg Expr
              | Max Expr
              | Min Expr
              | Sum Expr
              | CountStar
              | Count Expr
              | All Expr
              | Any Expr
    deriving (Eq, Ord, Show)

instance P.Pretty AggrType where
    pretty (Avg c)   = P.text "avg" <> P.parens (P.pretty c)
    pretty (Max c)   = P.text "max" <> P.parens (P.pretty c)
    pretty (Min c)   = P.text "min" <> P.parens (P.pretty c)
    pretty (Sum c)   = P.text "sum" <> P.parens (P.pretty c)
    pretty CountStar = P.text "count(*)"
    pretty (Count c) = P.text "count" <> P.parens (P.pretty c)
    pretty (All c)   = P.text "all" <> P.parens (P.pretty c)
    pretty (Any c)   = P.text "any" <> P.parens (P.pretty c)

-- | The show instance results in values that are accepted in the xml plan.
instance P.Pretty SortDir where
    pretty Asc  = P.text "ascending"
    pretty Desc = P.text "descending"

-- | table algebra types
--  At this level we do not have any structural types anymore
--  those are represented by columns.
data ATy where
    AInt :: ATy
    AStr :: ATy
    ABool :: ATy
    ADec :: ATy
    ADouble :: ATy
    ADate :: ATy
    deriving (Eq, Ord, Show)

-- | Show the table algebra types in a way that is compatible with
--  the xml plan.
instance P.Pretty ATy where
    pretty ADate    = P.text "date"
    pretty AInt     = P.text "int"
    pretty AStr     = P.text "str"
    pretty ABool    = P.text "bool"
    pretty ADec     = P.text "dec"
    pretty ADouble  = P.text "dbl"

-- | Wrapper around values that can occur in an table algebra plan
data AVal where
    VInt    :: Integer -> AVal
    VStr    :: T.Text -> AVal
    VBool   :: Bool -> AVal
    VDouble :: Double -> AVal
    VDec    :: Decimal -> AVal
    VDate   :: C.Day -> AVal
    deriving (Eq, Ord, Show)

-- | Show the values in the way compatible with the xml plan.
instance P.Pretty AVal where
    pretty (VInt x)      = P.integer x
    pretty (VStr x)      = P.text $ T.unpack x
    pretty (VBool True)  = P.text "true"
    pretty (VBool False) = P.text "false"
    pretty (VDouble x)   = P.double x
    pretty (VDec d)      = P.text $ show d
    pretty (VDate d)     = P.text $ C.showGregorian d

-- | Attribute name or column name
type Attr            = String

-- | Name of an attribute in which the result of an expression,
-- aggregate or window function is stored.
type ResAttr         = Attr

-- | Names of partition attributes used in window specifications
type PartAttr        = Attr

-- | Left attribute name, used to represent the left argument when
-- applying binary operators
type LeftAttr        = Attr

-- | Right attribute name, used to represent the right argument when
-- applying binary operators
type RightAttr       = Attr
--
-- | Name of a database table
type TableName           = String

-- | Typed columns
type TypedAttr = (Attr, ATy)

-- | Key of a database table, a key consists of multiple column names
newtype Key = Key [Attr] deriving (Eq, Ord, Show)

-- | Sorting information
type SortSpec              = (Expr, SortDir)

-- | Binary functions and operators in expressions
data BinFun = Gt
            | Lt
            | GtE
            | LtE
            | Eq
            | NEq
            | And
            | Or
            | Plus
            | Minus
            | Times
            | Div
            | Modulo
            | Contains
            | SimilarTo
            | Like
            | Concat
            | Coalesce
            deriving (Eq, Ord, Show)

instance P.Pretty BinFun where
    pretty Minus     = P.text $ "-"
    pretty Plus      = P.text $ "+"
    pretty Times     = P.text $ "*"
    pretty Div       = P.text $ "/"
    pretty Modulo    = P.text $ "%"
    pretty Contains  = P.text $ "fn:contains"
    pretty Concat    = P.text $ "fn:concat"
    pretty SimilarTo = P.text $ "fn:similar_to"
    pretty Like      = P.text $ "fn:like"
    pretty Gt        = P.text $ ">"
    pretty Lt        = P.text $ "<"
    pretty GtE       = P.text $ ">="
    pretty LtE       = P.text $ "<="
    pretty Eq        = P.text $ "=="
    pretty NEq       = P.text $ "<>"
    pretty And       = P.text $ "&&"
    pretty Or        = P.text $ "||"
    pretty Coalesce  = P.text $ "coalesce"

-- | Unary functions/operators in expressions
data UnFun = Not
           | Cast ATy
           | Sin
           | Cos
           | Tan
           | ASin
           | ACos
           | ATan
           | Sqrt
           | Log
           | Exp
           | DateDay
           | DateYear
           | DateMonth
           | SubString Integer Integer
           | IsNull
           deriving (Eq, Ord, Show)

instance P.Pretty UnFun where
    pretty Not             = P.text $ "not"
    pretty (Cast ty)       = P.text "cast->" <> P.pretty ty
    pretty Sin             = P.text $ "sin"
    pretty Cos             = P.text $ "cos"
    pretty Tan             = P.text $ "tan"
    pretty Sqrt            = P.text $ "sqrt"
    pretty Exp             = P.text $ "exp"
    pretty Log             = P.text $ "log"
    pretty ASin            = P.text $ "asin"
    pretty ACos            = P.text $ "acos"
    pretty ATan            = P.text $ "atan"
    pretty DateDay         = P.text $ "date_day"
    pretty DateYear        = P.text $ "date_year"
    pretty DateMonth       = P.text $ "date_month"
    pretty IsNull          = P.text $ "is_null"
    pretty (SubString f t) = P.text $ printf "subString_%d,%d" f t

-- | Projection expressions
data Expr = BinAppE BinFun Expr Expr
          | UnAppE UnFun Expr
          | ColE Attr
          | ConstE AVal
          | IfE Expr Expr Expr
          deriving (Eq, Ord, Show)

-- | Expressions which are used to specify partitioning in window
-- functions.
type PartExpr = Expr

parenthize :: Expr -> P.Doc
parenthize e =
    case e of
        ColE _   -> P.pretty e
        ConstE _ -> P.pretty e
        _        -> P.parens $ P.pretty e

instance P.Pretty Expr where
    pretty (BinAppE f e1 e2) = parenthize e1 <+> P.pretty f <+> parenthize e2
    pretty (UnAppE f e)      = P.pretty f <+> (parenthize e)
    pretty (ColE c)          = P.text c
    pretty (ConstE v)        = P.pretty v
    pretty (IfE c t e)       = P.text "if" <+> parenthize c
                                           <+> parenthize t
                                           <+> parenthize e

-- | New column name and the expression that generates the new column
type Proj                = (ResAttr, Expr)

-- | A tuple is a list of values
type Tuple = [AVal]

-- | Comparison operators which can be used for ThetaJoins.
data JoinRel = EqJ -- equal
             | GtJ -- greater than
             | GeJ -- greater equal
             | LtJ -- less than
             | LeJ -- less equal
             | NeJ -- not equal
             deriving (Eq, Ord, Show)

instance P.Pretty JoinRel where
    pretty EqJ = P.text "=="
    pretty GtJ = P.text ">"
    pretty GeJ = P.text ">="
    pretty LtJ = P.text "<"
    pretty LeJ = P.text "<="
    pretty NeJ = P.text "/="

-- | Window frame start specification
data FrameStart = FSUnboundPrec  -- ^ UNBOUNDED PRECEDING
                | FSValPrec Int  -- ^ <value> PRECEDING
                | FSCurrRow      -- ^ CURRENT ROW
                deriving (Eq, Ord, Show)

-- | Window frame end specification
data FrameEnd = FECurrRow    -- ^ CURRENT ROW
              | FEValFol Int -- ^ <value> FOLLOWING
              | FEUnboundFol -- ^ UNBOUNDED FOLLOWING
              deriving (Eq, Ord, Show)

data FrameBounds = HalfOpenFrame FrameStart
                 | ClosedFrame FrameStart FrameEnd
                 deriving (Eq, Ord, Show)

data WinFun = WinMax Expr
            | WinMin Expr
            | WinSum Expr
            | WinAvg Expr
            | WinAll Expr
            | WinAny Expr
            | WinFirstValue Expr
            | WinLastValue Expr
            | WinCount
            deriving (Eq, Ord, Show)


data NullOp = LitTable ([Tuple], [TypedAttr])
            | TableRef (TableName, [TypedAttr], [Key])
            deriving (Ord, Eq, Show)

data PayloadCol = PayloadCol Attr Expr deriving (Ord, Eq, Show)
data OrdCol     = OrdCol (Attr, SortDir) Expr deriving (Ord, Eq, Show)
data KeyCol     = KeyCol Attr Expr deriving (Ord, Eq, Show)
data RefCol     = RefCol Attr Expr deriving (Ord, Eq, Show)

instance P.Pretty PayloadCol where
    pretty (PayloadCol c e) = P.text c <> P.colon <> P.pretty e

instance P.Pretty OrdCol where
    pretty (OrdCol (c, Asc) e)  = P.text c <> P.text ".asc:" <> P.pretty e
    pretty (OrdCol (c, Desc) e) = P.text c <> P.text ".desc:" <> P.pretty e

instance P.Pretty KeyCol where
    pretty (KeyCol c e) = P.text c <> P.text ":" <> P.pretty e

instance P.Pretty RefCol where
    pretty (RefCol c e) = P.text c <> P.text ":" <> P.pretty e

data UnOp = RowNum (Attr, [SortSpec], [PartExpr])
          | RowRank (ResAttr, [SortSpec])
          | WinFun ((ResAttr, WinFun), [PartExpr], [SortSpec], Maybe FrameBounds)
          | Rank (ResAttr, [SortSpec])
          | Project [(Attr, Expr)]
          | Select Expr
          | Distinct ()
          | Aggr ([(AggrType, ResAttr)], [(PartAttr, Expr)])

          -- Serialize must only occur as the root node of a query. It
          -- defines physical order, natural key and reference columns
          -- of the query result.
          | Serialize ([RefCol], [KeyCol], [OrdCol], [PayloadCol])
          deriving (Ord, Eq, Show)

data BinOp = Cross ()
           | EqJoin (LeftAttr,RightAttr)
           | ThetaJoin [(Expr, Expr, JoinRel)]
           | LeftOuterJoin [(Expr, Expr, JoinRel)]
           | SemiJoin [(Expr, Expr, JoinRel)]
           | AntiJoin [(Expr, Expr, JoinRel)]
           | DisjUnion ()
           | Difference ()
           deriving (Ord, Eq, Show)

type TableAlgebra = Algebra () BinOp UnOp NullOp AlgNode

replace :: Eq a => a -> a -> a -> a
replace orig new x = if x == orig then new else x

replaceChild :: forall t b u n c. Eq c => c -> c -> Algebra t b u n c -> Algebra t b u n c
replaceChild o n (TerOp op c1 c2 c3) = TerOp op (replace o n c1) (replace o n c2) (replace o n c3)
replaceChild o n (BinOp op c1 c2) = BinOp op (replace o n c1) (replace o n c2)
replaceChild o n (UnOp op c) = UnOp op (replace o n c)
replaceChild _ _ (NullaryOp op) = NullaryOp op

instance Operator TableAlgebra where
    opChildren (TerOp _ c1 c2 c3) = [c1, c2, c3]
    opChildren (BinOp _ c1 c2) = [c1, c2]
    opChildren (UnOp _ c) = [c]
    opChildren (NullaryOp _) = []

    replaceOpChild op old new = replaceChild old new op

instance FromJSON C.Day where
    parseJSON o = (\(y, m, d) -> C.fromGregorian y m d) <$> parseJSON o

instance ToJSON C.Day where
    toJSON = toJSON . C.toGregorian

instance ToJSON Decimal where
    toJSON = toJSON . show

instance FromJSON Decimal where
    parseJSON s = read <$> parseJSON s

--------------------------------------------------------------------------------
-- Aeson instances for JSON serialization

deriveJSON defaultOptions ''AggrType
deriveJSON defaultOptions ''ATy
deriveJSON defaultOptions ''AVal
deriveJSON defaultOptions ''SortDir
deriveJSON defaultOptions ''JoinRel
deriveJSON defaultOptions ''NullOp
deriveJSON defaultOptions ''WinFun
deriveJSON defaultOptions ''UnOp
deriveJSON defaultOptions ''BinOp
deriveJSON defaultOptions ''Expr
deriveJSON defaultOptions ''UnFun
deriveJSON defaultOptions ''BinFun
deriveJSON defaultOptions ''Key
deriveJSON defaultOptions ''RefCol
deriveJSON defaultOptions ''KeyCol
deriveJSON defaultOptions ''OrdCol
deriveJSON defaultOptions ''PayloadCol
deriveJSON defaultOptions ''FrameBounds
deriveJSON defaultOptions ''FrameEnd
deriveJSON defaultOptions ''FrameStart