You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Implementing embedded sum types will allow more flexible mapping between types and DB. Field types like (Either Int String) or (Maybe MyEmbedded) where Maybe is treated as another embedded sum type will become possible.
We have to specify precise behavior for the following operations before starting implementation. The main obstacle for the feature is semantics of accessing subfields that have a sum type in their chain. Such subfields can appear in projections, conditions, updates, etc.
Mapping embedded sum datatype to columns:
The discriminator column and nullable columns for the fields from all constructors.
Conversion to PersistValues:
toPersistValues returns: discriminator value, NULLs for the columns in the other constructors. Note that since we cannot call toPersistValues for values in the other constructors, we must know the number of columns to pad values with nulls. To get this number we can either analyze dbType (slow) or create a new PersistField function numberOfColumns.
Creating data values:
fromPersistValues chooses constructor basing on the discriminator value, skips the NULLs for the other constructors using number of columns for their field types.
Example:
data Sum = One Int | Two String | Three (Int, String)
columns: sumDiscr INTEGER NOT NULL, one INTEGER, two VARCHAR, three#val0 INTEGER, three#val1 VARCHAR
toPersistValues (Two "abc") -> [PersistInt64 1, PersistNull, PersistString "abc", PersistNull, PersistNull]
Subfields:
Consider
data MyEntity = MyEntity {myEither :: Either Int Sum}
project (MyEither ~> LeftSelector)
project (MyEither ~> RightSelector). In this case all values of Sum including its discriminator may be NULLs.
Since any column of a embedded sum type can be NULL, the final type of selector chains must have Maybe (or another optional type), eg, SubField v c (Maybe a). Example:
MyEither ~> LeftSelector :: SubField MyEntity MyEntityConstructor (Maybe Int)
MyEither ~> RightSelector :: SubField MyEntity MyEntityConstructor (Maybe Sum)
MyEither ~> RightSelector ~> TwoSelector :: SubField MyEntity MyEntityConstructor (Maybe String)
instance Embedded (Either a b) where
data Selector (Either a b) c where
LeftSelector :: Selector v c (Maybe a)
RightSelector :: Selector v c (Maybe b)
selectorNum LeftSelector = 0
selectorNum RightSelector = 1
But in this case to access nested sum types, operator ~> should be applicable both to (emb :: f db r a) and (emb :: f db r (Maybe a)) which requires some class hackery. Also we will have nested Maybes and it is not clear how a single PersistField (Maybe a) instance can handle them.
If the fields we select are nullable themselves, we should distinguish between nulls in case when our type has different constructor and when constructor matches, but field is null. Either Int (Maybe Int). To do this we may select the discriminator column for the innermost embedded type. But in this case the number of columns in comparison expressions will not match.
Another approach for projection is to use another subfield type for a chain that contains a sum type. A separate Projection instance will add Maybe. Something like (Projection (SumTypeSubField a) db r (Maybe a)).
The text was updated successfully, but these errors were encountered:
I think it would be a useful feature but it has many cases where behavior is undefined. For example when we try to access a value inside of an embedded type created with a different constructor:
k <- insert $ MyEntity $ Right (One 1)
update [MyEither ~> LeftSelector =. 1] $ AutoKeyField ==. k
Implementing embedded sum types will allow more flexible mapping between types and DB. Field types like
(Either Int String)
or(Maybe MyEmbedded)
where Maybe is treated as another embedded sum type will become possible.We have to specify precise behavior for the following operations before starting implementation. The main obstacle for the feature is semantics of accessing subfields that have a sum type in their chain. Such subfields can appear in projections, conditions, updates, etc.
Mapping embedded sum datatype to columns:
The discriminator column and nullable columns for the fields from all constructors.
Conversion to PersistValues:
toPersistValues returns: discriminator value, NULLs for the columns in the other constructors. Note that since we cannot call toPersistValues for values in the other constructors, we must know the number of columns to pad values with nulls. To get this number we can either analyze dbType (slow) or create a new PersistField function numberOfColumns.
Creating data values:
fromPersistValues chooses constructor basing on the discriminator value, skips the NULLs for the other constructors using number of columns for their field types.
Example:
data Sum = One Int | Two String | Three (Int, String)
columns: sumDiscr INTEGER NOT NULL, one INTEGER, two VARCHAR, three#val0 INTEGER, three#val1 VARCHAR
toPersistValues (Two "abc") -> [PersistInt64 1, PersistNull, PersistString "abc", PersistNull, PersistNull]
Subfields:
Consider
data MyEntity = MyEntity {myEither :: Either Int Sum}
project (MyEither ~> LeftSelector)
project (MyEither ~> RightSelector). In this case all values of Sum including its discriminator may be NULLs.
Since any column of a embedded sum type can be NULL, the final type of selector chains must have Maybe (or another optional type), eg, SubField v c (Maybe a). Example:
MyEither ~> LeftSelector :: SubField MyEntity MyEntityConstructor (Maybe Int)
MyEither ~> RightSelector :: SubField MyEntity MyEntityConstructor (Maybe Sum)
MyEither ~> RightSelector ~> TwoSelector :: SubField MyEntity MyEntityConstructor (Maybe String)
instance Embedded (Either a b) where
data Selector (Either a b) c where
LeftSelector :: Selector v c (Maybe a)
RightSelector :: Selector v c (Maybe b)
selectorNum LeftSelector = 0
selectorNum RightSelector = 1
But in this case to access nested sum types, operator ~> should be applicable both to (emb :: f db r a) and (emb :: f db r (Maybe a)) which requires some class hackery. Also we will have nested Maybes and it is not clear how a single PersistField (Maybe a) instance can handle them.
If the fields we select are nullable themselves, we should distinguish between nulls in case when our type has different constructor and when constructor matches, but field is null. Either Int (Maybe Int). To do this we may select the discriminator column for the innermost embedded type. But in this case the number of columns in comparison expressions will not match.
Another approach for projection is to use another subfield type for a chain that contains a sum type. A separate Projection instance will add Maybe. Something like (Projection (SumTypeSubField a) db r (Maybe a)).
The text was updated successfully, but these errors were encountered: