{-# language FlexibleContexts #-}

module Rel8.Query.Materialize
  ( materialize
  )
where

-- base
import Prelude

-- opaleye
import Opaleye.With ( withMaterializedExplicit )

-- rel8
import Rel8.Expr ( Expr )
import Rel8.Query ( Query )
import Rel8.Query.Opaleye ( fromOpaleye, toOpaleye )
import Rel8.Query.Rebind ( rebind )
import Rel8.Table ( Table )
import Rel8.Table.Opaleye ( unpackspec )


-- | 'materialize' takes a 'Query' and fully evaluates it and caches the
-- results thereof, and passes to a continuation a new 'Query' that simply
-- looks up these cached results. It's usually best not to use this and to let
-- the Postgres optimizer decide for itself what's best, but if you know what
-- you're doing this can sometimes help to nudge it in a particular direction.
--
-- 'materialize' is currently implemented in terms of Postgres'
-- [@WITH](https://www.postgresql.org/docs/current/queries-with.html) syntax,
-- specifically the @WITH _ AS MATERIALIZED (_)@ form introduced in PostgreSQL
-- 12. This means that 'materialize' can only be used with PostgreSQL 12 or
-- newer.
materialize :: (Table Expr a, Table Expr b)
  => Query a -> (Query a -> Query b) -> Query b
materialize :: forall a b.
(Table Expr a, Table Expr b) =>
Query a -> (Query a -> Query b) -> Query b
materialize Query a
query Query a -> Query b
f =
  (Query b -> (b -> Query b) -> Query b
forall a b. Query a -> (a -> Query b) -> Query b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= String -> b -> Query b
forall a. Table Expr a => String -> a -> Query a
rebind String
"with") (Query b -> Query b)
-> (Select b -> Query b) -> Select b -> Query b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Select b -> Query b
forall a. Select a -> Query a
fromOpaleye (Select b -> Query b) -> Select b -> Query b
forall a b. (a -> b) -> a -> b
$
    Unpackspec a a -> Select a -> (Select a -> Select b) -> Select b
forall a b.
Unpackspec a a -> Select a -> (Select a -> Select b) -> Select b
withMaterializedExplicit Unpackspec a a
forall a. Table Expr a => Unpackspec a a
unpackspec
      (Query a -> Select a
forall a. Query a -> Select a
toOpaleye Query a
query')
      (Query b -> Select b
forall a. Query a -> Select a
toOpaleye (Query b -> Select b)
-> (Select a -> Query b) -> Select a -> Select b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Query a -> Query b
f (Query a -> Query b)
-> (Select a -> Query a) -> Select a -> Query b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Select a -> Query a
forall a. Select a -> Query a
fromOpaleye)
  where
    query' :: Query a
query' = Query a
query