{-# LANGUAGE GADTs, FlexibleContexts, FlexibleInstances #-}
{-# LANGUAGE CPP, DataKinds #-}
-- | The expression type underlying 'Col'.
module Database.Selda.Exp where
import Database.Selda.SqlType ( Lit, SqlTypeRep )
import Database.Selda.Types ( ColName )
import Data.Text (Text)

-- | A type-erased column, which may also be renamed.
--   Only for internal use.
data SomeCol sql where
  Some  :: !(Exp sql a) -> SomeCol sql
  Named :: !ColName -> !(Exp sql a) -> SomeCol sql

data UntypedCol sql where
  Untyped :: !(Exp sql a) -> UntypedCol sql

-- | Turn a renamed column back into a regular one.
--   If the column was renamed, it will be represented by a literal column,
--   and not its original expression.
hideRenaming :: SomeCol sql -> UntypedCol sql
hideRenaming :: forall sql. SomeCol sql -> UntypedCol sql
hideRenaming (Named ColName
n Exp sql a
_) = forall sql a. Exp sql a -> UntypedCol sql
Untyped (forall sql a. ColName -> Exp sql a
Col ColName
n)
hideRenaming (Some Exp sql a
c)    = forall sql a. Exp sql a -> UntypedCol sql
Untyped Exp sql a
c

-- | Underlying column expression type, parameterised over the type of
--   SQL queries.
data Exp sql a where
  Col     :: !ColName -> Exp sql a
  Lit     :: !(Lit a) -> Exp sql a
  BinOp   :: !(BinOp a b c) -> !(Exp sql a) -> !(Exp sql b) -> Exp sql c
  UnOp    :: !(UnOp a b) -> !(Exp sql a) -> Exp sql b
  NulOp   :: !(NulOp a) -> Exp sql a
  Fun2    :: !Text -> !(Exp sql a) -> !(Exp sql b) -> Exp sql c
  If      :: !(Exp sql Bool) -> !(Exp sql a) -> !(Exp sql a) -> Exp sql a
  Cast    :: !SqlTypeRep -> !(Exp sql a) -> Exp sql b
  Raw     :: !Text -> Exp sql a
  AggrEx  :: !Text -> !(Exp sql a) -> Exp sql b
  InList  :: !(Exp sql a) -> ![Exp sql a] -> Exp sql Bool
  InQuery :: !(Exp sql a) -> !sql -> Exp sql Bool

data NulOp a where
  Fun0 :: !Text -> NulOp a

data UnOp a b where
  Abs    :: UnOp a a
  Not    :: UnOp Bool Bool
  Neg    :: UnOp a a
  Sgn    :: UnOp a a
  IsNull :: UnOp (Maybe a) Bool
  Fun    :: !Text -> UnOp a b

data BinOp a b c where
  Gt   :: BinOp a a Bool
  Lt   :: BinOp a a Bool
  Gte  :: BinOp a a Bool
  Lte  :: BinOp a a Bool
  Eq   :: BinOp a a Bool
  Neq  :: BinOp a a Bool
  And  :: BinOp Bool Bool Bool
  Or   :: BinOp Bool Bool Bool
  Add  :: BinOp a a a
  Sub  :: BinOp a a a
  Mul  :: BinOp a a a
  Div  :: BinOp a a a
  Like :: BinOp Text Text Bool
  CustomOp :: !Text -> BinOp a b c

-- | Any type which may contain column names.
class Names a where
  -- | Get all column names used in the given expression.
  allNamesIn :: a -> [ColName]

instance Names a => Names [a] where
  allNamesIn :: [a] -> [ColName]
allNamesIn = forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap forall a. Names a => a -> [ColName]
allNamesIn

instance Names sql => Names (Exp sql a) where
  allNamesIn :: Exp sql a -> [ColName]
allNamesIn (Col ColName
n)       = [ColName
n]
  allNamesIn (Lit Lit a
_)       = []
  allNamesIn (BinOp BinOp a b a
_ Exp sql a
a Exp sql b
b) = forall a. Names a => a -> [ColName]
allNamesIn Exp sql a
a forall a. [a] -> [a] -> [a]
++ forall a. Names a => a -> [ColName]
allNamesIn Exp sql b
b
  allNamesIn (UnOp UnOp a a
_ Exp sql a
a)    = forall a. Names a => a -> [ColName]
allNamesIn Exp sql a
a
  allNamesIn (NulOp NulOp a
_)     = []
  allNamesIn (Fun2 Text
_ Exp sql a
a Exp sql b
b)  = forall a. Names a => a -> [ColName]
allNamesIn Exp sql a
a forall a. [a] -> [a] -> [a]
++ forall a. Names a => a -> [ColName]
allNamesIn Exp sql b
b
  allNamesIn (If Exp sql Bool
a Exp sql a
b Exp sql a
c)    = forall a. Names a => a -> [ColName]
allNamesIn Exp sql Bool
a forall a. [a] -> [a] -> [a]
++ forall a. Names a => a -> [ColName]
allNamesIn Exp sql a
b forall a. [a] -> [a] -> [a]
++ forall a. Names a => a -> [ColName]
allNamesIn Exp sql a
c
  allNamesIn (Cast SqlTypeRep
_ Exp sql a
x)    = forall a. Names a => a -> [ColName]
allNamesIn Exp sql a
x
  allNamesIn (AggrEx Text
_ Exp sql a
x)  = forall a. Names a => a -> [ColName]
allNamesIn Exp sql a
x
  allNamesIn (InList Exp sql a
x [Exp sql a]
xs) = forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap forall a. Names a => a -> [ColName]
allNamesIn (Exp sql a
xforall a. a -> [a] -> [a]
:[Exp sql a]
xs)
  allNamesIn (InQuery Exp sql a
x sql
q) = forall a. Names a => a -> [ColName]
allNamesIn Exp sql a
x forall a. [a] -> [a] -> [a]
++ forall a. Names a => a -> [ColName]
allNamesIn sql
q
  allNamesIn (Raw Text
_)       = []

instance Names sql => Names (SomeCol sql) where
  allNamesIn :: SomeCol sql -> [ColName]
allNamesIn (Some Exp sql a
c)    = forall a. Names a => a -> [ColName]
allNamesIn Exp sql a
c
  allNamesIn (Named ColName
n Exp sql a
c) = ColName
n forall a. a -> [a] -> [a]
: forall a. Names a => a -> [ColName]
allNamesIn Exp sql a
c

instance Names sql => Names (UntypedCol sql) where
  allNamesIn :: UntypedCol sql -> [ColName]
allNamesIn (Untyped Exp sql a
c) = forall a. Names a => a -> [ColName]
allNamesIn Exp sql a
c