Skip to content

Commit

Permalink
Add loopDistinct (#277)
Browse files Browse the repository at this point in the history
  • Loading branch information
shane-circuithub authored Oct 2, 2023
1 parent d058b64 commit 7eec1f8
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 4 deletions.
2 changes: 1 addition & 1 deletion changelog.d/20230707_191301_ollie_scriv.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
### Added

- - `Rel8.loop`, which allows writing `WITH .. RECURSIVE` queries. ([#180](https://github.com/circuithub/rel8/pull/180))
- - `Rel8.loop` and `Rel8.loopDistinct`, which allow writing `WITH .. RECURSIVE` queries. ([#180](https://github.com/circuithub/rel8/pull/180))
2 changes: 1 addition & 1 deletion rel8.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ library
, data-textual
, hasql ^>= 1.6.1.2
, network-ip
, opaleye ^>= 0.10.0.0
, opaleye ^>= 0.10.1.0
, pretty
, profunctors
, product-profunctors
Expand Down
1 change: 1 addition & 0 deletions src/Rel8.hs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ module Rel8

-- ** @WITH RECURSIVE@
, loop
, loopDistinct

-- ** Aggregation
, Aggregator
Expand Down
45 changes: 43 additions & 2 deletions src/Rel8/Query/Loop.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

module Rel8.Query.Loop
( loop
, loopDistinct
) where

-- base
import Prelude

-- opaleye
import Opaleye.With ( withRecursiveExplicit )
import Opaleye.With (withRecursiveExplicit, withRecursiveDistinctExplicit)

-- rel8
import Rel8.Expr ( Expr )
Expand All @@ -23,10 +24,50 @@ import Rel8.Table.Opaleye ( binaryspec )
-- 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\".
-- 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

0 comments on commit 7eec1f8

Please sign in to comment.