Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/tutorials/tutorial2.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,18 @@ runBeamSqliteDebug putStrLn conn $ runInsert $
insertValues [ james, betty, sam ]
```

!!! tip "Tip"
The `let` bindings for `james`, `betty`, and `sam` above are reused at two
different types: as concrete rows (`UserT Identity`) inside
`insertValues`, and as expression-level values (here as `pk james`, of
type `PrimaryKey UserT (QExpr Sqlite s)`) inside `insertExpressions`.
For the same binding to be usable at both types, GHC must infer a
polymorphic type for it. Inside GHCi this happens by default, but in a
compiled module you may need to enable `NoMonomorphismRestriction` (or
give each binding an explicit polymorphic signature). Otherwise the
monomorphism restriction will pin `james` to whichever type it is first
used at, and the second use will fail to typecheck.

Now that we have some `User` objects, we can create associated addresses. Notice
that above, we used `insertValues` to insert concrete `User` rows. This worked
because we could determine every field of `User` before insertion. `Address`es
Expand Down
26 changes: 22 additions & 4 deletions docs/tutorials/tutorial3.md
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,25 @@ Some RDBMSs, like Postgres, given such a query will be unable to utilize availab
to perform join operations - this translates to *extremely* poor perfomance for even moderately
sized data.

!!! warning "Nullable columns and `maybe_` on a left-joined table"
There is also a *correctness* gotcha here whenever the left-joined table
contains a schema-level nullable column. `maybe_` (and `isJust_`) on a
nullable table is implemented as "every column of the row is non-`NULL`",
so a real, matched row whose nullable column happens to be `NULL` is
treated as `Nothing`. In our schema `OrderT` has the nullable
`_orderShippingInfo` column, so for any of James's unshipped orders
`maybe_ (val_ False) ... order` evaluates to `False`, and the chained
`lineItem` and `product` left joins return no rows for him. The query
above will compute `Just 0` for James instead of `Just 27000`.

The next variant uses `leftJoin_'` and avoids `maybe_` over a nullable
table entirely, so it is both faster *and* correct in this case. As a
rule of thumb, when a left-joined table contains nullable columns,
prefer `leftJoin_'` with `==?.` (and other `SqlBool` operators) over
`leftJoin_` with `maybe_`.

Luckily, Beam also provides an alternate way to phrase things that directly maps to SQL semantics
(and avoids the gotcha above)

!beam-query
```haskell
Expand Down Expand Up @@ -589,7 +607,7 @@ shippingInformationByUser <-
do user <- all_ (shoppingCartDb ^. shoppingCartUsers)

(userEmail, unshippedCount) <-
aggregate_ (\(userEmail, order) -> (group_ userEmail, as_ @Int32 countAll_)) $
aggregate_ (\(userEmail, order) -> (group_ userEmail, as_ @Int32 (count_ (_orderId order)))) $
do user <- all_ (shoppingCartDb ^. shoppingCartUsers)
order <- leftJoin_ (all_ (shoppingCartDb ^. shoppingCartOrders))
(\order -> _orderForUser order `references_` user &&. isNothing_ (_orderShippingInfo order))
Expand All @@ -598,7 +616,7 @@ shippingInformationByUser <-
guard_ (userEmail `references_` user)

(userEmail, shippedCount) <-
aggregate_ (\(userEmail, order) -> (group_ userEmail, as_ @Int32 countAll_)) $
aggregate_ (\(userEmail, order) -> (group_ userEmail, as_ @Int32 (count_ (_orderId order)))) $
do user <- all_ (shoppingCartDb ^. shoppingCartUsers)
order <- leftJoin_ (all_ (shoppingCartDb ^. shoppingCartOrders))
(\order -> _orderForUser order `references_` user &&. isJust_ (_orderShippingInfo order))
Expand Down Expand Up @@ -629,7 +647,7 @@ shippingInformationByUser <-

(userEmail, unshippedCount) <-
subselect_ $
aggregate_ (\(userEmail, order) -> (group_ userEmail, as_ @Int32 countAll_)) $
aggregate_ (\(userEmail, order) -> (group_ userEmail, as_ @Int32 (count_ (_orderId order)))) $
do user <- all_ (shoppingCartDb ^. shoppingCartUsers)
order <- leftJoin_ (all_ (shoppingCartDb ^. shoppingCartOrders))
(\order -> _orderForUser order `references_` user &&. isNothing_ (_orderShippingInfo order))
Expand All @@ -639,7 +657,7 @@ shippingInformationByUser <-

(userEmail, shippedCount) <-
subselect_ $
aggregate_ (\(userEmail, order) -> (group_ userEmail, as_ @Int32 countAll_)) $
aggregate_ (\(userEmail, order) -> (group_ userEmail, as_ @Int32 (count_ (_orderId order)))) $
do user <- all_ (shoppingCartDb ^. shoppingCartUsers)
order <- leftJoin_ (all_ (shoppingCartDb ^. shoppingCartOrders))
(\order -> _orderForUser order `references_` user &&. isJust_ (_orderShippingInfo order))
Expand Down
Loading