diff --git a/docs/tutorials/tutorial2.md b/docs/tutorials/tutorial2.md index af565da60..c2dd4fb88 100644 --- a/docs/tutorials/tutorial2.md +++ b/docs/tutorials/tutorial2.md @@ -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 diff --git a/docs/tutorials/tutorial3.md b/docs/tutorials/tutorial3.md index aa1acb95c..64a1cff4f 100644 --- a/docs/tutorials/tutorial3.md +++ b/docs/tutorials/tutorial3.md @@ -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 @@ -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)) @@ -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)) @@ -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)) @@ -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))