ribbit-1.0.0.0: src/Database/Ribbit/Select.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module Database.Ribbit.Select (
-- * Query Structure
Select,
From,
As,
On,
LeftJoin,
-- * Type families
RenderFieldList,
RenderField,
Expr,
) where
import Database.Ribbit.Conditions (RenderJoinConditions, Where, RenderConditions)
import Database.Ribbit.Render (Render)
import Database.Ribbit.Table (Name, DBSchema, Field, (:>), Table, Flatten,
Validate)
import GHC.TypeLits (AppendSymbol, Symbol)
{- | "SELECT" constructor, used for starting a @SELECT@ statement. -}
data Select fields
{- |
"FROM" constructor, used for attaching a SELECT projection to a relation
in the database.
-}
data From proj relation
infixl 6 `From`
{- | "AS" constructor, used for attaching a name to a table in a FROM clause. -}
data As relation (name :: Symbol)
infix 9 `As`
{- | Cross Product -}
instance (Table table) => Table ((table `As` alias) : more) where
type Name ((table `As` alias):more) =
CrossProductName ((table `As` alias) : more)
type DBSchema ((table `As` alias) : more) =
CrossProductSchema ((table `As` alias) : more)
{- | Product the renderable "name" of a cross product. -}
type family CrossProductName cp where
CrossProductName '[table `As` name] =
Name table
`AppendSymbol` " AS "
`AppendSymbol` name
CrossProductName ((table `As` name) : moreTables) =
Name table
`AppendSymbol` " AS "
`AppendSymbol` name
`AppendSymbol` ", "
`AppendSymbol` CrossProductName moreTables
{- | Produce the schema of a cross product. -}
type family CrossProductSchema cp where
CrossProductSchema '[table `as` name] =
Flatten (
AliasAs name (DBSchema table)
)
CrossProductSchema ((table `As` name) : moreTables) =
Flatten (
AliasAs name (DBSchema table)
:> CrossProductSchema moreTables
)
{- |
Rename the fields in a given schema to reflect an applied table
alias. For instance, data Foo
-}
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
{- | "ON" keyword, for joins. -}
data On join (conditions :: *)
infix 7 `On`
{- | Left Joins. -}
data LeftJoin left right
infix 8 `LeftJoin`
type instance Render (From (Select proj) table) =
"SELECT "
`AppendSymbol` RenderFieldList proj (DBSchema table)
`AppendSymbol` " FROM "
`AppendSymbol` Name table
type family RenderFieldList fields schema where
RenderFieldList '[field] schema =
RenderField field schema
RenderFieldList (f1:f2:more) schema =
RenderField f1 schema
`AppendSymbol` ", "
`AppendSymbol` RenderFieldList (f2:more) schema
type family RenderField field schema where
RenderField field schema =
Validate field schema field
data Expr (a :: k)
instance Table (((l `As` lname) `LeftJoin` (r `As` rname)) `On` conditions) where
type Name (((l `As` lname) `LeftJoin` (r `As` rname)) `On` conditions) =
Name l
`AppendSymbol` " AS "
`AppendSymbol` lname
`AppendSymbol` " LEFT JOIN "
`AppendSymbol` Name r
`AppendSymbol` " AS "
`AppendSymbol` rname
`AppendSymbol` " ON "
`AppendSymbol`
RenderJoinConditions
conditions
(
LeftJoinSchema
(AliasAs lname (DBSchema l))
(AliasAs rname (DBSchema r))
)
type DBSchema (((l `As` lname) `LeftJoin` (r `As` rname)) `On` conditions) =
LeftJoinSchema
(AliasAs lname (DBSchema l))
(AliasAs rname (DBSchema r))
{- | Produce the schema for a left join. -}
type family LeftJoinSchema l r where
LeftJoinSchema l r =
Flatten (l :> Nullable r)
{- | Make all the fields of a schema nullable. -}
type family Nullable schema where
Nullable (Field name (Maybe typ)) =
Field name (Maybe typ)
Nullable (Field name typ) =
Field name (Maybe typ)
Nullable (a :> b) =
Flatten (Nullable a :> Nullable b)
type instance Render (proj `From` table `Where` conditions) =
Render (proj `From` table)
`AppendSymbol` " WHERE "
`AppendSymbol` RenderConditions conditions (DBSchema table)