module Database.Beam.Schema.Tables
(
Database
, zipTables
, DatabaseSettings
, IsDatabaseEntity(..)
, DatabaseEntityDescriptor(..)
, DatabaseEntity(..), TableEntity, ViewEntity, DomainTypeEntity
, dbEntityDescriptor
, DatabaseModification, EntityModification(..)
, FieldModification(..)
, dbModification, tableModification, withDbModification
, withTableModification, modifyTable, fieldNamed
, defaultDbSettings
, RenamableWithRule(..), RenamableField(..)
, FieldRenamer(..)
, Lenses, LensFor(..)
, Columnar, C, Columnar'(..)
, Nullable, TableField(..)
, Exposed
, fieldName
, TableSettings, HaskellTable
, TableSkeleton, Ignored(..)
, GFieldsFulfillConstraint(..), FieldsFulfillConstraint
, FieldsFulfillConstraintNullable
, WithConstraint(..)
, TagReducesTo(..), ReplaceBaseTag
, Table(..), Beamable(..)
, Retaggable(..)
, defTblFieldSettings
, tableValuesNeeded
, pk
, allBeamValues, changeBeamRep )
where
import Database.Beam.Backend.Types
import Control.Arrow (first)
import Control.Monad.Identity
import Control.Monad.Writer
import Data.Char (isUpper, toLower)
import Data.Monoid ((<>))
import Data.Proxy
import Data.String (IsString(..))
import Data.Text (Text)
import qualified Data.Text as T
import Data.Typeable
import qualified GHC.Generics as Generic
import GHC.Generics hiding (R, C)
import GHC.TypeLits
import GHC.Types
import Lens.Micro hiding (to)
import qualified Lens.Micro as Lens
class Database db where
zipTables :: Monad m
=> Proxy be
-> (forall tbl. (IsDatabaseEntity be tbl, DatabaseEntityRegularRequirements be tbl) =>
f tbl -> g tbl -> m (h tbl))
-> db f -> db g -> m (db h)
default zipTables :: ( Generic (db f), Generic (db g), Generic (db h)
, Monad m
, GZipDatabase be f g h
(Rep (db f)) (Rep (db g)) (Rep (db h)) ) =>
Proxy be ->
(forall tbl. (IsDatabaseEntity be tbl, DatabaseEntityRegularRequirements be tbl) => f tbl -> g tbl -> m (h tbl)) ->
db f -> db g -> m (db h)
zipTables be combine (f :: db f) (g :: db g) =
refl $ \h ->
to <$> gZipDatabase (Proxy @f, Proxy @g, h, be) combine (from f) (from g)
where
refl :: (Proxy h -> m (db h)) -> m (db h)
refl fn = fn Proxy
defaultDbSettings :: ( Generic (DatabaseSettings be db)
, GAutoDbSettings (Rep (DatabaseSettings be db) ()) ) =>
DatabaseSettings be db
defaultDbSettings = to' autoDbSettings'
type DatabaseModification f be db = db (EntityModification f be)
newtype EntityModification f be e = EntityModification (f e -> f e)
newtype FieldModification f a
= FieldModification (Columnar f a -> Columnar f a)
dbModification :: forall f be db. Database db => DatabaseModification f be db
dbModification = runIdentity $
zipTables (Proxy @be) (\_ _ -> pure (EntityModification id)) (undefined :: DatabaseModification f be db) (undefined :: DatabaseModification f be db)
tableModification :: forall f tbl. Beamable tbl => tbl (FieldModification f)
tableModification = runIdentity $
zipBeamFieldsM (\(Columnar' _ :: Columnar' Ignored x) (Columnar' _ :: Columnar' Ignored x) ->
pure (Columnar' (FieldModification id :: FieldModification f x))) (undefined :: TableSkeleton tbl) (undefined :: TableSkeleton tbl)
withDbModification :: forall db be entity
. Database db
=> db (entity be db)
-> DatabaseModification (entity be db) be db
-> db (entity be db)
withDbModification db mods =
runIdentity $ zipTables (Proxy @be) (\tbl (EntityModification entityFn) -> pure (entityFn tbl)) db mods
withTableModification :: Beamable tbl => tbl (FieldModification f) -> tbl f -> tbl f
withTableModification mods tbl =
runIdentity $ zipBeamFieldsM (\(Columnar' field :: Columnar' f a) (Columnar' (FieldModification fieldFn :: FieldModification f a)) ->
pure (Columnar' (fieldFn field))) tbl mods
modifyTable :: (Text -> Text)
-> tbl (FieldModification (TableField tbl))
-> EntityModification (DatabaseEntity be db) be (TableEntity tbl)
modifyTable modTblNm modFields =
EntityModification (\(DatabaseEntity (DatabaseTable nm fields)) ->
(DatabaseEntity (DatabaseTable (modTblNm nm) (withTableModification modFields fields))))
fieldNamed :: Text -> FieldModification (TableField tbl) a
fieldNamed newName = FieldModification (\_ -> TableField newName)
newtype FieldRenamer entity = FieldRenamer { withFieldRenamer :: entity -> entity }
class RenamableField f where
renameField :: Proxy f -> Proxy a -> (Text -> Text) -> Columnar f a -> Columnar f a
instance RenamableField (TableField tbl) where
renameField _ _ f (TableField nm) = TableField (f nm)
class RenamableWithRule mod where
renamingFields :: (Text -> Text) -> mod
instance Database db => RenamableWithRule (db (EntityModification (DatabaseEntity be db) be)) where
renamingFields renamer =
runIdentity $
zipTables (Proxy @be) (\_ _ -> pure (renamingFields renamer))
(undefined :: DatabaseModification f be db)
(undefined :: DatabaseModification f be db)
instance IsDatabaseEntity be entity => RenamableWithRule (EntityModification (DatabaseEntity be db) be entity) where
renamingFields renamer =
EntityModification (\(DatabaseEntity tbl) -> DatabaseEntity (withFieldRenamer (renamingFields renamer) tbl))
instance (Beamable tbl, RenamableField f) => RenamableWithRule (tbl (FieldModification f)) where
renamingFields renamer =
runIdentity $
zipBeamFieldsM (\(Columnar' _ :: Columnar' Ignored x) (Columnar' _ :: Columnar' Ignored x) ->
pure (Columnar' (FieldModification (renameField (Proxy @f) (Proxy @x) renamer) :: FieldModification f x) ::
Columnar' (FieldModification f) x))
(undefined :: TableSkeleton tbl) (undefined :: TableSkeleton tbl)
instance IsString (FieldModification (TableField tbl) a) where
fromString = fieldNamed . fromString
data TableEntity (tbl :: (* -> *) -> *)
data ViewEntity (view :: (* -> *) -> *)
data DomainTypeEntity (ty :: *)
class RenamableWithRule (FieldRenamer (DatabaseEntityDescriptor be entityType)) =>
IsDatabaseEntity be entityType where
data DatabaseEntityDescriptor be entityType :: *
type DatabaseEntityDefaultRequirements be entityType :: Constraint
type DatabaseEntityRegularRequirements be entityType :: Constraint
dbEntityName :: Lens' (DatabaseEntityDescriptor be entityType) Text
dbEntityAuto :: DatabaseEntityDefaultRequirements be entityType =>
Text -> DatabaseEntityDescriptor be entityType
instance Beamable tbl => RenamableWithRule (FieldRenamer (DatabaseEntityDescriptor be (TableEntity tbl))) where
renamingFields renamer =
FieldRenamer $ \(DatabaseTable tblName fields) ->
DatabaseTable tblName $
changeBeamRep (\(Columnar' tblField :: Columnar' (TableField tbl) a) ->
Columnar' (renameField (Proxy @(TableField tbl)) (Proxy @a) renamer tblField) :: Columnar' (TableField tbl) a) $
fields
instance Beamable tbl => IsDatabaseEntity be (TableEntity tbl) where
data DatabaseEntityDescriptor be (TableEntity tbl) where
DatabaseTable :: Table tbl => Text -> TableSettings tbl -> DatabaseEntityDescriptor be (TableEntity tbl)
type DatabaseEntityDefaultRequirements be (TableEntity tbl) =
( GDefaultTableFieldSettings (Rep (TableSettings tbl) ())
, Generic (TableSettings tbl), Table tbl, Beamable tbl )
type DatabaseEntityRegularRequirements be (TableEntity tbl) =
( Table tbl, Beamable tbl )
dbEntityName f (DatabaseTable t s) = fmap (\t' -> DatabaseTable t' s) (f t)
dbEntityAuto nm =
DatabaseTable (unCamelCaseSel nm) defTblFieldSettings
instance Beamable tbl => RenamableWithRule (FieldRenamer (DatabaseEntityDescriptor be (ViewEntity tbl))) where
renamingFields renamer =
FieldRenamer $ \(DatabaseView tblName fields) ->
DatabaseView tblName $
changeBeamRep (\(Columnar' tblField :: Columnar' (TableField tbl) a) ->
Columnar' (renameField (Proxy @(TableField tbl)) (Proxy @a) renamer tblField) :: Columnar' (TableField tbl) a) $
fields
instance Beamable tbl => IsDatabaseEntity be (ViewEntity tbl) where
data DatabaseEntityDescriptor be (ViewEntity tbl) where
DatabaseView :: Text -> TableSettings tbl -> DatabaseEntityDescriptor be (ViewEntity tbl)
type DatabaseEntityDefaultRequirements be (ViewEntity tbl) =
( GDefaultTableFieldSettings (Rep (TableSettings tbl) ())
, Generic (TableSettings tbl), Beamable tbl )
type DatabaseEntityRegularRequirements be (ViewEntity tbl) =
( Beamable tbl )
dbEntityName f (DatabaseView t s) = fmap (\t' -> DatabaseView t' s) (f t)
dbEntityAuto nm =
DatabaseView (unCamelCaseSel nm) defTblFieldSettings
instance RenamableWithRule (FieldRenamer (DatabaseEntityDescriptor be (DomainTypeEntity ty))) where
renamingFields _ = FieldRenamer id
instance IsDatabaseEntity be (DomainTypeEntity ty) where
data DatabaseEntityDescriptor be (DomainTypeEntity ty)
= DatabaseDomainType !Text
type DatabaseEntityDefaultRequirements be (DomainTypeEntity ty) = ()
type DatabaseEntityRegularRequirements be (DomainTypeEntity ty) = ()
dbEntityName f (DatabaseDomainType t) = DatabaseDomainType <$> f t
dbEntityAuto = DatabaseDomainType
data DatabaseEntity be (db :: (* -> *) -> *) entityType where
DatabaseEntity ::
IsDatabaseEntity be entityType =>
DatabaseEntityDescriptor be entityType -> DatabaseEntity be db entityType
dbEntityDescriptor :: SimpleGetter (DatabaseEntity be db entityType) (DatabaseEntityDescriptor be entityType)
dbEntityDescriptor = Lens.to (\(DatabaseEntity e) -> e)
type DatabaseSettings be db = db (DatabaseEntity be db)
class GAutoDbSettings x where
autoDbSettings' :: x
instance GAutoDbSettings (x p) => GAutoDbSettings (D1 f x p) where
autoDbSettings' = M1 autoDbSettings'
instance GAutoDbSettings (x p) => GAutoDbSettings (C1 f x p) where
autoDbSettings' = M1 autoDbSettings'
instance (GAutoDbSettings (x p), GAutoDbSettings (y p)) => GAutoDbSettings ((x :*: y) p) where
autoDbSettings' = autoDbSettings' :*: autoDbSettings'
instance ( Selector f, IsDatabaseEntity be x, DatabaseEntityDefaultRequirements be x ) =>
GAutoDbSettings (S1 f (K1 Generic.R (DatabaseEntity be db x)) p) where
autoDbSettings' = M1 (K1 (DatabaseEntity (dbEntityAuto name)))
where name = T.pack (selName (undefined :: S1 f (K1 Generic.R (DatabaseEntity be db x)) p))
class GZipDatabase be f g h x y z where
gZipDatabase :: Monad m =>
(Proxy f, Proxy g, Proxy h, Proxy be)
-> (forall tbl. (IsDatabaseEntity be tbl, DatabaseEntityRegularRequirements be tbl) => f tbl -> g tbl -> m (h tbl))
-> x () -> y () -> m (z ())
instance GZipDatabase be f g h x y z =>
GZipDatabase be f g h (M1 a b x) (M1 a b y) (M1 a b z) where
gZipDatabase p combine ~(M1 f) ~(M1 g) = M1 <$> gZipDatabase p combine f g
instance ( GZipDatabase be f g h ax ay az
, GZipDatabase be f g h bx by bz ) =>
GZipDatabase be f g h (ax :*: bx) (ay :*: by) (az :*: bz) where
gZipDatabase p combine ~(ax :*: bx) ~(ay :*: by) =
do a <- gZipDatabase p combine ax ay
b <- gZipDatabase p combine bx by
pure (a :*: b)
instance (IsDatabaseEntity be tbl, DatabaseEntityRegularRequirements be tbl) =>
GZipDatabase be f g h (K1 Generic.R (f tbl)) (K1 Generic.R (g tbl)) (K1 Generic.R (h tbl)) where
gZipDatabase _ combine ~(K1 x) ~(K1 y) =
K1 <$> combine x y
data Lenses (t :: (* -> *) -> *) (f :: * -> *) x
data LensFor t x where
LensFor :: Generic t => Lens' t x -> LensFor t x
type family Columnar (f :: * -> *) x where
Columnar Exposed x = Exposed x
Columnar Identity x = x
Columnar (Lenses t f) x = LensFor (t f) (Columnar f x)
Columnar (Nullable c) x = Columnar c (Maybe x)
Columnar f x = f x
type C f a = Columnar f a
newtype Columnar' f a = Columnar' (Columnar f a)
data TableField (table :: (* -> *) -> *) ty
= TableField
{ _fieldName :: Text
} deriving (Show, Eq)
fieldName :: Lens' (TableField table ty) Text
fieldName f (TableField name) = TableField <$> f name
type TableSettings table = table (TableField table)
type HaskellTable table = table Identity
data Ignored x = Ignored
type TableSkeleton table = table Ignored
from' :: Generic x => x -> Rep x ()
from' = from
to' :: Generic x => Rep x () -> x
to' = to
type HasBeamFields table f g h = ( GZipTables f g h (Rep (table Exposed)) (Rep (table f)) (Rep (table g)) (Rep (table h))
, Generic (table f), Generic (table g), Generic (table h) )
class (Typeable table, Beamable table, Beamable (PrimaryKey table)) => Table (table :: (* -> *) -> *) where
data PrimaryKey table (column :: * -> *) :: *
primaryKey :: table column -> PrimaryKey table column
class Beamable table where
zipBeamFieldsM :: Applicative m =>
(forall a. Columnar' f a -> Columnar' g a -> m (Columnar' h a)) -> table f -> table g -> m (table h)
default zipBeamFieldsM :: ( HasBeamFields table f g h
, Applicative m ) =>
(forall a. Columnar' f a -> Columnar' g a -> m (Columnar' h a)) -> table f -> table g -> m (table h)
zipBeamFieldsM combine (f :: table f) g =
to' <$> gZipTables (Proxy :: Proxy (Rep (table Exposed))) combine (from' f) (from' g)
tblSkeleton :: TableSkeleton table
default tblSkeleton :: ( Generic (TableSkeleton table)
, GTableSkeleton (Rep (TableSkeleton table)) ) => TableSkeleton table
tblSkeleton = withProxy $ \proxy -> to' (gTblSkeleton proxy)
where withProxy :: (Proxy (Rep (TableSkeleton table)) -> TableSkeleton table) -> TableSkeleton table
withProxy f = f Proxy
tableValuesNeeded :: Beamable table => Proxy table -> Int
tableValuesNeeded (Proxy :: Proxy table) = length (allBeamValues (const ()) (tblSkeleton :: TableSkeleton table))
allBeamValues :: Beamable table => (forall a. Columnar' f a -> b) -> table f -> [b]
allBeamValues (f :: forall a. Columnar' f a -> b) (tbl :: table f) =
execWriter (zipBeamFieldsM combine tbl tbl)
where combine :: Columnar' f a -> Columnar' f a -> Writer [b] (Columnar' f a)
combine x _ = do tell [f x]
return x
changeBeamRep :: Beamable table => (forall a. Columnar' f a -> Columnar' g a) -> table f -> table g
changeBeamRep f tbl = runIdentity (zipBeamFieldsM (\x _ -> return (f x)) tbl tbl)
class Retaggable f x | x -> f where
type Retag (tag :: (* -> *) -> * -> *) x :: *
retag :: (forall a. Columnar' f a -> Columnar' (tag f) a) -> x
-> Retag tag x
instance Beamable tbl => Retaggable f (tbl (f :: * -> *)) where
type Retag tag (tbl f) = tbl (tag f)
retag = changeBeamRep
instance (Retaggable f a, Retaggable f b) => Retaggable f (a, b) where
type Retag tag (a, b) = (Retag tag a, Retag tag b)
retag transform (a, b) = (retag transform a, retag transform b)
instance (Retaggable f a, Retaggable f b, Retaggable f c) =>
Retaggable f (a, b, c) where
type Retag tag (a, b, c) = (Retag tag a, Retag tag b, Retag tag c)
retag transform (a, b, c) = (retag transform a, retag transform b, retag transform c)
instance (Retaggable f a, Retaggable f b, Retaggable f c, Retaggable f d) =>
Retaggable f (a, b, c, d) where
type Retag tag (a, b, c, d) =
(Retag tag a, Retag tag b, Retag tag c, Retag tag d)
retag transform (a, b, c, d) =
(retag transform a, retag transform b, retag transform c, retag transform d)
instance ( Retaggable f a, Retaggable f b, Retaggable f c, Retaggable f d
, Retaggable f e ) =>
Retaggable f (a, b, c, d, e) where
type Retag tag (a, b, c, d, e) =
(Retag tag a, Retag tag b, Retag tag c, Retag tag d, Retag tag e)
retag transform (a, b, c, d, e) =
( retag transform a, retag transform b, retag transform c, retag transform d
, retag transform e)
instance ( Retaggable f' a, Retaggable f' b, Retaggable f' c, Retaggable f' d
, Retaggable f' e, Retaggable f' f ) =>
Retaggable f' (a, b, c, d, e, f) where
type Retag tag (a, b, c, d, e, f) =
( Retag tag a, Retag tag b, Retag tag c, Retag tag d
, Retag tag e, Retag tag f)
retag transform (a, b, c, d, e, f) =
( retag transform a, retag transform b, retag transform c, retag transform d
, retag transform e, retag transform f )
instance ( Retaggable f' a, Retaggable f' b, Retaggable f' c, Retaggable f' d
, Retaggable f' e, Retaggable f' f, Retaggable f' g ) =>
Retaggable f' (a, b, c, d, e, f, g) where
type Retag tag (a, b, c, d, e, f, g) =
( Retag tag a, Retag tag b, Retag tag c, Retag tag d
, Retag tag e, Retag tag f, Retag tag g )
retag transform (a, b, c, d, e, f, g) =
( retag transform a, retag transform b, retag transform c, retag transform d
, retag transform e, retag transform f, retag transform g )
instance ( Retaggable f' a, Retaggable f' b, Retaggable f' c, Retaggable f' d
, Retaggable f' e, Retaggable f' f, Retaggable f' g, Retaggable f' h ) =>
Retaggable f' (a, b, c, d, e, f, g, h) where
type Retag tag (a, b, c, d, e, f, g, h) =
( Retag tag a, Retag tag b, Retag tag c, Retag tag d
, Retag tag e, Retag tag f, Retag tag g, Retag tag h )
retag transform (a, b, c, d, e, f, g, h) =
( retag transform a, retag transform b, retag transform c, retag transform d
, retag transform e, retag transform f, retag transform g, retag transform h )
data WithConstraint (c :: * -> Constraint) x where
WithConstraint :: c x => x -> WithConstraint c x
class GFieldsFulfillConstraint (c :: * -> Constraint) (exposed :: * -> *) values withconstraint where
gWithConstrainedFields :: Proxy c -> Proxy exposed -> values () -> withconstraint ()
instance GFieldsFulfillConstraint c exposed values withconstraint =>
GFieldsFulfillConstraint c (M1 s m exposed) (M1 s m values) (M1 s m withconstraint) where
gWithConstrainedFields c _ (M1 x) = M1 (gWithConstrainedFields c (Proxy @exposed) x)
instance GFieldsFulfillConstraint c U1 U1 U1 where
gWithConstrainedFields _ _ _ = U1
instance (GFieldsFulfillConstraint c aExp a aC, GFieldsFulfillConstraint c bExp b bC) =>
GFieldsFulfillConstraint c (aExp :*: bExp) (a :*: b) (aC :*: bC) where
gWithConstrainedFields be _ (a :*: b) = gWithConstrainedFields be (Proxy @aExp) a :*: gWithConstrainedFields be (Proxy @bExp) b
instance (c x) => GFieldsFulfillConstraint c (K1 Generic.R (Exposed x)) (K1 Generic.R x) (K1 Generic.R (WithConstraint c x)) where
gWithConstrainedFields _ _ (K1 x) = K1 (WithConstraint x)
instance FieldsFulfillConstraint c t =>
GFieldsFulfillConstraint c (K1 Generic.R (t Exposed)) (K1 Generic.R (t Identity)) (K1 Generic.R (t (WithConstraint c))) where
gWithConstrainedFields _ _ (K1 x) = K1 (to (gWithConstrainedFields (Proxy @c) (Proxy @(Rep (t Exposed))) (from x)))
instance FieldsFulfillConstraintNullable c t =>
GFieldsFulfillConstraint c (K1 Generic.R (t (Nullable Exposed))) (K1 Generic.R (t (Nullable Identity))) (K1 Generic.R (t (Nullable (WithConstraint c)))) where
gWithConstrainedFields _ _ (K1 x) = K1 (to (gWithConstrainedFields (Proxy @c) (Proxy @(Rep (t (Nullable Exposed)))) (from x)))
type FieldsFulfillConstraint (c :: * -> Constraint) t =
( Generic (t (WithConstraint c)), Generic (t Identity), Generic (t Exposed)
, GFieldsFulfillConstraint c (Rep (t Exposed)) (Rep (t Identity)) (Rep (t (WithConstraint c))))
type FieldsFulfillConstraintNullable (c :: * -> Constraint) t =
( Generic (t (Nullable (WithConstraint c))), Generic (t (Nullable Identity)), Generic (t (Nullable Exposed))
, GFieldsFulfillConstraint c (Rep (t (Nullable Exposed))) (Rep (t (Nullable Identity))) (Rep (t (Nullable (WithConstraint c)))))
pk :: Table t => t f -> PrimaryKey t f
pk = primaryKey
defTblFieldSettings :: ( Generic (TableSettings table)
, GDefaultTableFieldSettings (Rep (TableSettings table) ())) =>
TableSettings table
defTblFieldSettings = withProxy $ \proxy -> to' (gDefTblFieldSettings proxy)
where withProxy :: (Proxy (Rep (TableSettings table) ()) -> TableSettings table) -> TableSettings table
withProxy f = f Proxy
class GZipTables f g h (exposedRep :: * -> *) fRep gRep hRep where
gZipTables :: Applicative m => Proxy exposedRep -> (forall a. Columnar' f a -> Columnar' g a -> m (Columnar' h a)) -> fRep () -> gRep () -> m (hRep ())
instance ( GZipTables f g h exp1 f1 g1 h1
, GZipTables f g h exp2 f2 g2 h2) =>
GZipTables f g h (exp1 :*: exp2) (f1 :*: f2) (g1 :*: g2) (h1 :*: h2) where
gZipTables _ combine ~(f1 :*: f2) ~(g1 :*: g2) =
(:*:) <$> gZipTables (Proxy :: Proxy exp1) combine f1 g1
<*> gZipTables (Proxy :: Proxy exp2) combine f2 g2
instance GZipTables f g h exp fRep gRep hRep =>
GZipTables f g h (M1 x y exp) (M1 x y fRep) (M1 x y gRep) (M1 x y hRep) where
gZipTables _ combine ~(M1 f) ~(M1 g) = M1 <$> gZipTables (Proxy :: Proxy exp) combine f g
instance ( fa ~ Columnar f a
, ga ~ Columnar g a
, ha ~ Columnar h a) =>
GZipTables f g h (K1 Generic.R (Exposed a)) (K1 Generic.R fa) (K1 Generic.R ga) (K1 Generic.R ha) where
gZipTables _ combine ~(K1 f) ~(K1 g) = (\(Columnar' h) -> K1 h) <$> combine (Columnar' f :: Columnar' f a) (Columnar' g :: Columnar' g a)
instance ( Generic (tbl f)
, Generic (tbl g)
, Generic (tbl h)
, GZipTables f g h (Rep (tbl Exposed)) (Rep (tbl f)) (Rep (tbl g)) (Rep (tbl h))) =>
GZipTables f g h (K1 Generic.R (tbl Exposed)) (K1 Generic.R (tbl f)) (K1 Generic.R (tbl g)) (K1 Generic.R (tbl h)) where
gZipTables _ combine ~(K1 f) ~(K1 g) = K1 . to' <$> gZipTables (Proxy :: Proxy (Rep (tbl Exposed))) combine (from' f) (from' g)
instance GZipTables f g h U1 U1 U1 U1 where
gZipTables _ _ _ _ = pure U1
instance ( Generic (tbl (Nullable f))
, Generic (tbl (Nullable g))
, Generic (tbl (Nullable h))
, GZipTables f g h (Rep (tbl (Nullable Exposed))) (Rep (tbl (Nullable f))) (Rep (tbl (Nullable g))) (Rep (tbl (Nullable h)))) =>
GZipTables f g h
(K1 Generic.R (tbl (Nullable Exposed)))
(K1 Generic.R (tbl (Nullable f)))
(K1 Generic.R (tbl (Nullable g)))
(K1 Generic.R (tbl (Nullable h))) where
gZipTables _ combine ~(K1 f) ~(K1 g) = K1 . to' <$> gZipTables (Proxy :: Proxy (Rep (tbl (Nullable Exposed)))) combine (from' f) (from' g)
class GDefaultTableFieldSettings x where
gDefTblFieldSettings :: Proxy x -> x
instance GDefaultTableFieldSettings (p x) => GDefaultTableFieldSettings (D1 f p x) where
gDefTblFieldSettings (_ :: Proxy (D1 f p x)) = M1 $ gDefTblFieldSettings (Proxy :: Proxy (p x))
instance GDefaultTableFieldSettings (p x) => GDefaultTableFieldSettings (C1 f p x) where
gDefTblFieldSettings (_ :: Proxy (C1 f p x)) = M1 $ gDefTblFieldSettings (Proxy :: Proxy (p x))
instance (GDefaultTableFieldSettings (a p), GDefaultTableFieldSettings (b p)) => GDefaultTableFieldSettings ((a :*: b) p) where
gDefTblFieldSettings (_ :: Proxy ((a :*: b) p)) = gDefTblFieldSettings (Proxy :: Proxy (a p)) :*: gDefTblFieldSettings (Proxy :: Proxy (b p))
instance Selector f =>
GDefaultTableFieldSettings (S1 f (K1 Generic.R (TableField table field)) p) where
gDefTblFieldSettings (_ :: Proxy (S1 f (K1 Generic.R (TableField table field)) p)) = M1 (K1 s)
where s = TableField name
name = unCamelCaseSel (T.pack (selName (undefined :: S1 f (K1 Generic.R (TableField table field)) ())))
instance ( TypeError ('Text "All Beamable types must be record types, so appropriate names can be given to columns")) => GDefaultTableFieldSettings (K1 r f p) where
gDefTblFieldSettings _ = error "impossible"
data SubTableStrategy
= PrimaryKeyStrategy
| BeamableStrategy
| RecursiveKeyStrategy
type family ChooseSubTableStrategy (tbl :: (* -> *) -> *) (sub :: (* -> *) -> *) :: SubTableStrategy where
ChooseSubTableStrategy tbl (PrimaryKey tbl) = 'RecursiveKeyStrategy
ChooseSubTableStrategy tbl (PrimaryKey rel) = 'PrimaryKeyStrategy
ChooseSubTableStrategy tbl sub = 'BeamableStrategy
type family CheckNullable (f :: * -> *) :: Constraint where
CheckNullable (Nullable f) = ()
CheckNullable f = TypeError ('Text "Recursive reference without Nullable constraint forms an infinite loop." ':$$:
'Text "Hint: Only embed nullable 'PrimaryKey tbl' within the definition of 'tbl'." ':$$:
'Text " For example, replace 'PrimaryKey tbl f' with 'PrimaryKey tbl (Nullable f)'")
class SubTableStrategyImpl (strategy :: SubTableStrategy) (f :: * -> *) sub where
namedSubTable :: Proxy strategy -> sub f
instance ( Table rel, Generic (rel (TableField rel))
, TagReducesTo f (TableField tbl)
, GDefaultTableFieldSettings (Rep (rel (TableField rel)) ()) ) =>
SubTableStrategyImpl 'PrimaryKeyStrategy f (PrimaryKey rel) where
namedSubTable _ = primaryKey tbl
where tbl = changeBeamRep (\(Columnar' (TableField nm) :: Columnar' (TableField rel) a) ->
let c = Columnar' (TableField nm) :: Columnar' (TableField tbl) a
in runIdentity (reduceTag (\_ -> pure c) undefined)) $
to' $ gDefTblFieldSettings (Proxy @(Rep (rel (TableField rel)) ()))
instance ( Generic (sub f)
, GDefaultTableFieldSettings (Rep (sub f) ()) ) =>
SubTableStrategyImpl 'BeamableStrategy f sub where
namedSubTable _ = to' $ gDefTblFieldSettings (Proxy @(Rep (sub f) ()))
instance ( CheckNullable f, SubTableStrategyImpl 'PrimaryKeyStrategy f (PrimaryKey rel) ) =>
SubTableStrategyImpl 'RecursiveKeyStrategy f (PrimaryKey rel) where
namedSubTable _ = namedSubTable (Proxy @'PrimaryKeyStrategy)
instance
( Selector f'
, ChooseSubTableStrategy tbl sub ~ strategy
, SubTableStrategyImpl strategy f sub
, TagReducesTo f (TableField tbl)
, Beamable sub ) =>
GDefaultTableFieldSettings (S1 f' (K1 Generic.R (sub f)) p) where
gDefTblFieldSettings _ = M1 . K1 $ settings'
where tbl :: sub f
tbl = namedSubTable (Proxy @strategy)
relName = unCamelCaseSel (T.pack (selName (undefined :: S1 f' (K1 Generic.R (sub f)) p)))
settings' :: sub f
settings' = changeBeamRep (reduceTag %~ \(Columnar' (TableField nm)) -> Columnar' (TableField (relName <> "__" <> nm))) tbl
type family ReplaceBaseTag tag f where
ReplaceBaseTag tag (Nullable f) = Nullable (ReplaceBaseTag tag f)
ReplaceBaseTag tag x = tag
class TagReducesTo f f' | f -> f' where
reduceTag :: Functor m =>
(Columnar' f' a' -> m (Columnar' f' a'))
-> Columnar' f a -> m (Columnar' f a)
instance TagReducesTo (TableField tbl) (TableField tbl) where
reduceTag f ~(Columnar' (TableField nm)) =
(\(Columnar' (TableField nm')) -> Columnar' (TableField nm')) <$>
f (Columnar' (TableField nm))
instance TagReducesTo f f' => TagReducesTo (Nullable f) f' where
reduceTag fn ~(Columnar' x :: Columnar' (Nullable f) a) =
(\(Columnar' x' :: Columnar' f (Maybe a')) -> Columnar' x') <$>
reduceTag fn (Columnar' x :: Columnar' f (Maybe a))
class GTableSkeleton x where
gTblSkeleton :: Proxy x -> x ()
instance GTableSkeleton p => GTableSkeleton (M1 t f p) where
gTblSkeleton (_ :: Proxy (M1 t f p)) = M1 (gTblSkeleton (Proxy :: Proxy p))
instance GTableSkeleton U1 where
gTblSkeleton _ = U1
instance (GTableSkeleton a, GTableSkeleton b) =>
GTableSkeleton (a :*: b) where
gTblSkeleton _ = gTblSkeleton (Proxy :: Proxy a) :*: gTblSkeleton (Proxy :: Proxy b)
instance GTableSkeleton (K1 Generic.R (Ignored field)) where
gTblSkeleton _ = K1 Ignored
instance ( Generic (tbl Ignored)
, GTableSkeleton (Rep (tbl Ignored)) ) =>
GTableSkeleton (K1 Generic.R (tbl Ignored)) where
gTblSkeleton _ = K1 (to' (gTblSkeleton (Proxy :: Proxy (Rep (tbl Ignored)))))
instance ( Generic (tbl (Nullable Ignored))
, GTableSkeleton (Rep (tbl (Nullable Ignored))) ) =>
GTableSkeleton (K1 Generic.R (tbl (Nullable Ignored))) where
gTblSkeleton _ = K1 (to' (gTblSkeleton (Proxy :: Proxy (Rep (tbl (Nullable Ignored))))))
unCamelCase :: T.Text -> [T.Text]
unCamelCase "" = []
unCamelCase s
| (comp, next) <- T.break isUpper s, not (T.null comp) =
let next' = maybe mempty (uncurry T.cons . first toLower) (T.uncons next)
in T.toLower comp:unCamelCase next'
| otherwise =
let (comp, next) = T.span isUpper s
next' = maybe mempty (uncurry T.cons . first toLower) (T.uncons next)
in T.toLower comp:unCamelCase next'
unCamelCaseSel :: Text -> Text
unCamelCaseSel original =
let symbolLeft = T.dropWhile (=='_') original
in if T.null symbolLeft
then original
else if T.any (=='_') symbolLeft
then symbolLeft
else case unCamelCase symbolLeft of
[] -> symbolLeft
[xs] -> xs
_:xs -> T.intercalate "_" xs