rel8-1.0.0.0: src/Rel8/Table.hs
{-# language AllowAmbiguousTypes #-}
{-# language DataKinds #-}
{-# language DefaultSignatures #-}
{-# language DisambiguateRecordFields #-}
{-# language FlexibleContexts #-}
{-# language FlexibleInstances #-}
{-# language FunctionalDependencies #-}
{-# language LambdaCase #-}
{-# language NamedFieldPuns #-}
{-# language ScopedTypeVariables #-}
{-# language StandaloneKindSignatures #-}
{-# language TypeApplications #-}
{-# language TypeFamilies #-}
{-# language TypeOperators #-}
{-# language UndecidableInstances #-}
module Rel8.Table
( Table (Columns, Context, Unreify, toColumns, fromColumns, reify, unreify)
, Congruent
, TTable, TColumns, TContext, TUnreify
)
where
-- base
import Data.Functor ( ($>) )
import Data.Functor.Identity ( Identity( Identity ) )
import Data.Kind ( Constraint, Type )
import Data.List.NonEmpty ( NonEmpty )
import Data.Proxy ( Proxy( Proxy ) )
import Data.Type.Equality ( (:~:)( Refl ) )
import GHC.Generics ( Generic, Rep, from, to )
import Prelude hiding ( null )
-- rel8
import Rel8.FCF ( Eval, Exp )
import Rel8.Kind.Algebra ( KnownAlgebra )
import Rel8.Generic.Map ( GMap, GMappable, gmap, gunmap )
import Rel8.Generic.Table
( GGTable, GGColumns, GGContext, ggfromColumns, ggtoColumns
, GAlgebra
)
import Rel8.Generic.Record ( Record(..) )
import Rel8.Generic.Reify ( ARep )
import Rel8.Schema.Context.Label ( Labelable, labeler, unlabeler )
import Rel8.Schema.HTable ( HTable )
import Rel8.Schema.HTable.Either ( HEitherTable(..) )
import Rel8.Schema.HTable.Identity ( HIdentity(..), HType )
import Rel8.Schema.HTable.Label ( hlabel, hunlabel )
import Rel8.Schema.HTable.List ( HListTable )
import Rel8.Schema.HTable.Maybe ( HMaybeTable(..) )
import Rel8.Schema.HTable.NonEmpty ( HNonEmptyTable )
import Rel8.Schema.HTable.Nullify ( hnulls, hnullify, hunnullify )
import Rel8.Schema.HTable.These ( HTheseTable(..) )
import Rel8.Schema.HTable.Vectorize ( hvectorize, hunvectorize )
import qualified Rel8.Schema.Kind as K
import Rel8.Schema.Null ( Sql )
import Rel8.Schema.Reify
( Reify, Col( Reify ), hreify, hunreify
, UnwrapReify
, notReify
)
import Rel8.Schema.Result
( Col( R ), Result
, relabel
, null, nullifier, unnullifier
, vectorizer, unvectorizer
)
import Rel8.Schema.Spec ( KnownSpec )
import Rel8.Type ( DBType )
import Rel8.Type.Tag ( EitherTag( IsLeft, IsRight ), MaybeTag( IsJust ) )
-- these
import Data.These ( These( This, That, These ) )
import Data.These.Combinators ( justHere, justThere )
-- | @Table@s are one of the foundational elements of Rel8, and describe data
-- types that have a finite number of columns. Each of these columns contains
-- data under a shared context, and contexts describe how to interpret the
-- metadata about a column to a particular Haskell type. In Rel8, we have
-- contexts for expressions (the 'Rel8.Expr' context), aggregations (the
-- 'Rel8.Aggregate' context), insert values (the 'Rel8.Insert' contex), among
-- others.
--
-- In typical usage of Rel8 you don't need to derive instances of 'Table'
-- yourself, as anything that's an instance of 'Rel8.Rel8able' is always a
-- 'Table'.
type Table :: K.Context -> Type -> Constraint
class (HTable (Columns a), context ~ Context a) => Table context a | a -> context where
-- | The 'HTable' functor that describes the schema of this table.
type Columns a :: K.HTable
-- | The common context that all columns use as an interpretation.
type Context a :: K.Context
type Unreify a :: Type
toColumns :: a -> Columns a (Col context)
fromColumns :: Columns a (Col context) -> a
reify :: context :~: Reify ctx -> Unreify a -> a
unreify :: context :~: Reify ctx -> a -> Unreify a
type Columns a = Eval (GGColumns (GAlgebra (Rep (Record a))) TColumns (Rep (Record a)))
type Context a = Eval (GGContext (GAlgebra (Rep (Record a))) TContext (Rep (Record a)))
type Unreify a = DefaultUnreify a
default toColumns ::
( Generic (Record a)
, KnownAlgebra (GAlgebra (Rep (Record a)))
, Eval (GGTable (GAlgebra (Rep (Record a))) (TTable context) TColumns (Col context) (Rep (Record a)))
, Columns a ~ Eval (GGColumns (GAlgebra (Rep (Record a))) TColumns (Rep (Record a)))
, Context a ~ Eval (GGContext (GAlgebra (Rep (Record a))) TContext (Rep (Record a)))
)
=> a -> Columns a (Col context)
toColumns =
ggtoColumns
@(GAlgebra (Rep (Record a)))
@(TTable context)
@TColumns
id
id
toColumns .
from .
Record
default fromColumns ::
( Generic (Record a)
, KnownAlgebra (GAlgebra (Rep (Record a)))
, Eval (GGTable (GAlgebra (Rep (Record a))) (TTable context) TColumns (Col context) (Rep (Record a)))
, Columns a ~ Eval (GGColumns (GAlgebra (Rep (Record a))) TColumns (Rep (Record a)))
, Context a ~ Eval (GGContext (GAlgebra (Rep (Record a))) TContext (Rep (Record a)))
)
=> Columns a (Col context) -> a
fromColumns =
unrecord .
to .
ggfromColumns
@(GAlgebra (Rep (Record a)))
@(TTable context)
@TColumns
id
id
fromColumns
default reify ::
( Generic (Record a)
, Generic (Record (Unreify a))
, GMappable (TTable context) (Rep (Record a))
, Rep (Record (Unreify a)) ~ GMap TUnreify (Rep (Record a))
)
=> context :~: Reify ctx -> Unreify a -> a
reify Refl =
unrecord .
to .
gunmap @(TTable context) (Proxy @TUnreify) (reify Refl) .
from .
Record
default unreify ::
( Generic (Record a)
, Generic (Record (Unreify a))
, GMappable (TTable context) (Rep (Record a))
, Rep (Record (Unreify a)) ~ GMap TUnreify (Rep (Record a))
)
=> context :~: Reify ctx -> a -> Unreify a
unreify Refl =
unrecord .
to .
gmap @(TTable context) (Proxy @TUnreify) (unreify Refl) .
from .
Record
data TTable :: K.Context -> Type -> Exp Constraint
type instance Eval (TTable context a) = Table context a
data TColumns :: Type -> Exp K.HTable
type instance Eval (TColumns a) = Columns a
data TContext :: Type -> Exp K.Context
type instance Eval (TContext a) = Context a
data TUnreify :: Type -> Exp Type
type instance Eval (TUnreify a) = Unreify a
type DefaultUnreify :: Type -> Type
type family DefaultUnreify a where
DefaultUnreify (t a b c d e f g) =
t (Unreify a) (Unreify b) (Unreify c) (Unreify d) (Unreify e) (Unreify f) (Unreify g)
DefaultUnreify (t a b c d e f) =
t (Unreify a) (Unreify b) (Unreify c) (Unreify d) (Unreify e) (Unreify f)
DefaultUnreify (t a b c d e) =
t (Unreify a) (Unreify b) (Unreify c) (Unreify d) (Unreify e)
DefaultUnreify (t a b c d) =
t (Unreify a) (Unreify b) (Unreify c) (Unreify d)
DefaultUnreify (t a b c) = t (Unreify a) (Unreify b) (Unreify c)
DefaultUnreify (t a b) = t (Unreify a) (Unreify b)
DefaultUnreify (t a) = t (Unreify a)
DefaultUnreify a = ARep (GMap TUnreify (Rep a))
-- | Any 'HTable' is also a 'Table'.
instance HTable t => Table context (t (Col context)) where
type Columns (t (Col context)) = t
type Context (t (Col context)) = context
type Unreify (t (Col context)) = t (Col (UnwrapReify context))
toColumns = id
fromColumns = id
reify Refl = hreify
unreify Refl = hunreify
-- | Any context is trivially a table.
instance KnownSpec spec => Table context (Col context spec) where
type Columns (Col context spec) = HIdentity spec
type Context (Col context spec) = context
type Unreify (Col context spec) = Col (UnwrapReify context) spec
toColumns = HIdentity
fromColumns = unHIdentity
reify Refl = Reify
unreify Refl (Reify a) = a
instance Sql DBType a => Table Result (Identity a) where
type Columns (Identity a) = HType a
type Context (Identity a) = Result
toColumns (Identity a) = HType (R a)
fromColumns (HType (R a)) = Identity a
reify = notReify
unreify = notReify
instance (Table Result a, Table Result b) => Table Result (Either a b) where
type Columns (Either a b) = HEitherTable (Columns a) (Columns b)
type Context (Either a b) = Result
toColumns = \case
Left table -> HEitherTable
{ htag = HIdentity (R IsLeft)
, hleft = hlabel labeler (hnullify nullifier (toColumns table))
, hright = hlabel labeler (hnulls (const null))
}
Right table -> HEitherTable
{ htag = HIdentity (R IsRight)
, hleft = hlabel labeler (hnulls (const null))
, hright = hlabel labeler (hnullify nullifier (toColumns table))
}
fromColumns HEitherTable {htag, hleft, hright} = case htag of
HIdentity (R tag) -> case tag of
IsLeft -> maybe err (Left . fromColumns) $ hunnullify unnullifier (hunlabel unlabeler hleft)
IsRight -> maybe err (Right . fromColumns) $ hunnullify unnullifier (hunlabel unlabeler hright)
where
err = error "Either.fromColumns: mismatch between tag and data"
instance Table Result a => Table Result [a] where
type Columns [a] = HListTable (Columns a)
type Context [a] = Result
toColumns = hvectorize vectorizer . fmap toColumns
fromColumns = fmap fromColumns . hunvectorize unvectorizer
instance Table Result a => Table Result (Maybe a) where
type Columns (Maybe a) = HMaybeTable (Columns a)
type Context (Maybe a) = Result
toColumns = \case
Nothing -> HMaybeTable
{ htag = HIdentity (R Nothing)
, hjust = hlabel labeler (hnulls (const null))
}
Just table -> HMaybeTable
{ htag = HIdentity (R (Just IsJust))
, hjust = hlabel labeler (hnullify nullifier (toColumns table))
}
fromColumns HMaybeTable {htag, hjust} = case htag of
HIdentity (R tag) -> tag $>
case hunnullify unnullifier (hunlabel unlabeler hjust) of
Nothing -> error "Maybe.fromColumns: mismatch between tag and data"
Just just -> fromColumns just
instance Table Result a => Table Result (NonEmpty a) where
type Columns (NonEmpty a) = HNonEmptyTable (Columns a)
type Context (NonEmpty a) = Result
toColumns = hvectorize vectorizer . fmap toColumns
fromColumns = fmap fromColumns . hunvectorize unvectorizer
instance (Table Result a, Table Result b) => Table Result (These a b) where
type Columns (These a b) = HTheseTable (Columns a) (Columns b)
type Context (These a b) = Result
toColumns tables = HTheseTable
{ hhereTag = relabel hhereTag
, hhere = hlabel labeler (hunlabel unlabeler (toColumns hhere))
, hthereTag = relabel hthereTag
, hthere = hlabel labeler (hunlabel unlabeler (toColumns hthere))
}
where
HMaybeTable
{ htag = hhereTag
, hjust = hhere
} = toColumns (justHere tables)
HMaybeTable
{ htag = hthereTag
, hjust = hthere
} = toColumns (justThere tables)
fromColumns HTheseTable {hhereTag, hhere, hthereTag, hthere} =
case (fromColumns mhere, fromColumns mthere) of
(Just a, Nothing) -> This (fromColumns a)
(Nothing, Just b) -> That (fromColumns b)
(Just a, Just b) -> These (fromColumns a) (fromColumns b)
_ -> error "These.fromColumns: mismatch between tags and data"
where
mhere = HMaybeTable
{ htag = relabel hhereTag
, hjust = hlabel labeler (hunlabel unlabeler hhere)
}
mthere = HMaybeTable
{ htag = relabel hthereTag
, hjust = hlabel labeler (hunlabel unlabeler hthere)
}
instance (Table context a, Table context b, Labelable context)
=> Table context (a, b)
instance
( Table context a, Table context b, Table context c
, Labelable context
)
=> Table context (a, b, c)
instance
( Table context a, Table context b, Table context c, Table context d
, Labelable context
)
=> Table context (a, b, c, d)
instance
( Table context a, Table context b, Table context c, Table context d
, Table context e
, Labelable context
)
=> Table context (a, b, c, d, e)
instance
( Table context a, Table context b, Table context c, Table context d
, Table context e, Table context f
, Labelable context
)
=> Table context (a, b, c, d, e, f)
instance
( Table context a, Table context b, Table context c, Table context d
, Table context e, Table context f, Table context g
, Labelable context
)
=> Table context (a, b, c, d, e, f, g)
type Congruent :: Type -> Type -> Constraint
class Columns a ~ Columns b => Congruent a b
instance Columns a ~ Columns b => Congruent a b