{-|
Module: Squeal.PostgreSQL.Query.Table
Description: intermediate table expressions
Copyright: (c) Eitan Chatav, 2019
Maintainer: eitan@morphism.tech
Stability: experimental

intermediate table expressions
-}

{-# LANGUAGE
    ConstraintKinds
  , DeriveGeneric
  , DerivingStrategies
  , FlexibleContexts
  , FlexibleInstances
  , GADTs
  , GeneralizedNewtypeDeriving
  , LambdaCase
  , MultiParamTypeClasses
  , OverloadedLabels
  , OverloadedStrings
  , QuantifiedConstraints
  , ScopedTypeVariables
  , StandaloneDeriving
  , TypeApplications
  , TypeFamilies
  , DataKinds
  , PolyKinds
  , TypeOperators
  , RankNTypes
  , UndecidableInstances
  #-}

module Squeal.PostgreSQL.Query.Table
  ( -- * Table Expression
    TableExpression (..)
  , from
  , where_
  , groupBy
  , having
  , limit
  , offset
  , lockRows
    -- * Grouping
  , By (..)
  , GroupByClause (..)
  , HavingClause (..)
    -- * Row Locks
  , LockingClause (..)
  , LockStrength (..)
  , Waiting (..)
  ) where

import Control.DeepSeq
import Data.ByteString (ByteString)
import Data.String
import Data.Word
import Generics.SOP hiding (from)
import GHC.TypeLits

import qualified GHC.Generics as GHC

import Squeal.PostgreSQL.Type.Alias
import Squeal.PostgreSQL.Expression.Logic
import Squeal.PostgreSQL.Expression.Sort
import Squeal.PostgreSQL.Query.From
import Squeal.PostgreSQL.Render
import Squeal.PostgreSQL.Type.Schema

-- $setup
-- >>> import Squeal.PostgreSQL

{-----------------------------------------
Table Expressions
-----------------------------------------}

-- | A `TableExpression` computes a table. The table expression contains
-- a `fromClause` that is optionally followed by a `whereClause`,
-- `groupByClause`, `havingClause`, `orderByClause`, `limitClause`
-- `offsetClause` and `lockingClauses`. Trivial table expressions simply refer
-- to a table on disk, a so-called base table, but more complex expressions
-- can be used to modify or combine base tables in various ways.
data TableExpression
  (grp :: Grouping)
  (lat :: FromType)
  (with :: FromType)
  (db :: SchemasType)
  (params :: [NullType])
  (from :: FromType)
    = TableExpression
    { forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
TableExpression grp lat with db params from
-> FromClause lat with db params from
fromClause :: FromClause lat with db params from
    -- ^ A table reference that can be a table name, or a derived table such
    -- as a subquery, a @JOIN@ construct, or complex combinations of these.
    , forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
TableExpression grp lat with db params from
-> [Condition 'Ungrouped lat with db params from]
whereClause :: [Condition 'Ungrouped lat with db params from]
    -- ^ optional search coditions, combined with `.&&`. After the processing
    -- of the `fromClause` is done, each row of the derived virtual table
    -- is checked against the search condition. If the result of the
    -- condition is true, the row is kept in the output table,
    -- otherwise it is discarded. The search condition typically references
    -- at least one column of the table generated in the `fromClause`;
    -- this is not required, but otherwise the WHERE clause will
    -- be fairly useless.
    , forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
TableExpression grp lat with db params from
-> GroupByClause grp from
groupByClause :: GroupByClause grp from
    -- ^ The `groupByClause` is used to group together those rows in a table
    -- that have the same values in all the columns listed. The order in which
    -- the columns are listed does not matter. The effect is to combine each
    -- set of rows having common values into one group row that represents all
    -- rows in the group. This is done to eliminate redundancy in the output
    -- and/or compute aggregates that apply to these groups.
    , forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
TableExpression grp lat with db params from
-> HavingClause grp lat with db params from
havingClause :: HavingClause grp lat with db params from
    -- ^ If a table has been grouped using `groupBy`, but only certain groups
    -- are of interest, the `havingClause` can be used, much like a
    -- `whereClause`, to eliminate groups from the result. Expressions in the
    -- `havingClause` can refer both to grouped expressions and to ungrouped
    -- expressions (which necessarily involve an aggregate function).
    , forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
TableExpression grp lat with db params from
-> [SortExpression grp lat with db params from]
orderByClause :: [SortExpression grp lat with db params from]
    -- ^ The `orderByClause` is for optional sorting. When more than one
    -- `SortExpression` is specified, the later (right) values are used to sort
    -- rows that are equal according to the earlier (left) values.
    , forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
TableExpression grp lat with db params from -> [Word64]
limitClause :: [Word64]
    -- ^ The `limitClause` is combined with `min` to give a limit count
    -- if nonempty. If a limit count is given, no more than that many rows
    -- will be returned (but possibly fewer, if the query itself yields
    -- fewer rows).
    , forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
TableExpression grp lat with db params from -> [Word64]
offsetClause :: [Word64]
    -- ^ The `offsetClause` is combined with `Prelude.+` to give an offset count
    -- if nonempty. The offset count says to skip that many rows before
    -- beginning to return rows. The rows are skipped before the limit count
    -- is applied.
    , forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
TableExpression grp lat with db params from -> [LockingClause from]
lockingClauses :: [LockingClause from]
    -- ^ `lockingClauses` can be added to a table expression with `lockRows`.
    } deriving (forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType) x.
Rep (TableExpression grp lat with db params from) x
-> TableExpression grp lat with db params from
forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType) x.
TableExpression grp lat with db params from
-> Rep (TableExpression grp lat with db params from) x
$cto :: forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType) x.
Rep (TableExpression grp lat with db params from) x
-> TableExpression grp lat with db params from
$cfrom :: forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType) x.
TableExpression grp lat with db params from
-> Rep (TableExpression grp lat with db params from) x
GHC.Generic)

