squeal-postgresql-0.6.0.0: src/Squeal/PostgreSQL/Query/With.hs
{-|
Module: Squeal.PostgreSQL.Query.With
Description: with statements
Copyright: (c) Eitan Chatav, 2019
Maintainer: eitan@morphism.tech
Stability: experimental
with statements
-}
{-# LANGUAGE
ConstraintKinds
, DeriveGeneric
, DerivingStrategies
, FlexibleContexts
, FlexibleInstances
, GADTs
, GeneralizedNewtypeDeriving
, LambdaCase
, MultiParamTypeClasses
, OverloadedLabels
, OverloadedStrings
, QuantifiedConstraints
, ScopedTypeVariables
, StandaloneDeriving
, TypeApplications
, TypeFamilies
, TypeInType
, TypeOperators
, RankNTypes
, UndecidableInstances
#-}
module Squeal.PostgreSQL.Query.With
( -- ** With
With (..)
, CommonTableExpression (..)
, withRecursive
) where
import Data.Quiver.Functor
import GHC.TypeLits
import Squeal.PostgreSQL.Type.Alias
import Squeal.PostgreSQL.Query
import Squeal.PostgreSQL.Type.List
import Squeal.PostgreSQL.Render
import Squeal.PostgreSQL.Type.Schema
-- $setup
-- >>> import Squeal.PostgreSQL
-- | `with` provides a way to write auxiliary statements for use in a larger query.
-- These statements, referred to as `CommonTableExpression`s, can be thought of as
-- defining temporary tables that exist just for one query.
class With statement where
with
:: Path (CommonTableExpression statement db params) with0 with1
-- ^ common table expressions
-> statement with1 db params row
-- ^ larger query
-> statement with0 db params row
instance With (Query lat) where
with Done query = query
with ctes query = UnsafeQuery $
"WITH" <+> commaSeparated (qtoList renderSQL ctes) <+> renderSQL query
{- |
>>> import Data.Monoid (Sum (..))
>>> import Data.Int (Int64)
>>> :{
let
query :: Query_ schema () (Sum Int64)
query = withRecursive
( values_ ((1 & astype int) `as` #n)
`unionAll`
select_ ((#n + 1) `as` #n)
(from (common #t) & where_ (#n .< 100)) `as` #t )
( select_ (fromNull 0 (sum_ (All #n)) `as` #getSum) (from (common #t) & groupBy Nil))
in printSQL query
:}
WITH RECURSIVE "t" AS ((SELECT * FROM (VALUES (((1 :: int4) :: int))) AS t ("n")) UNION ALL (SELECT ("n" + (1 :: int4)) AS "n" FROM "t" AS "t" WHERE ("n" < (100 :: int4)))) SELECT COALESCE(sum(ALL "n"), (0 :: int8)) AS "getSum" FROM "t" AS "t"
-}
withRecursive
:: Aliased (Query lat (recursive ': with) db params) recursive
-- ^ recursive query
-> Query lat (recursive ': with) db params row
-- ^ larger query
-> Query lat with db params row
withRecursive (recursive `As` cte) query = UnsafeQuery $
"WITH RECURSIVE" <+> renderSQL cte
<+> "AS" <+> parenthesized (renderSQL recursive)
<+> renderSQL query
-- | A `CommonTableExpression` is an auxiliary statement in a `with` clause.
data CommonTableExpression statement
(db :: SchemasType)
(params :: [NullType])
(with0 :: FromType)
(with1 :: FromType) where
CommonTableExpression
:: Aliased (statement with db params) (cte ::: common)
-- ^ aliased statement
-> CommonTableExpression statement db params with (cte ::: common ': with)
instance
( KnownSymbol cte
, with1 ~ (cte ::: common ': with)
) => Aliasable cte
(statement with db params common)
(CommonTableExpression statement db params with with1) where
statement `as` cte = CommonTableExpression (statement `as` cte)
instance
( KnownSymbol cte
, with1 ~ (cte ::: common ': with)
) => Aliasable cte
(statement with db params common)
(Path (CommonTableExpression statement db params) with with1) where
statement `as` cte = qsingle (statement `as` cte)
instance (forall c s p r. RenderSQL (statement c s p r)) => RenderSQL
(CommonTableExpression statement db params with0 with1) where
renderSQL (CommonTableExpression (statement `As` cte)) =
renderSQL cte <+> "AS" <+> parenthesized (renderSQL statement)