@@ -36,6 +36,8 @@ module Database.Esqueleto.PostgreSQL
3636 , forKeyShareOf
3737 , filterWhere
3838 , values
39+ , withMaterialized
40+ , withNotMaterialized
3941 , ascNullsFirst
4042 , ascNullsLast
4143 , descNullsFirst
@@ -52,15 +54,22 @@ import Control.Exception (throw)
5254import Control.Monad (void )
5355import Control.Monad.IO.Class (MonadIO (.. ))
5456import qualified Control.Monad.Trans.Reader as R
57+ import qualified Control.Monad.Trans.Writer as W
5558import Data.Int (Int64 )
5659import qualified Data.List.NonEmpty as NE
5760import Data.Maybe
5861import Data.Proxy (Proxy (.. ))
5962import qualified Data.Text.Internal.Builder as TLB
6063import qualified Data.Text.Lazy as TL
64+ import qualified Data.Text.Lazy.Builder as TLB
6165import Data.Time.Clock (UTCTime )
6266import qualified Database.Esqueleto.Experimental as Ex
63- import Database.Esqueleto.Internal.Internal hiding (random_ )
67+ import qualified Database.Esqueleto.Experimental.From as Ex
68+ import Database.Esqueleto.Experimental.From.CommonTableExpression
69+ import Database.Esqueleto.Experimental.From.SqlSetOperation
70+ import Database.Esqueleto.Experimental.ToAlias
71+ import Database.Esqueleto.Experimental.ToAliasReference
72+ import Database.Esqueleto.Internal.Internal hiding (From (.. ), from , on , random_ )
6473import Database.Esqueleto.Internal.PersistentImport hiding
6574 (uniqueFields , upsert , upsertBy )
6675import Database.Persist.SqlBackend
@@ -490,7 +499,7 @@ forNoKeyUpdateOf lockableEntities onLockedBehavior =
490499forShareOf :: LockableEntity a => a -> OnLockedBehavior -> SqlQuery ()
491500forShareOf lockableEntities onLockedBehavior =
492501 putLocking $ PostgresLockingClauses [PostgresLockingKind PostgresForShare (Just $ LockingOfClause lockableEntities) onLockedBehavior]
493-
502+
494503-- | `FOR KEY SHARE OF` syntax for postgres locking
495504-- allows locking of specific tables with a key share lock in a view or join
496505--
@@ -499,6 +508,82 @@ forKeyShareOf :: LockableEntity a => a -> OnLockedBehavior -> SqlQuery ()
499508forKeyShareOf lockableEntities onLockedBehavior =
500509 putLocking $ PostgresLockingClauses [PostgresLockingKind PostgresForKeyShare (Just $ LockingOfClause lockableEntities) onLockedBehavior]
501510
511+ -- | @WITH@ @MATERIALIZED@ clause is used to introduce a
512+ -- [Common Table Expression (CTE)](https://en.wikipedia.org/wiki/Hierarchical_and_recursive_queries_in_SQL#Common_table_expression)
513+ -- with the MATERIALIZED keyword. The MATERIALIZED keyword is only supported in PostgreSQL >= version 12.
514+ -- In Esqueleto, CTEs should be used as a subquery memoization tactic. PostgreSQL treats a materialized CTE as an optimization fence.
515+ -- A materialized CTE is always fully calculated, and is not "inlined" with other table joins.
516+ -- Without the MATERIALIZED keyword, PostgreSQL >= 12 may "inline" the CTE as though it was any other join.
517+ -- You should always verify that using a materialized CTE will in fact improve your performance
518+ -- over a regular subquery.
519+ --
520+ -- @
521+ -- select $ do
522+ -- cte <- withMaterialized subQuery
523+ -- cteResult <- from cte
524+ -- where_ $ cteResult ...
525+ -- pure cteResult
526+ -- @
527+ --
528+ --
529+ -- For more information on materialized CTEs, see the PostgreSQL manual documentation on
530+ -- [Common Table Expression Materialization](https://www.postgresql.org/docs/14/queries-with.html#id-1.5.6.12.7).
531+ --
532+ -- @since 3.5.14.0
533+ withMaterialized :: ( ToAlias a
534+ , ToAliasReference a
535+ , SqlSelect a r
536+ ) => SqlQuery a -> SqlQuery (Ex. From a )
537+ withMaterialized query = do
538+ (ret, sideData) <- Q $ W. censor (\ _ -> mempty ) $ W. listen $ unQ query
539+ aliasedValue <- toAlias ret
540+ let aliasedQuery = Q $ W. WriterT $ pure (aliasedValue, sideData)
541+ ident <- newIdentFor (DBName " cte" )
542+ let clause = CommonTableExpressionClause NormalCommonTableExpression (\ _ _ -> " MATERIALIZED " ) ident (\ info -> toRawSql SELECT info aliasedQuery)
543+ Q $ W. tell mempty {sdCteClause = [clause]}
544+ ref <- toAliasReference ident aliasedValue
545+ pure $ Ex. From $ pure (ref, (\ _ info -> (useIdent info ident, mempty )))
546+
547+ -- | @WITH@ @NOT@ @MATERIALIZED@ clause is used to introduce a
548+ -- [Common Table Expression (CTE)](https://en.wikipedia.org/wiki/Hierarchical_and_recursive_queries_in_SQL#Common_table_expression)
549+ -- with the NOT MATERIALIZED keywords. These are only supported in PostgreSQL >=
550+ -- version 12. In Esqueleto, CTEs should be used as a subquery memoization
551+ -- tactic. PostgreSQL treats a materialized CTE as an optimization fence. A
552+ -- MATERIALIZED CTE is always fully calculated, and is not "inlined" with other
553+ -- table joins. Sometimes, this is undesirable, so postgres provides the NOT
554+ -- MATERIALIZED modifier to prevent this behavior, thus enabling it to possibly
555+ -- decide to treat the CTE as any other join.
556+ --
557+ -- Given the above, it is unlikely that this function will be useful, as a
558+ -- normal join should be used instead, but is provided for completeness.
559+ --
560+ -- @
561+ -- select $ do
562+ -- cte <- withNotMaterialized subQuery
563+ -- cteResult <- from cte
564+ -- where_ $ cteResult ...
565+ -- pure cteResult
566+ -- @
567+ --
568+ --
569+ -- For more information on materialized CTEs, see the PostgreSQL manual documentation on
570+ -- [Common Table Expression Materialization](https://www.postgresql.org/docs/14/queries-with.html#id-1.5.6.12.7).
571+ --
572+ -- @since 3.5.14.0
573+ withNotMaterialized :: ( ToAlias a
574+ , ToAliasReference a
575+ , SqlSelect a r
576+ ) => SqlQuery a -> SqlQuery (Ex. From a )
577+ withNotMaterialized query = do
578+ (ret, sideData) <- Q $ W. censor (\ _ -> mempty ) $ W. listen $ unQ query
579+ aliasedValue <- toAlias ret
580+ let aliasedQuery = Q $ W. WriterT $ pure (aliasedValue, sideData)
581+ ident <- newIdentFor (DBName " cte" )
582+ let clause = CommonTableExpressionClause NormalCommonTableExpression (\ _ _ -> " NOT MATERIALIZED " ) ident (\ info -> toRawSql SELECT info aliasedQuery)
583+ Q $ W. tell mempty {sdCteClause = [clause]}
584+ ref <- toAliasReference ident aliasedValue
585+ pure $ Ex. From $ pure (ref, (\ _ info -> (useIdent info ident, mempty )))
586+
502587-- | Ascending order of this field or SqlExpression with nulls coming first.
503588--
504589-- @since 3.5.14.0
0 commit comments