Skip to content

Commit 738a7ab

Browse files
committed
Updated the join documentation to highlight the (:.) type constructor.
1 parent 3cbe970 commit 738a7ab

File tree

1 file changed

+50
-0
lines changed

1 file changed

+50
-0
lines changed

Guide/relationships.markdown

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,56 @@ data LabeledData a b = LabeledData { labelValue :: a, contentValue :: b }
288288

289289
In the case above, `a` would be instantiated by (Id' "tags") and `b` by `Post`.
290290

291+
### Simple Joins and Outer Joins
292+
An alternative approach to joining data in IHP can be accomplished by using the [postresql-simple (:.)](https://hackage.haskell.org/package/postgresql-simple-0.6.4/docs/Database-PostgreSQL-Simple-Types.html#t::.)
293+
and a custom sql query.
294+
295+
For example say there is a `Student`, `StudentDeskCombo`, and `Desk` data type derived by IHP from
296+
`students`, `student_desk_combos`, and `desks` tables.
297+
298+
If the application wished to get a list of all the desks and whether a student
299+
is associated with that desk a `left outer join` on the three tables would be a simple
300+
way of accomplishing this. The postgresql data type `(:.)` allows for a compound data
301+
structure to be created without having to define any `newtype` wrappers or define
302+
functions that do any type level computations.
303+
304+
All that is required is that a `FromRow` instance for any potentially nullable return value in the query, e.g. `Maybe Student`,
305+
is manually defined in the IHP application:
306+
307+
```haskell
308+
instance FromRow (Maybe Student) where
309+
fromRow = (null *> null *> null *> pure Nothing) <|> (Just <$> fromRow)
310+
where null = field :: RowParser Null
311+
```
312+
313+
At the moment the postgresql-simple library does not derive this instance generically.
314+
315+
Once you define this instance, preferably in `Application.Helper.Controller`, you can then
316+
access the IHP derived data types directly by writing a custom sql query:
317+
318+
```
319+
deskStudentCombos :: [Desk :. Maybe StudentDeskCombo :. Maybe Student] <- sqlQuery [select * from desks
320+
left outer join on studentdeskcombo.desk_id = desks.id
321+
left outer join on studentdeskcombo.student_id = students.id
322+
]()
323+
```
324+
325+
326+
the result data type can be unpacked and rendered using straight forward pattern matching with the `(:.)`
327+
data type/type constructor:
328+
```
329+
renderStudentDesk :: (Desk :. Maybe StudentDeskCombo :. Student) -> Html
330+
renderStudentDesk (desk :. Just studentDeskCombo :. Just student) = [hsx|{get #name student} {get #id desk}|]
331+
renderStudentDesk (desk :. Nothing :. Nothing) = [hsx|<p>No student assigned to this Desk: {get #id desk}.</p>|]
332+
```
333+
334+
In the case of inner joins the process is even simpler and does not require
335+
defining the `instance FromRow Maybe a`. This approach to joins allows
336+
for custom queries to leverage the autogenerated schema/IHP derived data types directly
337+
and cuts down on clutter from `newtype` definitions.
338+
339+
340+
291341
### Many-to-many relationships and views
292342

293343
Let's say we have the following schema:

0 commit comments

Comments
 (0)