{-# LANGUAGE BangPatterns, TypeFamilies, DataKinds, PolyKinds, TypeApplications, ScopedTypeVariables, ConstraintKinds, TypeOperators, GADTs, UndecidableInstances, StandaloneDeriving, FunctionalDependencies, FlexibleContexts, InstanceSigs, AllowAmbiguousTypes, DeriveAnyClass #-}
{-|
Module: IHP.QueryBuilder.Order
Description: ORDER BY, LIMIT, OFFSET, and DISTINCT operations for QueryBuilder
Copyright: (c) digitally induced GmbH, 2020

This module provides functions for ordering, limiting, and deduplicating query results.
-}
module IHP.QueryBuilder.Order
( orderBy
, orderByAsc
, orderByDesc
, limit
, offset
, distinct
, distinctOn
) where

import IHP.Prelude
import IHP.ModelSupport
import IHP.QueryBuilder.Types
import IHP.QueryBuilder.Compiler (qualifiedColumnName)

-- | Adds an @ORDER BY .. ASC@ to your query.
--
-- Use 'orderByDesc' for descending order.
--
-- __Example:__ Fetch the 10 oldest books.
--
-- > query @Book
-- >     |> orderBy #createdAt -- >     |> limit 10
-- >     |> fetch
-- > -- SELECT * FROM books LIMIT 10 ORDER BY created_at ASC
orderByAsc :: forall name model table value. (KnownSymbol table, KnownSymbol name, HasField name model value, model ~ GetModelByTableName table, Table model) => Proxy name -> QueryBuilder table -> QueryBuilder table
orderByAsc :: forall (name :: Symbol) model (table :: Symbol) value.
(KnownSymbol table, KnownSymbol name, HasField name model value,
 model ~ GetModelByTableName table, Table model) =>
Proxy name -> QueryBuilder table -> QueryBuilder table
orderByAsc !Proxy name
name (QueryBuilder SQLQuery
sq) =
    SQLQuery -> QueryBuilder table
forall (table :: Symbol). SQLQuery -> QueryBuilder table
QueryBuilder SQLQuery
sq { orderByClause = orderByClause sq <> [OrderByClause { orderByColumn = columnName, orderByDirection = Asc }] }
    where
        columnName :: Text
columnName = Text -> Text -> Text
qualifiedColumnName (forall record. Table record => Text
tableName @model) (forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @name)
{-# INLINE orderByAsc #-}

-- | Adds an @ORDER BY .. DESC@ to your query.
--
-- Use 'orderBy' for ascending order.
--
-- __Example:__ Fetch the 10 newest projects (ordered by creation time).
--
-- > query @Project
-- >     |> orderByDesc #createdAt
-- >     |> limit 10
-- >     |> fetch
-- > -- SELECT * FROM projects LIMIT 10 ORDER BY created_at DESC
orderByDesc :: forall name model table value. (KnownSymbol table, KnownSymbol name, HasField name model value, model ~ GetModelByTableName table, Table model) => Proxy name -> QueryBuilder table -> QueryBuilder table
orderByDesc :: forall (name :: Symbol) model (table :: Symbol) value.
(KnownSymbol table, KnownSymbol name, HasField name model value,
 model ~ GetModelByTableName table, Table model) =>
Proxy name -> QueryBuilder table -> QueryBuilder table
orderByDesc !Proxy name
name (QueryBuilder SQLQuery
sq) =
    SQLQuery -> QueryBuilder table
forall (table :: Symbol). SQLQuery -> QueryBuilder table
QueryBuilder SQLQuery
sq { orderByClause = orderByClause sq <> [OrderByClause { orderByColumn = columnName, orderByDirection = Desc }] }
    where
        columnName :: Text
columnName = Text -> Text -> Text
qualifiedColumnName (forall record. Table record => Text
tableName @model) (forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @name)
{-# INLINE orderByDesc #-}

-- | Alias for 'orderByAsc'
orderBy :: (KnownSymbol table, KnownSymbol name, HasField name model value, model ~ GetModelByTableName table, Table model) => Proxy name -> QueryBuilder table -> QueryBuilder table
orderBy :: forall (table :: Symbol) (name :: Symbol) model value.
(KnownSymbol table, KnownSymbol name, HasField name model value,
 model ~ GetModelByTableName table, Table model) =>
Proxy name -> QueryBuilder table -> QueryBuilder table
orderBy !Proxy name
name = Proxy name -> QueryBuilder table -> QueryBuilder table
forall (name :: Symbol) model (table :: Symbol) value.
(KnownSymbol table, KnownSymbol name, HasField name model value,
 model ~ GetModelByTableName table, Table model) =>
Proxy name -> QueryBuilder table -> QueryBuilder table
orderByAsc Proxy name
name
{-# INLINE orderBy #-}

-- | Adds an @LIMIT ..@ to your query.
--
--
-- __Example:__ Fetch 10 posts
--
-- > query @Post
-- >     |> limit 10
-- >     |> fetch
-- > -- SELECT * FROM posts LIMIT 10
limit :: Int -> QueryBuilder model -> QueryBuilder model
limit :: forall (model :: Symbol).
Int -> QueryBuilder model -> QueryBuilder model
limit !Int
queryLimit (QueryBuilder SQLQuery
sq) =
    SQLQuery -> QueryBuilder model
forall (table :: Symbol). SQLQuery -> QueryBuilder table
QueryBuilder SQLQuery
sq { limitClause = Just queryLimit }
{-# INLINE limit #-}

-- | Adds an @OFFSET ..@ to your query. Most often used together with @LIMIT...@
--
--
-- __Example:__ Fetch posts 10-20
--
-- > query @Post
-- >     |> limit 10
-- >     |> offset 10
-- >     |> fetch
-- > -- SELECT * FROM posts LIMIT 10 OFFSET 10
offset :: Int -> QueryBuilder model -> QueryBuilder model
offset :: forall (model :: Symbol).
Int -> QueryBuilder model -> QueryBuilder model
offset !Int
queryOffset (QueryBuilder SQLQuery
sq) =
    SQLQuery -> QueryBuilder model
forall (table :: Symbol). SQLQuery -> QueryBuilder table
QueryBuilder SQLQuery
sq { offsetClause = Just queryOffset }
{-# INLINE offset #-}

-- | Adds a @DISTINCT@ to your query.
--
-- Use 'distinct' to remove all duplicate rows from the result
--
-- __Example:__ Fetch distinct books
--
-- > query @Book
-- >     |> distinct
-- >     |> fetch
-- > -- SELECT DISTINCT * FROM books
distinct :: QueryBuilder table -> QueryBuilder table
distinct :: forall (table :: Symbol). QueryBuilder table -> QueryBuilder table
distinct (QueryBuilder SQLQuery
sq) =
    SQLQuery -> QueryBuilder table
forall (table :: Symbol). SQLQuery -> QueryBuilder table
QueryBuilder SQLQuery
sq { distinctClause = True }
{-# INLINE distinct #-}

-- | Adds an @DISTINCT ON .. to your query.
--
-- Use 'distinctOn' to return a single row for each distinct value provided.
--
-- __Example:__ Fetch one book for each categoryId field
--
-- > query @Book
-- >     |> distinctOn #categoryId
-- >     |> fetch
-- > -- SELECT DISTINCT ON (category_id) * FROM books
distinctOn :: forall name model value table. (KnownSymbol table, KnownSymbol name, HasField name model value, model ~ GetModelByTableName table, Table model) => Proxy name -> QueryBuilder table -> QueryBuilder table
distinctOn :: forall (name :: Symbol) model value (table :: Symbol).
(KnownSymbol table, KnownSymbol name, HasField name model value,
 model ~ GetModelByTableName table, Table model) =>
Proxy name -> QueryBuilder table -> QueryBuilder table
distinctOn !Proxy name
name (QueryBuilder SQLQuery
sq) =
    SQLQuery -> QueryBuilder table
forall (table :: Symbol). SQLQuery -> QueryBuilder table
QueryBuilder SQLQuery
sq { distinctOnClause = Just columnName }
    where
        columnName :: Text
columnName = Text -> Text -> Text
qualifiedColumnName (forall record. Table record => Text
tableName @model) (forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @name)
{-# INLINE distinctOn #-}