ribbit-0.1.0.0: src/Database/Ribbit.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{- | No documentation yet. See README for now. -}
module Database.Ribbit (
Select,
From,
X,
Where,
As,
Equals,
(:>)(..),
ReflectRelation(..),
Render(..),
ArgsType,
ResultType,
Field,
And,
Or,
type (?),
) where
import Data.Proxy (Proxy(Proxy))
import Data.Text (Text)
import Data.Tuple.Only (Only(Only))
import GHC.TypeLits (symbolVal, KnownSymbol, TypeError,
ErrorMessage((:<>:), (:$$:), ShowType), AppendSymbol)
import qualified Data.Text as T
import qualified GHC.TypeLits as Lit
{-
Select
'[Foo :> "foo", Bar :> "bar"]
`From` Foo `X` Bar
`Where`
Foo :> id `Equals` Bar :> "foo_id"
`And` Foo :> "baz" `Equals` (?)
-}
class Render query where
render :: proxy query -> Text
{- SELECT -}
instance (Render fields) => Render (Select fields) where
render _proxy =
"SELECT "
<> render (Proxy @fields)
{- Field list -}
instance {-# OVERLAPS #-} (KnownSymbol field) => Render '[field] where
render _proxy = T.pack $ symbolVal (Proxy @field)
instance (KnownSymbol field, Render more) => Render (field:more) where
render _proxy =
T.pack (symbolVal (Proxy @field)) <> ", " <> render (Proxy @more)
{- FROM -}
instance (Render proj, ReflectRelation relation) => Render (From proj relation) where
render _proxy =
render (Proxy @proj)
<> " FROM "
<> reflectRelation (Proxy @relation)
{- WHERE -}
instance (Render query, Render conditions) => Render (Where query conditions) where
render _proxy =
render (Proxy @query)
<> " WHERE "
<> render (Proxy @conditions)
{- Equals -}
instance (Render (Expr l), Render (Expr r)) => Render (Equals l r) where
render _proxy =
render (Proxy @(Expr l))
<> " = "
<> render (Proxy @(Expr r))
{- AND -}
instance (Render l, Render r) => Render (And l r) where
render _proxy =
"( "
<> render (Proxy @l)
<> " AND "
<> render (Proxy @r)
<> " )"
{- OR -}
instance (Render l, Render r) => Render (Or l r) where
render _proxy =
"( "
<> render (Proxy @l)
<> " AND "
<> render (Proxy @r)
<> " )"
{- Expr -}
instance Render (Expr (?)) where
render _proxy = "?"
instance (KnownSymbol a) => Render (Expr a) where
render _proxy = T.pack (symbolVal (Proxy @a))
{- (?) -}
instance Render (?) where
render _proxy = "?"
data Select fields
data From proj relation
infixl 6 `From`
data Where query conditions
infixl 6 `Where`
data Equals l r
infix 9 `Equals`
data And l r
infixr 8 `And`
data Or l r
infixr 7 `Or`
data Field name typ
data X l r
infixr 7 `X`
data As relation name
infix 8 `As`
data a :> b = a :> b
deriving (Eq, Ord, Show)
infixr 5 :>
data (?)
data Expr a
type family ProjectionType proj schema where
ProjectionType '[name] (Field name typ) = Only typ
ProjectionType '[name] (Field name2 typ) =
TypeError (
'Lit.Text "name ("
':<>: 'ShowType name
':<>: 'Lit.Text ") not found in relation."
)
ProjectionType '[name] (Field name typ :> _) = Only typ
ProjectionType '[name] (_ :> more) = ProjectionType '[name] more
ProjectionType (name:more) relation =
ProjectionType '[name] relation :> ProjectionType more relation
class ReflectRelation relation where
type DBSchema relation
reflectRelation :: proxy relation -> Text
{- | Cross product -}
instance (ReflectRelation l, ReflectRelation r, KnownSymbol lname, KnownSymbol rname) => ReflectRelation (l `As` lname `X` r `As` rname) where
type DBSchema (l `As` lname `X` r `As` rname) =
AliasAs lname (DBSchema l)
:> AliasAs rname (DBSchema r)
reflectRelation _prxoy =
reflectRelation (Proxy @l)
<> " as "
<> T.pack (symbolVal (Proxy @lname))
<> ", "
<> reflectRelation(Proxy @r)
<> " as "
<> T.pack (symbolVal (Proxy @rname))
type family AliasAs prefix schema where
AliasAs prefix (Field name typ) =
Field
(prefix `AppendSymbol` "." `AppendSymbol` name)
typ
AliasAs prefix (Field name typ :> more) =
Field
(prefix `AppendSymbol` "." `AppendSymbol` name)
typ
:> AliasAs prefix more
class TestVal a where
testVal :: a
instance (TestVal a, TestVal b) => TestVal (a :> b) where
testVal = testVal :> testVal
instance TestVal (Only Int) where
testVal = Only 0
instance TestVal (Only Text) where
testVal = Only "foo"
type family ResultType query where
ResultType (Select fields `From` relation) =
ProjectionType fields (DBSchema relation)
ResultType (query `Where` conditions) = ResultType query
ResultType query =
TypeError ('Lit.Text "Malformed Query" ':$$: 'ShowType query)
type family ArgsType query where
ArgsType (_ `From` relation `Where` conditions) =
ArgsType (DBSchema relation, conditions)
ArgsType (schema, And a b) =
StripUnit (Flatten (ArgsType (schema, a) :> ArgsType (schema, b)))
ArgsType (schema, Or a b) =
StripUnit (Flatten (ArgsType (schema, a) :> ArgsType (schema, b)))
ArgsType (schema, Equals field (?)) =
ProjectionType '[field] schema
ArgsType _ = ()
type family Flatten a where
Flatten ((a :> b) :> c) = Flatten (a :> b :> c)
Flatten (a :> b) = a :> Flatten b
Flatten a = a
type family StripUnit a where
StripUnit (() :> a) = StripUnit a
StripUnit (a :> ()) = StripUnit a
StripUnit (a :> b) = a :> StripUnit b
StripUnit a = a