HORIZON HASKELLDocslts/ghc-10.0.x8c2f8e82026-10-19Search names, modules, packages, or :: a typeCtrl K

GHC 10.0.0.20260917 · lts/ghc-10.0.x · 8c2f8e8 · 2026-10-19

Moduleesqueleto-3.6.0.3Haskell2010

Database.Esqueleto.Experimental.From.Join

  • 3 types
  • 5 classes
  • 12 values
  • Packageesqueleto-3.6.0.3
  • Exports23
  • LanguageHaskell2010
  • LicenceBSD-3-Clause
  • SourceInternal.hs
datadata (:&) a b
#

A left-precedence pair. Pronounced "and". Used to represent expressions that have been joined together.

The precedence behavior can be demonstrated by:

a :& b :& c == ((a :& b) :& c)

See the examples at the beginning of this module to see how this operator is used in JOIN operations.

Constructors

  • a :& binfixl 2
Instances19Bifunctor, DoInnerJoin, DoLeftJoin, DoCrossJoin, GetFirstTable, Functor, …
valueon
  1. :: ValidOnClause a
  2. => a
  3. -> b -> SqlExpr (Value Bool)
  4. -> (a, b -> SqlExpr (Value Bool))
#

An ON clause that describes how two tables are related. This should be used as an infix operator after a JOIN. For example,

select $
from $ table @Person
`innerJoin` table @BlogPost
`on` (\(p :& bP) ->
        p ^. PersonId ==. bP ^. BlogPostAuthorId)
valueinnerJoin
  1. :: (ToFrom a a', ToFrom b b', HasOnClause rhs (a' :& b'), rhs ~ (b, a' :& b' -> SqlExpr (Value Bool)))
  2. => a
  3. -> rhs
  4. -> From (a' :& b')
#

INNER JOIN

Used as an infix operator `innerJoin`

select $
from $ table @Person
`innerJoin` table @BlogPost
`on` (\(p :& bp) ->
        p ^. PersonId ==. bp ^. BlogPostAuthorId)
valuecrossJoin :: (ToFrom a a', ToFrom b b') => a -> b -> From (a' :& b')
#

CROSS JOIN

Used as an infix `crossJoin`

