on master help sign in
Split concepts into separate files
commitfe6fdf410aaa370b6e731d3a0bd4a1eb25515501 authorOllie Charles <ollie@ocharles.org.uk> dateSat 3 Apr 2021, 16:54 (6 years ago) parent81b78ea
4 files changed+145−86
docs/concepts.rst → docs/concepts/dbtype.rst+6−83 docs/concepts/expr.rst+74 docs/concepts/tables.rst+60 docs/index.rst+5−3
docs/concepts.rst → docs/concepts/dbtype.rstrenamed+6−83
@@ -1,5 +1,3 @@
1−This documentation covers the main concepts that are used throughout Rel8.
2−
31``DBType``
42==========
53
@@ -114,114 +112,39 @@ this type with Rel8 by writing::
114112Here we used ``JSONBEncoded``, which will serialize to PostgreSQL ``jsonb``
115113(binary JSON) type, which is generally the best choice.
116114
117−``Expr`` and ``Sql``
118−====================
119−
120−``Expr``
121−--------
122−
123−Now that we've seen how types are bridged between a database and Haskell via
124−``DBType``, we can start looking at how expressions are modelled. In Rel8, any
125−expression will be a value of the form ``Expr a``, where ``a`` is the type of
126−the expression. For example, the SQL expression ``user.id = 42`` is an ``Expr
127−Bool``, and the subexpressions ``user.id`` and ``42`` might be ``Expr
128−UserId``\s.
129−
130−``Expr``\s are usually created by combining other ``Expr``\s together, like
131−using numeric operations like `+` and `*`, or by quoting Haskell values into
132−queries with ``lit``. Continuing with the example of ``user.id = 42``, we can
133−write this in Rel8 as::
134−
135− userId ==. lit (UserId 42)
136−
137−Here it's assumed that a ``userId :: Expr UserId`` expression is already in
138−scope, and we can compare that against another ``Expr UserId`` using the ``==.``
139−operator (which is like Haskell's normal ``==`` operator, but lifted to work on
140−``Expr``). The expression ``lit (UserId 42)`` quotes the Haskell term ``UserId
141−42`` literally as the SQL expression ``42``. This particular encoding is chosen
142−because ``UserId`` is simply a ``newtype`` around ``Int64``.
143−
144−``null``
145−--------
146−
147−So far we've only talked about ``DBType``, which represents database types
148−*excluding* ``null``. Rel8 chooses this encoding because ``null`` is rather
149−special in SQL, and doesn't really constitute a distinct type. For example,
150−there is no SQL notion of "stacking" nulls - which is to say a type like ``Maybe
151−(Maybe UserId))`` has no real analogous type in SQL.
152−
153−Of course, as ``null`` is pervasive in SQL queries, Rel8 does support ``null`` -
154−simply wrap up your type in ``Maybe``. ``Nothing`` will be translated as
155−``null``, and ``Just`` is used to represent non-null values.
156−
157−Rel8 comes with a set of functions to work with ``null`` values thash should
158−feel familiar to Haskell programmers:
159−
160−* ``null`` creates null values (you can also use ``lit Nothing``).
161−* ``nullify`` turns an ``Expr a`` into a ``Expr (Maybe a)`` (like ``Just``).
162−* ``nullable`` allows you to eliminate null values, like the ``maybe`` function.
163−* ``isNull`` and ``isNonNull`` work like ``isNothing`` and ``isJust``,
164− respectively.
165−* ``mapNullable`` is like ``fmap`` for ``Maybe``, and allows you to map over
166− non-null values.
167−* ``liftOpNullable`` is like ``liftA2`` for ``Maybe``, and allows you to combine
168− expressions together, given a way to combine non-null expressions.
169−
170−``SQL`` and null-polymorphic expressions
171−----------------------------------------
172−
173−Through the API reference documentation for Rel8, you might encounter the
174−``Sql`` type class. For example, if we look at the type of ``litExpr``, we
175−have::
176−
177− litExpr :: Sql DBType a => a -> Expr a
178−
179−Here ``Sql DBType a`` means that ``a`` can either be literally a type that has
180−an instance of ``DBType`` (like ``UserId`` or ``Bool``), *or* that same type
181−wrapped in ``Maybe`` (so ``Maybe UserId`` or ``Maybe Bool``). ``Maybe`` here
182−encodes the SQL concept of ``null``.
183−
184−Some functions work regardless of whether or not a value is null, and in these
185−cases you'll see ``Sql DBType a``. ``Sql`` can be used with any ``DBType``
186−subtype. For example, the type of ``div`` is::
187−
188− div :: Sql DBIntegral a => Expr a -> Expr a -> Expr a
189−
190−Which means ``div`` works on any ``DBIntegral a``, including ``Maybe a``.
191−
192115The ``DBType`` subtypes
193−=======================
116+-----------------------
194117
195118``DBEq``
196−--------
119+^^^^^^^^
197120
198121.. todo::
199122
200123 Document this
201124
202125``DBOrd``
203−---------
126+^^^^^^^^^
204127
205128.. todo::
206129
207130 Write this
208131
209132``DBSemigroup`` and ``DBMonoid``
210−--------------------------------
133+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
211134
212135.. todo::
213136
214137 Write this
215138
216139``DBNum``, ``DBIntegral`` and ``DBFractional``
217−----------------------------------------------
140+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
218141
219142.. todo::
220143
221144 Write this
222145
223146``DBString``
224−------------
147+^^^^^^^^^^^^
225148
226149.. todo::
227150
docs/concepts/expr.rstnew file+74
@@ -0,0 +1,74 @@
1+``Expr`` and ``Sql``
2+====================
3+
4+``Expr``
5+--------
6+
7+Now that we've seen how types are bridged between a database and Haskell via
8+``DBType``, we can start looking at how expressions are modelled. In Rel8, any
9+expression will be a value of the form ``Expr a``, where ``a`` is the type of
10+the expression. For example, the SQL expression ``user.id = 42`` is an ``Expr
11+Bool``, and the subexpressions ``user.id`` and ``42`` might be ``Expr
12+UserId``\s.
13+
14+``Expr``\s are usually created by combining other ``Expr``\s together, like
15+using numeric operations like `+` and `*`, or by quoting Haskell values into
16+queries with ``lit``. Continuing with the example of ``user.id = 42``, we can
17+write this in Rel8 as::
18+
19+ userId ==. lit (UserId 42)
20+
21+Here it's assumed that a ``userId :: Expr UserId`` expression is already in
22+scope, and we can compare that against another ``Expr UserId`` using the ``==.``
23+operator (which is like Haskell's normal ``==`` operator, but lifted to work on
24+``Expr``). The expression ``lit (UserId 42)`` quotes the Haskell term ``UserId
25+42`` literally as the SQL expression ``42``. This particular encoding is chosen
26+because ``UserId`` is simply a ``newtype`` around ``Int64``.
27+
28+``null``
29+--------
30+
31+So far we've only talked about ``DBType``, which represents database types
32+*excluding* ``null``. Rel8 chooses this encoding because ``null`` is rather
33+special in SQL, and doesn't really constitute a distinct type. For example,
34+there is no SQL notion of "stacking" nulls - which is to say a type like ``Maybe
35+(Maybe UserId))`` has no real analogous type in SQL.
36+
37+Of course, as ``null`` is pervasive in SQL queries, Rel8 does support ``null`` -
38+simply wrap up your type in ``Maybe``. ``Nothing`` will be translated as
39+``null``, and ``Just`` is used to represent non-null values.
40+
41+Rel8 comes with a set of functions to work with ``null`` values thash should
42+feel familiar to Haskell programmers:
43+
44+* ``null`` creates null values (you can also use ``lit Nothing``).
45+* ``nullify`` turns an ``Expr a`` into a ``Expr (Maybe a)`` (like ``Just``).
46+* ``nullable`` allows you to eliminate null values, like the ``maybe`` function.
47+* ``isNull`` and ``isNonNull`` work like ``isNothing`` and ``isJust``,
48+ respectively.
49+* ``mapNullable`` is like ``fmap`` for ``Maybe``, and allows you to map over
50+ non-null values.
51+* ``liftOpNullable`` is like ``liftA2`` for ``Maybe``, and allows you to combine
52+ expressions together, given a way to combine non-null expressions.
53+
54+``SQL`` and null-polymorphic expressions
55+----------------------------------------
56+
57+Through the API reference documentation for Rel8, you might encounter the
58+``Sql`` type class. For example, if we look at the type of ``litExpr``, we
59+have::
60+
61+ litExpr :: Sql DBType a => a -> Expr a
62+
63+Here ``Sql DBType a`` means that ``a`` can either be literally a type that has
64+an instance of ``DBType`` (like ``UserId`` or ``Bool``), *or* that same type
65+wrapped in ``Maybe`` (so ``Maybe UserId`` or ``Maybe Bool``). ``Maybe`` here
66+encodes the SQL concept of ``null``.
67+
68+Some functions work regardless of whether or not a value is null, and in these
69+cases you'll see ``Sql DBType a``. ``Sql`` can be used with any ``DBType``
70+subtype. For example, the type of ``div`` is::
71+
72+ div :: Sql DBIntegral a => Expr a -> Expr a -> Expr a
73+
74+Which means ``div`` works on any ``DBIntegral a``, including ``Maybe a``.
docs/concepts/tables.rstnew file+60
@@ -0,0 +1,60 @@
1+Tables
2+======
3+
4+So far we've seen that ``DBType`` bridges Haskell types to database types, and
5+``Expr`` lets us write SQL expressions using these types. The next concept is to
6+understand how tables are mapped into Rel8.
7+
8+In Rel8, we understand a table to be a list of columns. Each column has some
9+associated metadata - a column has a type, but it also has information as to
10+whether it can store ``null``, the name of the column, and whether this column
11+has a default value. All of this metadata can be configured when you define
12+custom table types, but first we'll look at some built in tables.
13+
14+``Expr`` is a single-column table
15+---------------------------------
16+
17+Any time you have an ``Expr``, you also have a table. All ``Expr``\s are tables
18+that consist of exactly one column. This means that whenever a table is
19+expected, you can usually use an ``Expr`` as well.
20+
21+Tuples combine tables
22+---------------------
23+
24+You can use Haskell's normal tuple syntax to combine tables into larger tables.
25+Now that we know that an ``Expr`` is a table with one column, we can use tuples
26+to build larger tables. For example, if we have ``userId :: Expr UserId`` and a
27+``name :: Expr Text``, we can pair these together as ``(userId, name) :: (Expr
28+UserId, Expr Text)``.
29+
30+Rel8 supports tuples with two, three, four and five elements. Beyond that, we
31+suggest writing a custom table type (though you can also nest tuples).
32+
33+Custom table types
34+------------------
35+
36+The primary way to define a table in Rel8 is to use the higher-kinded data
37+pattern. Rel8 advocates this system because it means you can define your data
38+type once, but use it in the context of Haskell expressions (for example, to
39+serialize it as JSON to use as a REST API call response type), and also within
40+Rel8 queries. This allows developers to share their understanding of a type in
41+multiple domains, while also reducing the amount of code that has to be written
42+and maintained.
43+
44+To define a custom table using this pattern, you define a Haskell data type that
45+has a single parameter (conventionally called ``f``). Next, we suggest using
46+record syntax to define the fields of your data type, and for each field use the
47+``Column`` type family to define the type of the column. Once all fields have
48+been defined, you can bridge this type with Rel8 by deriving ``Generic`` and
49+``Rel8able`` instance.
50+
51+A typical table definition might look like this::
52+
53+ data User f = User
54+ { userId :: Column f UserId
55+ , userName :: Column f Text
56+ , userCreatedAt :: Column f UTCTime
57+ , userEmail :: Column f (Maybe EmailAddress)
58+ }
59+ deriving stock (Generic)
60+ deriving anyclass (Rel8able)
docs/index.rst+5−3
@@ -1,5 +1,5 @@
11Welcome to Rel8!
2−================================
2+================
33
44Welcome to Rel8! Rel8 is a Haskell library for interacting with PostgreSQL
55databases, built on top of the fantastic `Opaleye
@@ -18,14 +18,16 @@ The main objectives of Rel8 are:
1818
1919.. toctree::
2020 :maxdepth: 3
21− :caption: Contents:
21+ :caption: Getting Started
2222
2323 tutorial
2424
2525.. toctree::
2626 :caption: Rel8 concepts
2727
28− concepts
28+ concepts/dbtype
29+ concepts/expr
30+ concepts/tables
2931
3032.. toctree::
3133 :caption: Tips and tricks