Typical ORMs map certain data types to tuples within a relation variable. Because Project:M36 supports Haskell data types natively, any record data type which derives the Generic
typeclass can be marshaled to a database tuple using the Tupleable
typeclass. These marshaled database values can be operated on within the database using Haskell as well.
Let's imagine we have a Haskell record type "Person" which we wish to map to a tuple in a relation variable called "person". Using GHC.Generics
, creating this mapping is trivial. Any record fields which are Atomable
, including algebraic data types, are supported.
import ProjectM36.Tupleable
data Person = Person {
name :: Text,
employeeNumber :: Int,
likesHorses :: Bool
}
deriving Generic
instance Tupleable Person
Now, Person
values can be marshaled between the database and the client. The attribute names appearing in the Haskell data type will appear identically in the server with the same types.
If our field names don't exactly match attribute names, but follow a common pattern, we can derive Tupleable with custom options. Let's say each field on person has a prefix "person", but we still want to use the attribute names without the prefix. We can make use of the ProjectM36.Tupleable module to accomplish our goal like so:
import ProjectM36.Tupleable.Deriving
data Person = Person {
personName :: Text,
personEmployeeNumber :: Int,
personLikesHorses :: Bool
}
deriving stock Generic
deriving Tupleable
via Codec (Field (DropPrefix "person" >>> CamelCase)) Person
The extensions DerivingGeneric, DerivingVia, TypeOperators, and DataKinds must be enabled for this code to compile. See the documentation on Hackage for all available options.
The Tupleable
module includes the following functions:
toInsertExpr :: forall a t. (Tupleable a, Traversable t) => t a -> RelVarName -> Either RelationalError DatabaseContextExpr
- creates an
Insert
expression which can be used to insert a set ofTupleable
values into the relation variable named by theRelVarName
- creates an
toUpdateExpr :: forall a. Tupleable a => RelVarName -> [AttributeName] -> a -> Either RelationalError DatabaseContextExpr
- create an
Update
expression which updates the tuples with the attribute values of the argumentTupleable
using the list of attribute names as the key to the underlying relation variable
- create an
toDeleteExpr :: forall a. Tupleable a => RelVarName -> [AttributeName] -> a -> Either RelationalError DatabaseContextExpr
- create a
Delete
expression which will remove the tuples from the relation variable matching the values of the argumentTupleable
using the attribute name list as the matching key
- create a
toAttributes :: proxy a -> Attributes
- generates
Attributes
from aTupleable
value- can be used withundefined
, so an actual value is not necessary
- generates
toTuple :: a -> RelationTuple
- converts a
Tupleable
value to a tuple for use in queries
- converts a
fromTuple :: RelationTuple -> Either RelationalError a
- converts a tuple to
Tupleable
value, unless there is an error such as a missing attribute
- converts a tuple to
For example usage, see the Out of the Tarpit example.
The most annoying misfeature of the so-called object-relational model (ORM) is the data type impedance mismatch. This is especially egregious for Haskell clients which must contend with down-sampling the quality of Haskell data structures. For example, most DBMSes don't support algebraic data types, so the ORM can't represent them or it must represent them as binary or JSON blobs in the database which precludes such types from database-level type safety, constraints, and value queries.
Project:M36 solves this mismatch by supporting algebraic data types as database-level values, Haskell scripting of database- and value-level server-side functions, and automatic and lossless marshaling of Haskell algebraic data types to values and tuples. Because the mapping between client data types and database-level data types is one-to-one, the mismatch is eliminated- any expression which could be evaluated against the data type on the client side can also be evaluated server-side.