{-# language FlexibleContexts #-} module Rel8.Query.Loop ( loop , loopDistinct ) where -- base import Prelude -- opaleye import Opaleye.With (withRecursiveExplicit, withRecursiveDistinctExplicit) -- rel8 import Rel8.Expr ( Expr ) import Rel8.Query ( Query ) import Rel8.Query.Opaleye ( fromOpaleye, toOpaleye ) import Rel8.Table ( Table ) import Rel8.Table.Opaleye ( binaryspec ) -- | 'loop' allows the construction of recursive queries, using Postgres' -- [@WITH RECURSIVE@](https://www.postgresql.org/docs/current/queries-with.html#QUERIES-WITH-RECURSIVE) -- under the hood. The first argument to 'loop' is what the Postgres -- documentation refers to as the \"non-recursive term\" and the second -- argument is the \"recursive term\", which is defined in terms of the result -- of the \"non-recursive term\". 'loop' uses @UNION ALL@ to combine the -- recursive and non-recursive terms. -- -- Denotionally, @'loop' s f@ is the smallest set of rows @r@ such -- that -- -- @ -- r == s \`'Rel8.unionAll'\` (r >>= f) -- @ -- -- Operationally, @'loop' s f@ takes each row in an initial set @s@ and -- supplies it to @f@, resulting in a new generation of rows which are added -- to the result set. Each row from this new generation is then fed back to -- @f@, and this process is repeated until a generation comes along for which -- @f@ returns an empty set for each row therein. loop :: Table Expr a => Query a -> (a -> Query a) -> Query a loop base recurse = fromOpaleye $ withRecursiveExplicit binaryspec base' recurse' where base' = toOpaleye base recurse' = toOpaleye . recurse -- | 'loopDistinct' is like 'loop' but uses @UNION@ instead of @UNION ALL@ to -- combine the recursive and non-recursive terms. -- -- Denotationally, @'loopDistinct' s f@ is the smallest set of rows -- @r@ such that -- -- @ -- r == s \`'Rel8.union'\` (r >>= f) -- @ -- -- Operationally, @'loopDistinct' s f@ takes each /distinct/ row in an -- initial set @s@ and supplies it to @f@, resulting in a new generation of -- rows. Any rows returned by @f@ that already exist in the result set are not -- considered part of this new generation by 'loopDistinct' (in contrast to -- 'loop'). This new generation is then added to the result set, and each row -- therein is then fed back to @f@, and this process is repeated until a -- generation comes along for which @f@ returns no rows that don't already -- exist in the result set. loopDistinct :: Table Expr a => Query a -> (a -> Query a) -> Query a loopDistinct base recurse = fromOpaleye $ withRecursiveDistinctExplicit binaryspec base' recurse' where base' = toOpaleye base recurse' = toOpaleye . recurse