-- | Render a `TableExpression`
instance RenderSQL (TableExpression grp lat with db params from) where
  renderSQL :: TableExpression grp lat with db params from -> ByteString
renderSQL
    (TableExpression FromClause lat with db params from
frm' [Condition 'Ungrouped lat with db params from]
whs' GroupByClause grp from
grps' HavingClause grp lat with db params from
hvs' [SortExpression grp lat with db params from]
srts' [Word64]
lims' [Word64]
offs' [LockingClause from]
lks') = forall a. Monoid a => [a] -> a
mconcat
      [ ByteString
"FROM ", forall sql. RenderSQL sql => sql -> ByteString
renderSQL FromClause lat with db params from
frm'
      , forall {grp :: Grouping} {lat :: FromType} {with :: FromType}
       {db :: SchemasType} {params :: [NullType]} {from :: FromType}
       {null :: PGType -> NullType}.
[Expression grp lat with db params from (null 'PGbool)]
-> ByteString
renderWheres [Condition 'Ungrouped lat with db params from]
whs'
      , forall sql. RenderSQL sql => sql -> ByteString
renderSQL GroupByClause grp from
grps'
      , forall sql. RenderSQL sql => sql -> ByteString
renderSQL HavingClause grp lat with db params from
hvs'
      , forall sql. RenderSQL sql => sql -> ByteString
renderSQL [SortExpression grp lat with db params from]
srts'
      , [Word64] -> ByteString
renderLimits [Word64]
lims'
      , [Word64] -> ByteString
renderOffsets [Word64]
offs'
      , [LockingClause from] -> ByteString
renderLocks [LockingClause from]
lks' ]
      where
        renderWheres :: [Expression grp lat with db params from (null 'PGbool)]
-> ByteString
renderWheres = \case
          [] -> ByteString
""
          Expression grp lat with db params from (null 'PGbool)
wh:[Expression grp lat with db params from (null 'PGbool)]
whs -> ByteString
" WHERE" ByteString -> ByteString -> ByteString
<+> forall sql. RenderSQL sql => sql -> ByteString
renderSQL (forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr forall (null :: PGType -> NullType).
Operator (null 'PGbool) (null 'PGbool) (null 'PGbool)
(.&&) Expression grp lat with db params from (null 'PGbool)
wh [Expression grp lat with db params from (null 'PGbool)]
whs)
        renderLimits :: [Word64] -> ByteString
renderLimits = \case
          [] -> ByteString
""
          [Word64]
lims -> ByteString
" LIMIT" ByteString -> ByteString -> ByteString
<+> forall a. IsString a => String -> a
fromString (forall a. Show a => a -> String
show (forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
minimum [Word64]
lims))
        renderOffsets :: [Word64] -> ByteString
renderOffsets = \case
          [] -> ByteString
""
          [Word64]
offs -> ByteString
" OFFSET" ByteString -> ByteString -> ByteString
<+> forall a. IsString a => String -> a
fromString (forall a. Show a => a -> String
show (forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum [Word64]
offs))
        renderLocks :: [LockingClause from] -> ByteString
renderLocks = forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr (\LockingClause from
l ByteString
b -> ByteString
b ByteString -> ByteString -> ByteString
<+> forall sql. RenderSQL sql => sql -> ByteString
renderSQL LockingClause from
l) ByteString
""

-- | A `from` generates a `TableExpression` from a table reference that can be
-- a table name, or a derived table such as a subquery, a JOIN construct,
-- or complex combinations of these. A `from` may be transformed by `where_`,
-- `groupBy`, `having`, `orderBy`, `limit` and `offset`,
-- using the `Data.Function.&` operator
-- to match the left-to-right sequencing of their placement in SQL.
from
  :: FromClause lat with db params from -- ^ table reference
  -> TableExpression 'Ungrouped lat with db params from
from :: forall (lat :: FromType) (with :: FromType) (db :: SchemasType)
       (params :: [NullType]) (from :: FromType).
FromClause lat with db params from
-> TableExpression 'Ungrouped lat with db params from
from FromClause lat with db params from
tab = forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
FromClause lat with db params from
-> [Condition 'Ungrouped lat with db params from]
-> GroupByClause grp from
-> HavingClause grp lat with db params from
-> [SortExpression grp lat with db params from]
-> [Word64]
-> [Word64]
-> [LockingClause from]
-> TableExpression grp lat with db params from
TableExpression FromClause lat with db params from
tab [] forall {k} (from :: k). GroupByClause 'Ungrouped from
noGroups forall (lat :: FromType) (with :: FromType) (db :: SchemasType)
       (params :: [NullType]) (from :: FromType).
HavingClause 'Ungrouped lat with db params from
NoHaving [] [] [] []

-- | A `where_` is an endomorphism of `TableExpression`s which adds a
-- search condition to the `whereClause`.
where_
  :: Condition 'Ungrouped lat with db params from -- ^ filtering condition
  -> TableExpression grp lat with db params from
  -> TableExpression grp lat with db params from
where_ :: forall (lat :: FromType) (with :: FromType) (db :: SchemasType)
       (params :: [NullType]) (from :: FromType) (grp :: Grouping).
Condition 'Ungrouped lat with db params from
-> TableExpression grp lat with db params from
-> TableExpression grp lat with db params from
where_ Condition 'Ungrouped lat with db params from
wh TableExpression grp lat with db params from
rels = TableExpression grp lat with db params from
rels {whereClause :: [Condition 'Ungrouped lat with db params from]
whereClause = Condition 'Ungrouped lat with db params from
wh forall a. a -> [a] -> [a]
: forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
TableExpression grp lat with db params from
-> [Condition 'Ungrouped lat with db params from]
whereClause TableExpression grp lat with db params from
rels}

-- | A `groupBy` is a transformation of `TableExpression`s which switches
-- its `Grouping` from `Ungrouped` to `Grouped`. Use @groupBy Nil@ to perform
-- a "grand total" aggregation query.
groupBy
  :: SListI bys
  => NP (By from) bys -- ^ grouped columns
  -> TableExpression 'Ungrouped lat with db params from
  -> TableExpression ('Grouped bys) lat with db params from
groupBy :: forall (bys :: [(Symbol, Symbol)]) (from :: FromType)
       (lat :: FromType) (with :: FromType) (db :: SchemasType)
       (params :: [NullType]).
SListI bys =>
NP (By from) bys
-> TableExpression 'Ungrouped lat with db params from
-> TableExpression ('Grouped bys) lat with db params from
groupBy NP (By from) bys
bys TableExpression 'Ungrouped lat with db params from
rels = TableExpression
  { fromClause :: FromClause lat with db params from
fromClause = forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
TableExpression grp lat with db params from
-> FromClause lat with db params from
fromClause TableExpression 'Ungrouped lat with db params from
rels
  , whereClause :: [Condition 'Ungrouped lat with db params from]
whereClause = forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
TableExpression grp lat with db params from
-> [Condition 'Ungrouped lat with db params from]
whereClause TableExpression 'Ungrouped lat with db params from
rels
  , groupByClause :: GroupByClause ('Grouped bys) from
groupByClause = forall (bys :: [(Symbol, Symbol)]) (from :: FromType).
SListI bys =>
NP (By from) bys -> GroupByClause ('Grouped bys) from
group NP (By from) bys
bys
  , havingClause :: HavingClause ('Grouped bys) lat with db params from
havingClause = forall (table :: [(Symbol, Symbol)]) (lat :: FromType)
       (with :: FromType) (db :: SchemasType) (params :: [NullType])
       (from :: FromType).
[Condition ('Grouped table) lat with db params from]
-> HavingClause ('Grouped table) lat with db params from
Having []
  , orderByClause :: [SortExpression ('Grouped bys) lat with db params from]
orderByClause = []
  , limitClause :: [Word64]
limitClause = forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
TableExpression grp lat with db params from -> [Word64]
limitClause TableExpression 'Ungrouped lat with db params from
rels
  , offsetClause :: [Word64]
offsetClause = forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
TableExpression grp lat with db params from -> [Word64]
offsetClause TableExpression 'Ungrouped lat with db params from
rels
  , lockingClauses :: [LockingClause from]
lockingClauses = forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
TableExpression grp lat with db params from -> [LockingClause from]
lockingClauses TableExpression 'Ungrouped lat with db params from
rels
  }

-- | A `having` is an endomorphism of `TableExpression`s which adds a
-- search condition to the `havingClause`.
having
  :: Condition ('Grouped bys) lat with db params from -- ^ having condition
  -> TableExpression ('Grouped bys) lat with db params from
  -> TableExpression ('Grouped bys) lat with db params from
having :: forall (bys :: [(Symbol, Symbol)]) (lat :: FromType)
       (with :: FromType) (db :: SchemasType) (params :: [NullType])
       (from :: FromType).
Condition ('Grouped bys) lat with db params from
-> TableExpression ('Grouped bys) lat with db params from
-> TableExpression ('Grouped bys) lat with db params from
having Condition ('Grouped bys) lat with db params from
hv TableExpression ('Grouped bys) lat with db params from
rels = TableExpression ('Grouped bys) lat with db params from
rels
  { havingClause :: HavingClause ('Grouped bys) lat with db params from
havingClause = case forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
TableExpression grp lat with db params from
-> HavingClause grp lat with db params from
havingClause TableExpression ('Grouped bys) lat with db params from
rels of Having [Condition ('Grouped bys) lat with db params from]
hvs -> forall (table :: [(Symbol, Symbol)]) (lat :: FromType)
       (with :: FromType) (db :: SchemasType) (params :: [NullType])
       (from :: FromType).
[Condition ('Grouped table) lat with db params from]
-> HavingClause ('Grouped table) lat with db params from
Having (Condition ('Grouped bys) lat with db params from
hvforall a. a -> [a] -> [a]
:[Condition ('Grouped bys) lat with db params from]
hvs) }

instance OrderBy (TableExpression grp) grp where
  orderBy :: forall (lat :: FromType) (with :: FromType) (db :: SchemasType)
       (params :: [NullType]) (from :: FromType).
[SortExpression grp lat with db params from]
-> TableExpression grp lat with db params from
-> TableExpression grp lat with db params from
orderBy [SortExpression grp lat with db params from]
srts TableExpression grp lat with db params from
rels = TableExpression grp lat with db params from
rels {orderByClause :: [SortExpression grp lat with db params from]
orderByClause = forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
TableExpression grp lat with db params from
-> [SortExpression grp lat with db params from]
orderByClause TableExpression grp lat with db params from
rels forall a. [a] -> [a] -> [a]
++ [SortExpression grp lat with db params from]
srts}

-- | A `limit` is an endomorphism of `TableExpression`s which adds to the
-- `limitClause`.
limit
  :: Word64 -- ^ limit parameter
  -> TableExpression grp lat with db params from
  -> TableExpression grp lat with db params from
limit :: forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
Word64
-> TableExpression grp lat with db params from
-> TableExpression grp lat with db params from
limit Word64
lim TableExpression grp lat with db params from
rels = TableExpression grp lat with db params from
rels {limitClause :: [Word64]
limitClause = Word64
lim forall a. a -> [a] -> [a]
: forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
TableExpression grp lat with db params from -> [Word64]
limitClause TableExpression grp lat with db params from
rels}

-- | An `offset` is an endomorphism of `TableExpression`s which adds to the
-- `offsetClause`.
offset
  :: Word64 -- ^ offset parameter
  -> TableExpression grp lat with db params from
  -> TableExpression grp lat with db params from
offset :: forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
Word64
-> TableExpression grp lat with db params from
-> TableExpression grp lat with db params from
offset Word64
off TableExpression grp lat with db params from
rels = TableExpression grp lat with db params from
rels {offsetClause :: [Word64]
offsetClause = Word64
off forall a. a -> [a] -> [a]
: forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
TableExpression grp lat with db params from -> [Word64]
offsetClause TableExpression grp lat with db params from
rels}

{- | Add a `LockingClause` to a `TableExpression`.
Multiple `LockingClause`s can be written if it is necessary
to specify different locking behavior for different tables.
If the same table is mentioned (or implicitly affected)
by more than one locking clause, then it is processed
as if it was only specified by the strongest one.
Similarly, a table is processed as `NoWait` if that is specified
in any of the clauses affecting it. Otherwise, it is processed
as `SkipLocked` if that is specified in any of the clauses affecting it.
Further, a `LockingClause` cannot be added to a grouped table expression.
-}
lockRows
  :: LockingClause from -- ^ row-level lock
  -> TableExpression 'Ungrouped lat with db params from
  -> TableExpression 'Ungrouped lat with db params from
lockRows :: forall (from :: FromType) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]).
LockingClause from
-> TableExpression 'Ungrouped lat with db params from
-> TableExpression 'Ungrouped lat with db params from
lockRows LockingClause from
lck TableExpression 'Ungrouped lat with db params from
tab = TableExpression 'Ungrouped lat with db params from
tab {lockingClauses :: [LockingClause from]
lockingClauses = LockingClause from
lck forall a. a -> [a] -> [a]
: forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType).
TableExpression grp lat with db params from -> [LockingClause from]
lockingClauses TableExpression 'Ungrouped lat with db params from
tab}

{-----------------------------------------
Grouping
-----------------------------------------}

-- | `By`s are used in `groupBy` to reference a list of columns which are then
-- used to group together those rows in a table that have the same values
-- in all the columns listed. @By \#col@ will reference an unambiguous
-- column @col@; otherwise @By2 (\#tab \! \#col)@ will reference a table
-- qualified column @tab.col@.
data By
    (from :: FromType)
    (by :: (Symbol,Symbol)) where
    By1
      :: (HasUnique table from columns, Has column columns ty)
      => Alias column
      -> By from '(table, column)
    By2
      :: (Has table from columns, Has column columns ty)
      => Alias table
      -> Alias column
      -> By from '(table, column)
deriving instance Show (By from by)
deriving instance Eq (By from by)
deriving instance Ord (By from by)
instance RenderSQL (By from by) where
  renderSQL :: By from by -> ByteString
renderSQL = \case
    By1 Alias column
column -> forall sql. RenderSQL sql => sql -> ByteString
renderSQL Alias column
column
    By2 Alias table
rel Alias column
column -> forall sql. RenderSQL sql => sql -> ByteString
renderSQL Alias table
rel forall a. Semigroup a => a -> a -> a
<> ByteString
"." forall a. Semigroup a => a -> a -> a
<> forall sql. RenderSQL sql => sql -> ByteString
renderSQL Alias column
column

instance (HasUnique rel rels cols, Has col cols ty, by ~ '(rel, col))
  => IsLabel col (By rels by) where fromLabel :: By rels by
fromLabel = forall (table :: Symbol) (from :: FromType)
       (columns :: [(Symbol, NullType)]) (column :: Symbol)
       (ty :: NullType).
(HasUnique table from columns, Has column columns ty) =>
Alias column -> By from '(table, column)
By1 forall (x :: Symbol) a. IsLabel x a => a
fromLabel
instance (HasUnique rel rels cols, Has col cols ty, bys ~ '[ '(rel, col)])
  => IsLabel col (NP (By rels) bys) where fromLabel :: NP (By rels) bys
fromLabel = forall (table :: Symbol) (from :: FromType)
       (columns :: [(Symbol, NullType)]) (column :: Symbol)
       (ty :: NullType).
(HasUnique table from columns, Has column columns ty) =>
Alias column -> By from '(table, column)
By1 forall (x :: Symbol) a. IsLabel x a => a
fromLabel forall {k} (a :: k -> *) (x :: k) (xs :: [k]).
a x -> NP a xs -> NP a (x : xs)
:* forall {k} (a :: k -> *). NP a '[]
Nil
instance (Has rel rels cols, Has col cols ty, by ~ '(rel, col))
  => IsQualified rel col (By rels by) where ! :: Alias rel -> Alias col -> By rels by
(!) = forall (table :: Symbol) (from :: FromType)
       (columns :: [(Symbol, NullType)]) (column :: Symbol)
       (ty :: NullType).
(Has table from columns, Has column columns ty) =>
Alias table -> Alias column -> By from '(table, column)
By2
instance (Has rel rels cols, Has col cols ty, bys ~ '[ '(rel, col)])
  => IsQualified rel col (NP (By rels) bys) where
    Alias rel
rel ! :: Alias rel -> Alias col -> NP (By rels) bys
! Alias col
col = forall (table :: Symbol) (from :: FromType)
       (columns :: [(Symbol, NullType)]) (column :: Symbol)
       (ty :: NullType).
(Has table from columns, Has column columns ty) =>
Alias table -> Alias column -> By from '(table, column)
By2 Alias rel
rel Alias col
col forall {k} (a :: k -> *) (x :: k) (xs :: [k]).
a x -> NP a xs -> NP a (x : xs)
:* forall {k} (a :: k -> *). NP a '[]
Nil

-- | A `GroupByClause` indicates the `Grouping` of a `TableExpression`.
newtype GroupByClause grp from = UnsafeGroupByClause
  { forall {k} {k} (grp :: k) (from :: k).
GroupByClause grp from -> ByteString
renderGroupByClause :: ByteString }
  deriving stock (forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
forall k (grp :: k) k (from :: k) x.
Rep (GroupByClause grp from) x -> GroupByClause grp from
forall k (grp :: k) k (from :: k) x.
GroupByClause grp from -> Rep (GroupByClause grp from) x
$cto :: forall k (grp :: k) k (from :: k) x.
Rep (GroupByClause grp from) x -> GroupByClause grp from
$cfrom :: forall k (grp :: k) k (from :: k) x.
GroupByClause grp from -> Rep (GroupByClause grp from) x
GHC.Generic,Int -> GroupByClause grp from -> ShowS
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
forall k (grp :: k) k (from :: k).
Int -> GroupByClause grp from -> ShowS
forall k (grp :: k) k (from :: k).
[GroupByClause grp from] -> ShowS
forall k (grp :: k) k (from :: k). GroupByClause grp from -> String
showList :: [GroupByClause grp from] -> ShowS
$cshowList :: forall k (grp :: k) k (from :: k).
[GroupByClause grp from] -> ShowS
show :: GroupByClause grp from -> String
$cshow :: forall k (grp :: k) k (from :: k). GroupByClause grp from -> String
showsPrec :: Int -> GroupByClause grp from -> ShowS
$cshowsPrec :: forall k (grp :: k) k (from :: k).
Int -> GroupByClause grp from -> ShowS
Show,GroupByClause grp from -> GroupByClause grp from -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
forall k (grp :: k) k (from :: k).
GroupByClause grp from -> GroupByClause grp from -> Bool
/= :: GroupByClause grp from -> GroupByClause grp from -> Bool
$c/= :: forall k (grp :: k) k (from :: k).
GroupByClause grp from -> GroupByClause grp from -> Bool
== :: GroupByClause grp from -> GroupByClause grp from -> Bool
$c== :: forall k (grp :: k) k (from :: k).
GroupByClause grp from -> GroupByClause grp from -> Bool
Eq,GroupByClause grp from -> GroupByClause grp from -> Bool
GroupByClause grp from -> GroupByClause grp from -> Ordering
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
forall k (grp :: k) k (from :: k). Eq (GroupByClause grp from)
forall k (grp :: k) k (from :: k).
GroupByClause grp from -> GroupByClause grp from -> Bool
forall k (grp :: k) k (from :: k).
GroupByClause grp from -> GroupByClause grp from -> Ordering
forall k (grp :: k) k (from :: k).
GroupByClause grp from
-> GroupByClause grp from -> GroupByClause grp from
min :: GroupByClause grp from
-> GroupByClause grp from -> GroupByClause grp from
$cmin :: forall k (grp :: k) k (from :: k).
GroupByClause grp from
-> GroupByClause grp from -> GroupByClause grp from
max :: GroupByClause grp from
-> GroupByClause grp from -> GroupByClause grp from
$cmax :: forall k (grp :: k) k (from :: k).
GroupByClause grp from
-> GroupByClause grp from -> GroupByClause grp from
>= :: GroupByClause grp from -> GroupByClause grp from -> Bool
$c>= :: forall k (grp :: k) k (from :: k).
GroupByClause grp from -> GroupByClause grp from -> Bool
> :: GroupByClause grp from -> GroupByClause grp from -> Bool
$c> :: forall k (grp :: k) k (from :: k).
GroupByClause grp from -> GroupByClause grp from -> Bool
<= :: GroupByClause grp from -> GroupByClause grp from -> Bool
$c<= :: forall k (grp :: k) k (from :: k).
GroupByClause grp from -> GroupByClause grp from -> Bool
< :: GroupByClause grp from -> GroupByClause grp from -> Bool
$c< :: forall k (grp :: k) k (from :: k).
GroupByClause grp from -> GroupByClause grp from -> Bool
compare :: GroupByClause grp from -> GroupByClause grp from -> Ordering
$ccompare :: forall k (grp :: k) k (from :: k).
GroupByClause grp from -> GroupByClause grp from -> Ordering
Ord)
  deriving newtype (GroupByClause grp from -> ()
forall a. (a -> ()) -> NFData a
forall k (grp :: k) k (from :: k). GroupByClause grp from -> ()
rnf :: GroupByClause grp from -> ()
$crnf :: forall k (grp :: k) k (from :: k). GroupByClause grp from -> ()
NFData)
instance RenderSQL (GroupByClause grp from) where
  renderSQL :: GroupByClause grp from -> ByteString
renderSQL = forall {k} {k} (grp :: k) (from :: k).
GroupByClause grp from -> ByteString
renderGroupByClause
noGroups :: GroupByClause 'Ungrouped from
noGroups :: forall {k} (from :: k). GroupByClause 'Ungrouped from
noGroups = forall {k} {k} (grp :: k) (from :: k).
ByteString -> GroupByClause grp from
UnsafeGroupByClause ByteString
""
group
  :: SListI bys
  => NP (By from) bys
  -> GroupByClause ('Grouped bys) from
group :: forall (bys :: [(Symbol, Symbol)]) (from :: FromType).
SListI bys =>
NP (By from) bys -> GroupByClause ('Grouped bys) from
group NP (By from) bys
bys = forall {k} {k} (grp :: k) (from :: k).
ByteString -> GroupByClause grp from
UnsafeGroupByClause forall a b. (a -> b) -> a -> b
$ case NP (By from) bys
bys of
  NP (By from) bys
Nil -> ByteString
""
  NP (By from) bys
_ -> ByteString
" GROUP BY" ByteString -> ByteString -> ByteString
<+> forall {k} (xs :: [k]) (expression :: k -> *).
SListI xs =>
(forall (x :: k). expression x -> ByteString)
-> NP expression xs -> ByteString
renderCommaSeparated forall sql. RenderSQL sql => sql -> ByteString
renderSQL NP (By from) bys
bys

-- | A `HavingClause` is used to eliminate groups that are not of interest.
-- An `Ungrouped` `TableExpression` may only use `NoHaving` while a `Grouped`
-- `TableExpression` must use `Having` whose conditions are combined with
-- `.&&`.
data HavingClause grp lat with db params from where
  NoHaving :: HavingClause 'Ungrouped lat with db params from
  Having
    :: [Condition ('Grouped bys) lat with db params from]
    -> HavingClause ('Grouped bys) lat with db params from
deriving instance Show (HavingClause grp lat with db params from)
deriving instance Eq (HavingClause grp lat with db params from)
deriving instance Ord (HavingClause grp lat with db params from)

-- | Render a `HavingClause`.
instance RenderSQL (HavingClause grp lat with db params from) where
  renderSQL :: HavingClause grp lat with db params from -> ByteString
renderSQL = \case
    HavingClause grp lat with db params from
NoHaving -> ByteString
""
    Having [] -> ByteString
""
    Having [Condition ('Grouped bys) lat with db params from]
conditions ->
      ByteString
" HAVING" ByteString -> ByteString -> ByteString
<+> [ByteString] -> ByteString
commaSeparated (forall sql. RenderSQL sql => sql -> ByteString
renderSQL forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Condition ('Grouped bys) lat with db params from]
conditions)

{- |
If specific tables are named in a locking clause,
then only rows coming from those tables are locked;
any other tables used in the `Squeal.PostgreSQL.Query.Select.select` are simply read as usual.
A locking clause with a `Nil` table list affects all tables used in the statement.
If a locking clause is applied to a `view` or `subquery`,
it affects all tables used in the `view` or `subquery`.
However, these clauses do not apply to `Squeal.PostgreSQL.Query.With.with` queries referenced by the primary query.
If you want row locking to occur within a `Squeal.PostgreSQL.Query.With.with` query,
specify a `LockingClause` within the `Squeal.PostgreSQL.Query.With.with` query.
-}
data LockingClause from where
  For
    :: HasAll tabs from tables
    => LockStrength -- ^ lock strength
    -> NP Alias tabs -- ^ table list
    -> Waiting -- ^ wait or not
    -> LockingClause from
instance RenderSQL (LockingClause from) where
  renderSQL :: LockingClause from -> ByteString
renderSQL (For LockStrength
str NP Alias tabs
tabs Waiting
wt) =
    ByteString
"FOR" ByteString -> ByteString -> ByteString
<+> forall sql. RenderSQL sql => sql -> ByteString
renderSQL LockStrength
str
    forall a. Semigroup a => a -> a -> a
<> case NP Alias tabs
tabs of
        NP Alias tabs
Nil -> ByteString
""
        NP Alias tabs
_ -> ByteString
" OF" ByteString -> ByteString -> ByteString
<+> forall sql. RenderSQL sql => sql -> ByteString
renderSQL NP Alias tabs
tabs
    forall a. Semigroup a => a -> a -> a
<> forall sql. RenderSQL sql => sql -> ByteString
renderSQL Waiting
wt

{- |
Row-level locks, which are listed as below with the contexts
in which they are used automatically by PostgreSQL.
Note that a transaction can hold conflicting locks on the same row,
even in different subtransactions; but other than that,
two transactions can never hold conflicting locks on the same row.
Row-level locks do not affect data querying;
they block only writers and lockers to the same row.
Row-level locks are released at transaction end or during savepoint rollback.
-}
data LockStrength
  = Update
  {- ^ `For` `Update` causes the rows retrieved by the `Squeal.PostgreSQL.Query.Select.select` statement
  to be locked as though for update. This prevents them from being locked,
  modified or deleted by other transactions until the current transaction ends.
  That is, other transactions that attempt `Squeal.PostgreSQL.Manipulation.Update.update`, `Squeal.PostgreSQL.Manipulation.Delete.deleteFrom`,
  `Squeal.PostgreSQL.Query.Select.select` `For` `Update`, `Squeal.PostgreSQL.Query.Select.select` `For` `NoKeyUpdate`,
  `Squeal.PostgreSQL.Query.Select.select` `For` `Share` or `Squeal.PostgreSQL.Query.Select.select` `For` `KeyShare` of these rows will be blocked
  until the current transaction ends; conversely, `Squeal.PostgreSQL.Query.Select.select` `For` `Update` will wait
  for a concurrent transaction that has run any of those commands on the same row,
  and will then lock and return the updated row (or no row, if the row was deleted).
  Within a `Squeal.PostgreSQL.Session.Transaction.RepeatableRead` or `Squeal.PostgreSQL.Session.Transaction.Serializable` transaction, however, an error will be
  thrown if a row to be locked has changed since the transaction started.

  The `For` `Update` lock mode is also acquired by any `Squeal.PostgreSQL.Manipulation.Delete.deleteFrom` a row,
  and also by an `Update` that modifies the values on certain columns.
  Currently, the set of columns considered for the `Squeal.PostgreSQL.Manipulation.Update.update` case are those
  that have a unique index on them that can be used in a foreign key
  (so partial indexes and expressional indexes are not considered),
  but this may change in the future.-}
  | NoKeyUpdate
  {- | Behaves similarly to `For` `Update`, except that the lock acquired is weaker:
  this lock will not block `Squeal.PostgreSQL.Query.Select.select` `For` `KeyShare` commands that attempt to acquire
  a lock on the same rows. This lock mode is also acquired by any `Squeal.PostgreSQL.Manipulation.Update.update`
  that does not acquire a `For` `Update` lock.-}
  | Share
  {- | Behaves similarly to `For` `Share`, except that the lock is weaker:
  `Squeal.PostgreSQL.Query.Select.select` `For` `Update` is blocked, but not `Squeal.PostgreSQL.Query.Select.select` `For` `NoKeyUpdate`.
  A key-shared lock blocks other transactions from performing
  `Squeal.PostgreSQL.Manipulation.Delete.deleteFrom` or any `Squeal.PostgreSQL.Manipulation.Update.update` that changes the key values,
  but not other `Update`, and neither does it prevent `Squeal.PostgreSQL.Query.Select.select` `For` `NoKeyUpdate`,
  `Squeal.PostgreSQL.Query.Select.select` `For` `Share`, or `Squeal.PostgreSQL.Query.Select.select` `For` `KeyShare`.-}
  | KeyShare
  deriving (LockStrength -> LockStrength -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: LockStrength -> LockStrength -> Bool
$c/= :: LockStrength -> LockStrength -> Bool
== :: LockStrength -> LockStrength -> Bool
$c== :: LockStrength -> LockStrength -> Bool
Eq, Eq LockStrength
LockStrength -> LockStrength -> Bool
LockStrength -> LockStrength -> Ordering
LockStrength -> LockStrength -> LockStrength
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: LockStrength -> LockStrength -> LockStrength
$cmin :: LockStrength -> LockStrength -> LockStrength
max :: LockStrength -> LockStrength -> LockStrength
$cmax :: LockStrength -> LockStrength -> LockStrength
>= :: LockStrength -> LockStrength -> Bool
$c>= :: LockStrength -> LockStrength -> Bool
> :: LockStrength -> LockStrength -> Bool
$c> :: LockStrength -> LockStrength -> Bool
<= :: LockStrength -> LockStrength -> Bool
$c<= :: LockStrength -> LockStrength -> Bool
< :: LockStrength -> LockStrength -> Bool
$c< :: LockStrength -> LockStrength -> Bool
compare :: LockStrength -> LockStrength -> Ordering
$ccompare :: LockStrength -> LockStrength -> Ordering
Ord, Int -> LockStrength -> ShowS
[LockStrength] -> ShowS
LockStrength -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [LockStrength] -> ShowS
$cshowList :: [LockStrength] -> ShowS
show :: LockStrength -> String
$cshow :: LockStrength -> String
showsPrec :: Int -> LockStrength -> ShowS
$cshowsPrec :: Int -> LockStrength -> ShowS
Show, ReadPrec [LockStrength]
ReadPrec LockStrength
Int -> ReadS LockStrength
ReadS [LockStrength]
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [LockStrength]
$creadListPrec :: ReadPrec [LockStrength]
readPrec :: ReadPrec LockStrength
$creadPrec :: ReadPrec LockStrength
readList :: ReadS [LockStrength]
$creadList :: ReadS [LockStrength]
readsPrec :: Int -> ReadS LockStrength
$creadsPrec :: Int -> ReadS LockStrength
Read, Int -> LockStrength
LockStrength -> Int
LockStrength -> [LockStrength]
LockStrength -> LockStrength
LockStrength -> LockStrength -> [LockStrength]
LockStrength -> LockStrength -> LockStrength -> [LockStrength]
forall a.
(a -> a)
-> (a -> a)
-> (Int -> a)
-> (a -> Int)
-> (a -> [a])
-> (a -> a -> [a])
-> (a -> a -> [a])
-> (a -> a -> a -> [a])
-> Enum a
enumFromThenTo :: LockStrength -> LockStrength -> LockStrength -> [LockStrength]
$cenumFromThenTo :: LockStrength -> LockStrength -> LockStrength -> [LockStrength]
enumFromTo :: LockStrength -> LockStrength -> [LockStrength]
$cenumFromTo :: LockStrength -> LockStrength -> [LockStrength]
enumFromThen :: LockStrength -> LockStrength -> [LockStrength]
$cenumFromThen :: LockStrength -> LockStrength -> [LockStrength]
enumFrom :: LockStrength -> [LockStrength]
$cenumFrom :: LockStrength -> [LockStrength]
fromEnum :: LockStrength -> Int
$cfromEnum :: LockStrength -> Int
toEnum :: Int -> LockStrength
$ctoEnum :: Int -> LockStrength
pred :: LockStrength -> LockStrength
$cpred :: LockStrength -> LockStrength
succ :: LockStrength -> LockStrength
$csucc :: LockStrength -> LockStrength
Enum, forall x. Rep LockStrength x -> LockStrength
forall x. LockStrength -> Rep LockStrength x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep LockStrength x -> LockStrength
$cfrom :: forall x. LockStrength -> Rep LockStrength x
GHC.Generic)
instance RenderSQL LockStrength where
  renderSQL :: LockStrength -> ByteString
renderSQL = \case
    LockStrength
Update -> ByteString
"UPDATE"
    LockStrength
NoKeyUpdate -> ByteString
"NO KEY UPDATE"
    LockStrength
Share -> ByteString
"SHARE"
    LockStrength
KeyShare -> ByteString
"KEY SHARE"

-- | To prevent the operation from `Waiting` for other transactions to commit,
-- use either the `NoWait` or `SkipLocked` option.
data Waiting
  = Wait
  -- ^ wait for other transactions to commit
  | NoWait
  -- ^ reports an error, rather than waiting
  | SkipLocked
  -- ^ any selected rows that cannot be immediately locked are skipped
  deriving (Waiting -> Waiting -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Waiting -> Waiting -> Bool
$c/= :: Waiting -> Waiting -> Bool
== :: Waiting -> Waiting -> Bool
$c== :: Waiting -> Waiting -> Bool
Eq, Eq Waiting
Waiting -> Waiting -> Bool
Waiting -> Waiting -> Ordering
Waiting -> Waiting -> Waiting
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: Waiting -> Waiting -> Waiting
$cmin :: Waiting -> Waiting -> Waiting
max :: Waiting -> Waiting -> Waiting
$cmax :: Waiting -> Waiting -> Waiting
>= :: Waiting -> Waiting -> Bool
$c>= :: Waiting -> Waiting -> Bool
> :: Waiting -> Waiting -> Bool
$c> :: Waiting -> Waiting -> Bool
<= :: Waiting -> Waiting -> Bool
$c<= :: Waiting -> Waiting -> Bool
< :: Waiting -> Waiting -> Bool
$c< :: Waiting -> Waiting -> Bool
compare :: Waiting -> Waiting -> Ordering
$ccompare :: Waiting -> Waiting -> Ordering
Ord, Int -> Waiting -> ShowS
[Waiting] -> ShowS
Waiting -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Waiting] -> ShowS
$cshowList :: [Waiting] -> ShowS
show :: Waiting -> String
$cshow :: Waiting -> String
showsPrec :: Int -> Waiting -> ShowS
$cshowsPrec :: Int -> Waiting -> ShowS
Show, ReadPrec [Waiting]
ReadPrec Waiting
Int -> ReadS Waiting
ReadS [Waiting]
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [Waiting]
$creadListPrec :: ReadPrec [Waiting]
readPrec :: ReadPrec Waiting
$creadPrec :: ReadPrec Waiting
readList :: ReadS [Waiting]
$creadList :: ReadS [Waiting]
readsPrec :: Int -> ReadS Waiting
$creadsPrec :: Int -> ReadS Waiting
Read, Int -> Waiting
Waiting -> Int
Waiting -> [Waiting]
Waiting -> Waiting
Waiting -> Waiting -> [Waiting]
Waiting -> Waiting -> Waiting -> [Waiting]
forall a.
(a -> a)
-> (a -> a)
-> (Int -> a)
-> (a -> Int)
-> (a -> [a])
-> (a -> a -> [a])
-> (a -> a -> [a])
-> (a -> a -> a -> [a])
-> Enum a
enumFromThenTo :: Waiting -> Waiting -> Waiting -> [Waiting]
$cenumFromThenTo :: Waiting -> Waiting -> Waiting -> [Waiting]
enumFromTo :: Waiting -> Waiting -> [Waiting]
$cenumFromTo :: Waiting -> Waiting -> [Waiting]
enumFromThen :: Waiting -> Waiting -> [Waiting]
$cenumFromThen :: Waiting -> Waiting -> [Waiting]
enumFrom :: Waiting -> [Waiting]
$cenumFrom :: Waiting -> [Waiting]
fromEnum :: Waiting -> Int
$cfromEnum :: Waiting -> Int
toEnum :: Int -> Waiting
$ctoEnum :: Int -> Waiting
pred :: Waiting -> Waiting
$cpred :: Waiting -> Waiting
succ :: Waiting -> Waiting
$csucc :: Waiting -> Waiting
Enum, forall x. Rep Waiting x -> Waiting
forall x. Waiting -> Rep Waiting x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep Waiting x -> Waiting
$cfrom :: forall x. Waiting -> Rep Waiting x
GHC.Generic)
instance RenderSQL Waiting where
  renderSQL :: Waiting -> ByteString
renderSQL = \case
    Waiting
Wait -> ByteString
""
    Waiting
NoWait -> ByteString
" NOWAIT"
    Waiting
SkipLocked -> ByteString
" SKIP LOCKED"