{-# 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 :: forall a. Table Expr a => Query a -> (a -> Query a) -> Query a
loop Query a
base a -> Query a
recurse =
  Select a -> Query a
forall a. Select a -> Query a
fromOpaleye (Select a -> Query a) -> Select a -> Query a
forall a b. (a -> b) -> a -> b
$ Binaryspec a a -> Select a -> (a -> Select a) -> Select a
forall a. Binaryspec a a -> Select a -> (a -> Select a) -> Select a
withRecursiveExplicit Binaryspec a a
forall a. Table Expr a => Binaryspec a a
binaryspec Select a
base' a -> Select a
recurse'
  where
    base' :: Select a
base' = Query a -> Select a
forall a. Query a -> Select a
toOpaleye Query a
base
    recurse' :: a -> Select a
recurse' = Query a -> Select a
forall a. Query a -> Select a
toOpaleye (Query a -> Select a) -> (a -> Query a) -> a -> Select a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> Query a
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 :: forall a. Table Expr a => Query a -> (a -> Query a) -> Query a
loopDistinct Query a
base a -> Query a
recurse =
  Select a -> Query a
forall a. Select a -> Query a
fromOpaleye (Select a -> Query a) -> Select a -> Query a
forall a b. (a -> b) -> a -> b
$ Binaryspec a a -> Select a -> (a -> Select a) -> Select a
forall a. Binaryspec a a -> Select a -> (a -> Select a) -> Select a
withRecursiveDistinctExplicit Binaryspec a a
forall a. Table Expr a => Binaryspec a a
binaryspec Select a
base' a -> Select a
recurse'
  where
    base' :: Select a
base' = Query a -> Select a
forall a. Query a -> Select a
toOpaleye Query a
base
    recurse' :: a -> Select a
recurse' = Query a -> Select a
forall a. Query a -> Select a
toOpaleye (Query a -> Select a) -> (a -> Query a) -> a -> Select a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> Query a
recurse