@@ -0,0 +1,85 @@
1+This documentation covers the main concepts that are used throughout Rel8.
2+
3+``DBType``
4+==========
5+
6+The ``DBType`` type class provides a bridge between database expression values
7+and Haskell values. Rel8 comes with stock instances for most types that come
8+predefined with PostgreSQL, such as `int4` (which is represented as by
9+``Data.Int.Int32``) and timestamptz (which is ``UTCTime`` from ``Data.Time``).
10+This means that you need at least one ``DBType`` instance per database type,
11+though most of these primitive ``DBType``\s should already be available.
12+
13+Combining ``newtype`` and ``DBType``
14+------------------------------------
15+
16+You can define new instances of ``DBType`` by using Haskell "generalized newtype
17+deriving" strategy. This is useful when you have a common database type, but
18+need to interpret this type differently in different contexts. A very common
19+example here is with auto-incrementing ``id`` counters. In PostgreSQL, it's
20+common for a table to have a primary key that uses the ``serial`` type, which
21+means the key is an ``int8`` with a default value for ``INSERT``. In Rel8, we
22+could use ``Int64`` (as ``Int64`` is the ``DBType`` instance for ``int8``), but
23+we can be even clearer if we make a ``newtype`` for each *type* of id.
24+
25+If our database has users and orders, these tables might both have ids, but they
26+are clearly not meant to be treated as a common type. Instead, we can make these
27+types clearly different by writing::
28+
29+ newtype UserId = UserId Int64
30+ deriving newtype DBType
31+
32+ newtype OrderId = OrderId Int64
33+ deriving newtype DBType
34+
35+Now we can use ``UserId`` and ``OrderId`` in our Rel8 queries and definitions,
36+and Haskell will make sure we don't accidentally use an ``OrderId`` when we're
37+looking up a user.
38+
39+Parsing with ``DBType``
40+-----------------------
41+
42+``DBType``\s can also *refine* database types with parsing, which allows you to
43+map more structured Haskell types to a PostgreSQL database. This allows you to
44+use the capabalities of Haskell's rich type system to make it harder to write
45+incorrect queries. For example, we might have a database where we need to store
46+the status of an order. In Haskell, we might write:
47+
48+.. code-block:: haskell
49+
50+ data OrderStatus = PaymentPending | Paid | Packed | Shipped
51+
52+In our PostgreSQL we have a few choices, but for now we'll assume that they are
53+stored as ``text`` values.
54+
55+In order to use this type in Rel8 queries, we need to write an instance of
56+``DBType`` for ``OrderStatus``. One approach is to use ``parseTypeInformation``,
57+which allows you to refine an existing ``DBType``::
58+
59+ instance DBType OrderStatus where
60+ typeInformation = parseTypeInformation parser printer typeInformation
61+ where
62+ parser :: Text -> Either String OrderStatus
63+ parser "PaymentPending" = Right PaymentPending
64+ parser "Paid" = Right Paid
65+ parser "Packed" = Right Packed
66+ parser "Shipped" = Right Shipped
67+ parser other = Left $ "Unknown OrderStatus: " <> unpack other
68+
69+ printer :: OrderStatus -> Text
70+ printer PaymentPending = "PaymentPending"
71+ printer Paid = "Paid"
72+ printer Packed = "Packed"
73+ printer Shipped = "Shipped"
74+
75+Deriving ``DBType`` via ``ReadShow``
76+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
77+
78+This is a perfectly reasonable definition, though it is quite verbose and
79+tedious. Rel8 makes it easy to map Haskell types that are encoded using
80+``Read``/``Show`` via the ``ReadShow`` wrapper. An equivalent ``DBType``
81+definition using ``ReadShow`` is::
82+
83+ data OrderStatus = PaymentPending | Paid | Packed | Shipped
84+ deriving stock (Read, Show)
85+ deriving DBType via ReadShow OrderStatus