select $ do
from $ table @Person
`crossJoin` table @BlogPost
valueleftJoin
  1. :: (ToFrom a a', ToFrom b b', ToMaybe b', HasOnClause rhs (a' :& ToMaybeT b'), rhs ~ (b, a' :& ToMaybeT b' -> SqlExpr (Value Bool)))
  2. => a
  3. -> rhs
  4. -> From (a' :& ToMaybeT b')
#

LEFT OUTER JOIN

Join where the right side may not exist. If the on clause fails then the right side will be NULL'ed Because of this the right side needs to be handled as a Maybe

Used as an infix operator `leftJoin`

select $
from $ table @Person
`leftJoin` table @BlogPost
`on` (\(p :& bp) ->
        just (p ^. PersonId) ==. bp ?. BlogPostAuthorId)
valueleftJoinLateral
  1. :: (ToFrom a a', SqlSelect b r, HasOnClause rhs (a' :& ToMaybeT b), ToAlias b, ToAliasReference b, ToMaybe b, rhs ~ (a' -> SqlQuery b, a' :& ToMaybeT b -> SqlExpr (Value Bool)))
  2. => a
  3. -> rhs
  4. -> From (a' :& ToMaybeT b)
#

LEFT OUTER JOIN LATERAL

Lateral join where the right side may not exist. In the case that the query returns nothing or the on clause fails the right side of the join will be NULL'ed Because of this the right side needs to be handled as a Maybe

Used as an infix operator `leftJoinLateral`

See example 6 for how to use LATERAL

valuerightJoin
  1. :: (ToFrom a a', ToFrom b b', ToMaybe a', HasOnClause rhs (ToMaybeT a' :& b'), rhs ~ (b, ToMaybeT a' :& b' -> SqlExpr (Value Bool)))
  2. => a
  3. -> rhs
  4. -> From (ToMaybeT a' :& b')
#

RIGHT OUTER JOIN

Join where the left side may not exist. If the on clause fails then the left side will be NULL'ed Because of this the left side needs to be handled as a Maybe

Used as an infix operator `rightJoin`

select $
from $ table @Person
`rightJoin` table @BlogPost
`on` (\(p :& bp) ->
        p ?. PersonId ==. bp ^. BlogPostAuthorId)
valuefullOuterJoin
  1. :: (ToFrom a a', ToFrom b b', ToMaybe a', ToMaybe b', HasOnClause rhs (ToMaybeT a' :& ToMaybeT b'), rhs ~ (b, ToMaybeT a' :& ToMaybeT b' -> SqlExpr (Value Bool)))
  2. => a
  3. -> rhs
  4. -> From (ToMaybeT a' :& ToMaybeT b')
#

FULL OUTER JOIN

Join where both sides of the join may not exist. Because of this the result needs to be handled as a Maybe

Used as an infix operator `fullOuterJoin`

select $
from $ table @Person
`fullOuterJoin` table @BlogPost
`on` (\(p :& bp) ->
        p ?. PersonId ==. bp ?. BlogPostAuthorId)
classclass GetFirstTable t ts where
#

Typeclass for selecting tables using type application syntax.

If you have a long chain of tables joined with (:&), like a :& b :& c :& d, then getTable @c (a :& b :& c :& d) will give you the c table back.

Note that this typeclass will only select the first table of the given type; it may be less useful if there's multiple tables of the same type.

Methods

  • getFirstTable :: ts -> t

    Get the first table of type t from the tables ts.

Instances3GetFirstTable
  • GetFirstTable t (t :& ts)Defined in esqueleto-3.6.0.3 · Database.Esqueleto.Experimental.From.Join
  • GetFirstTable t (x :& t)Defined in esqueleto-3.6.0.3 · Database.Esqueleto.Experimental.From.Join
  • GetFirstTable t ts => GetFirstTable t (ts :& x)Defined in esqueleto-3.6.0.3 · Database.Esqueleto.Experimental.From.Join
valuegetTable
  1. :: GetFirstTable (SqlExpr (Entity t)) ts
  2. => ts
  3. -> SqlExpr (Entity t)
#

Get the first table of a given type from a chain of tables joined with (:&).

This can make it easier to write queries with a large number of join clauses:

select $ do
(people :& followers :& blogPosts) <-
    from $ table @Person
    `innerJoin` table @Follow
    `on` (\(person :& follow) ->
            person ^. PersonId ==. follow ^. FollowFollowed)
    `innerJoin` table @BlogPost
    `on` (\((getTable @Follow -> follow) :& blogPost) ->
            blogPost ^. BlogPostAuthorId ==. follow ^. FollowFollower)
where_ (people1 ^. PersonName ==. val "John")
pure (followers, people2)

This example is a bit trivial, but once you've joined five or six tables it becomes enormously helpful. The above example uses a ViewPattern to call the function and assign the variable directly, but you can also imagine it being written like this:

    `on` (\(prev :& blogPost) ->
            let
                follow = getTable @Follow prev
             in
                blogPost ^. BlogPostAuthorId ==. follow ^. FollowFollower)

This function will pluck out the first table that matches the applied type, so if you join on the same table multiple times, it will always select the first one provided.

The (:&) operator associates so that the left hand side can be a wildcard for an arbitrary amount of nesting, and the "most recent" or "newest" table in a join sequence is always available on the rightmost - so (prev :& bar) is a pattern that matches bar table (the most recent table added) and prev tables (all prior tables in the join match).

By calling getTable on the prev, you can select exactly the table you want, allowing you to omit a large number of spurious pattern matches. Consider a query that does several LEFT JOIN on a first table:

SELECT *
FROM person
LEFT JOIN car
  ON person.id = car.person_id
LEFT JOIN bike
  ON person.id = bike.person_id
LEFT JOIN food
  ON person.id = food.person_id
LEFT JOIN address
  ON person.id = address.person_id

The final on clause in esqueleto would look like this:

    `on` do
        \(person :& _car :& _bike :& _food :& address) ->
            person.id ==. address.personId

First, we can change it to a prev :& newest match. We can do this because of the operator associativity. This is kind of like how a list : operator associates, but in the other direction: a : (b : c) = a : b : c.

    `on` do
        \(prev :& address) ->
            let (person :& _car :& _bike :& _food) = prev
             in person.id ==. address.personId

Then, we can use getTable to select the Person table directly, instead of pattern matching manually.

    `on` do
        \(prev :& address) ->
            let person = getTable @Person prev
             in person.id ==. address.personId

Finally, we can use a ViewPattern language extension to "inline" the access.

    `on` do
        \((getTable @Person -> person) :& address) ->
           person.id ==. address.personId

With this form, you do not need to be concerned about the number and wildcard status of tables that do not matter to the specific ON clause.

datadata Lateral
#
Instances3DoInnerJoin, DoLeftJoin, DoCrossJoin
datadata NotLateral
#
Instances3DoCrossJoin, DoInnerJoin, DoLeftJoin
classclass DoInnerJoin lateral lhs rhs res | lateral rhs lhs -> res where
#

Methods

Instances2DoInnerJoin
classclass DoLeftJoin lateral lhs rhs res | lateral rhs lhs -> res where
#

Methods

Instances2DoLeftJoin

Orphan instances

9 instances