{-|
Module: Squeal.PostgreSQL.Expression.Sort
Description: sort expressions
Copyright: (c) Eitan Chatav, 2019
Maintainer: eitan@morphism.tech
Stability: experimental

sort expressions
-}

{-# LANGUAGE
    DataKinds
  , FlexibleInstances
  , FunctionalDependencies
  , GADTs
  , LambdaCase
  , MultiParamTypeClasses
  , OverloadedStrings
  , StandaloneDeriving
#-}

module Squeal.PostgreSQL.Expression.Sort
  ( -- * Sort
    SortExpression (..)
  , OrderBy (..)
  ) where

import Squeal.PostgreSQL.Expression
import Squeal.PostgreSQL.Render
import Squeal.PostgreSQL.Type.Schema

-- | `SortExpression`s are used by `orderBy` to optionally sort the results
-- of a `Squeal.PostgreSQL.Query.Query`. `Asc` or `Desc`
-- set the sort direction of a `NotNull` result
-- column to ascending or descending. Ascending order puts smaller values
-- first, where "smaller" is defined in terms of the
-- `Squeal.PostgreSQL.Expression.Comparison..<` operator. Similarly,
-- descending order is determined with the
-- `Squeal.PostgreSQL.Expression.Comparison..>` operator. `AscNullsFirst`,
-- `AscNullsLast`, `DescNullsFirst` and `DescNullsLast` options are used to
-- determine whether nulls appear before or after non-null values in the sort
-- ordering of a `Null` result column.
data SortExpression grp lat with db params from where
  Asc
    :: Expression grp lat with db params from ('NotNull ty)
    -- ^ sort by
    -> SortExpression grp lat with db params from
  Desc
    :: Expression grp lat with db params from ('NotNull ty)
    -- ^ sort by
    -> SortExpression grp lat with db params from
  AscNullsFirst
    :: Expression grp lat with db params from  ('Null ty)
    -- ^ sort by
    -> SortExpression grp lat with db params from
  AscNullsLast
    :: Expression grp lat with db params from  ('Null ty)
    -- ^ sort by
    -> SortExpression grp lat with db params from
  DescNullsFirst
    :: Expression grp lat with db params from  ('Null ty)
    -- ^ sort by
    -> SortExpression grp lat with db params from
  DescNullsLast
    :: Expression grp lat with db params from  ('Null ty)
    -- ^ sort by
    -> SortExpression grp lat with db params from
deriving instance Show (SortExpression grp lat with db params from)
instance RenderSQL (SortExpression grp lat with db params from) where
  renderSQL :: SortExpression grp lat with db params from -> ByteString
renderSQL = \case
    Asc Expression grp lat with db params from ('NotNull ty)
expression -> Expression grp lat with db params from ('NotNull ty) -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL Expression grp lat with db params from ('NotNull ty)
expression ByteString -> ByteString -> ByteString
<+> ByteString
"ASC"
    Desc Expression grp lat with db params from ('NotNull ty)
expression -> Expression grp lat with db params from ('NotNull ty) -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL Expression grp lat with db params from ('NotNull ty)
expression ByteString -> ByteString -> ByteString
<+> ByteString
"DESC"
    AscNullsFirst Expression grp lat with db params from ('Null ty)
expression -> Expression grp lat with db params from ('Null ty) -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL Expression grp lat with db params from ('Null ty)
expression
      ByteString -> ByteString -> ByteString
<+> ByteString
"ASC NULLS FIRST"
    DescNullsFirst Expression grp lat with db params from ('Null ty)
expression -> Expression grp lat with db params from ('Null ty) -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL Expression grp lat with db params from ('Null ty)
expression
      ByteString -> ByteString -> ByteString
<+> ByteString
"DESC NULLS FIRST"
    AscNullsLast Expression grp lat with db params from ('Null ty)
expression -> Expression grp lat with db params from ('Null ty) -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL Expression grp lat with db params from ('Null ty)
expression ByteString -> ByteString -> ByteString
<+> ByteString
"ASC NULLS LAST"
    DescNullsLast Expression grp lat with db params from ('Null ty)
expression -> Expression grp lat with db params from ('Null ty) -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL Expression grp lat with db params from ('Null ty)
expression ByteString -> ByteString -> ByteString
<+> ByteString
"DESC NULLS LAST"
instance RenderSQL [SortExpression grp lat with db params from] where
  renderSQL :: [SortExpression grp lat with db params from] -> ByteString
renderSQL = \case
    [] -> ByteString
""
    [SortExpression grp lat with db params from]
srts -> ByteString
" ORDER BY"
      ByteString -> ByteString -> ByteString
<+> [ByteString] -> ByteString
commaSeparated (SortExpression grp lat with db params from -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL (SortExpression grp lat with db params from -> ByteString)
-> [SortExpression grp lat with db params from] -> [ByteString]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [SortExpression grp lat with db params from]
srts)

{- |
The `orderBy` clause causes the result rows of a `Squeal.PostgreSQL.Query.TableExpression`
to be sorted according to the specified `SortExpression`(s).
If two rows are equal according to the leftmost expression,
they are compared according to the next expression and so on.
If they are equal according to all specified expressions,
they are returned in an implementation-dependent order.

You can also control the order in which rows are processed by window functions
using `orderBy` within `Squeal.PostgreSQL.Query.Over`.
-}
class OrderBy expr grp | expr -> grp where
  orderBy
    :: [SortExpression grp lat with db params from]
      -- ^ sorts
    -> expr lat with db params from
    -> expr lat with db params from