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

This module provides functions for combining queries with UNION and OR operations.
-}
module IHP.QueryBuilder.Union
( queryUnion
, queryUnionList
, queryOr
) where

import IHP.Prelude
import IHP.ModelSupport
import IHP.QueryBuilder.Types
import IHP.QueryBuilder.Compiler (query)
import qualified Hasql.DynamicStatements.Snippet as Snippet

-- | Merges the results of two query builders.
--
-- Take a look at 'queryOr'  as well, as this might be a bit shorter.
--
-- __Example:__ Return all pages owned by the user or owned by the users team.
--
-- > let userPages = query @Page |> filterWhere (#ownerId, currentUserId)
-- > let teamPages = query @Page |> filterWhere (#teamId, currentTeamId)
-- > pages <- queryUnion userPages teamPages |> fetch
-- > -- (SELECT * FROM pages WHERE owner_id = '..') UNION (SELECT * FROM pages WHERE team_id = '..')
queryUnion :: (HasQueryBuilder queryBuilderProvider joinRegister, HasQueryBuilder r joinRegister') => queryBuilderProvider model -> r model -> NoJoinQueryBuilderWrapper model
queryUnion :: forall {k} {k} (queryBuilderProvider :: Symbol -> *)
       (joinRegister :: k) (r :: Symbol -> *) (joinRegister' :: k)
       (model :: Symbol).
(HasQueryBuilder queryBuilderProvider joinRegister,
 HasQueryBuilder r joinRegister') =>
queryBuilderProvider model
-> r model -> NoJoinQueryBuilderWrapper model
queryUnion queryBuilderProvider model
firstQueryBuilderProvider r model
secondQueryBuilderProvider = QueryBuilder model -> NoJoinQueryBuilderWrapper model
forall (table :: Symbol).
QueryBuilder table -> NoJoinQueryBuilderWrapper table
NoJoinQueryBuilderWrapper (UnionQueryBuilder { QueryBuilder model
firstQueryBuilder :: QueryBuilder model
firstQueryBuilder :: QueryBuilder model
firstQueryBuilder, QueryBuilder model
secondQueryBuilder :: QueryBuilder model
secondQueryBuilder :: QueryBuilder model
secondQueryBuilder })
    where
        firstQueryBuilder :: QueryBuilder model
firstQueryBuilder = queryBuilderProvider model -> QueryBuilder model
forall {k} (queryBuilderProvider :: Symbol -> *)
       (joinRegister :: k) (table :: Symbol).
HasQueryBuilder queryBuilderProvider joinRegister =>
queryBuilderProvider table -> QueryBuilder table
forall (table :: Symbol).
queryBuilderProvider table -> QueryBuilder table
getQueryBuilder queryBuilderProvider model
firstQueryBuilderProvider
        secondQueryBuilder :: QueryBuilder model
secondQueryBuilder = r model -> QueryBuilder model
forall {k} (queryBuilderProvider :: Symbol -> *)
       (joinRegister :: k) (table :: Symbol).
HasQueryBuilder queryBuilderProvider joinRegister =>
queryBuilderProvider table -> QueryBuilder table
forall (table :: Symbol). r table -> QueryBuilder table
getQueryBuilder r model
secondQueryBuilderProvider


{-# INLINE queryUnion #-}

-- | Like 'queryUnion', but applied on all the elements on the list
--
-- >  action ProjectsAction = do
-- >      let values :: [(ProjectType, Int)] = [(ProjectTypeOngoing, 3), (ProjectTypeNotStarted, 2)]
-- >
-- >          valuePairToCondition :: (ProjectType, Int) -> QueryBuilder "projects"
-- >          valuePairToCondition (projectType, participants) =
-- >              query @Project
-- >                  |> filterWhere (#projectType, projectType)
-- >                  |> filterWhere (#participants, participants)
-- >
-- >          theQuery = queryUnionList (map valuePairToCondition values)
-- >
-- >      projects <- fetch theQuery
-- >      render IndexView { .. }
queryUnionList :: forall table. (Table (GetModelByTableName table), KnownSymbol table, GetTableName (GetModelByTableName table) ~ table) => [QueryBuilder table] -> QueryBuilder table
-- For empty list, create a condition that is always false: id <> id (which is always false for non-null)
queryUnionList :: forall (table :: Symbol).
(Table (GetModelByTableName table), KnownSymbol table,
 GetTableName (GetModelByTableName table) ~ table) =>
[QueryBuilder table] -> QueryBuilder table
queryUnionList [] = FilterByQueryBuilder { queryBuilder :: QueryBuilder table
queryBuilder = forall model (table :: Symbol).
(table ~ GetTableName model, Table model, DefaultScope table) =>
QueryBuilder table
query @(GetModelByTableName table) @table, queryFilter :: (Text, FilterOperator, Snippet)
queryFilter = (Text
"id", FilterOperator
NotEqOp, Text -> Snippet
Snippet.sql Text
"id"), applyLeft :: Maybe Text
applyLeft = Maybe Text
forall a. Maybe a
Nothing, applyRight :: Maybe Text
applyRight = Maybe Text
forall a. Maybe a
Nothing }
queryUnionList (QueryBuilder table
firstQueryBuilder:QueryBuilder table
secondQueryBuilder:[]) = UnionQueryBuilder { QueryBuilder table
firstQueryBuilder :: QueryBuilder table
firstQueryBuilder :: QueryBuilder table
firstQueryBuilder, QueryBuilder table
secondQueryBuilder :: QueryBuilder table
secondQueryBuilder :: QueryBuilder table
secondQueryBuilder }
queryUnionList (QueryBuilder table
firstQueryBuilder:[QueryBuilder table]
rest) = UnionQueryBuilder { QueryBuilder table
firstQueryBuilder :: QueryBuilder table
firstQueryBuilder :: QueryBuilder table
firstQueryBuilder, secondQueryBuilder :: QueryBuilder table
secondQueryBuilder = forall (table :: Symbol).
(Table (GetModelByTableName table), KnownSymbol table,
 GetTableName (GetModelByTableName table) ~ table) =>
[QueryBuilder table] -> QueryBuilder table
queryUnionList @table [QueryBuilder table]
rest }


-- | Adds an @a OR b@ condition
--
-- __Example:__ Return all pages owned by the user or public.
--
-- > query @Page
-- >     |> queryOr
-- >         (filterWhere (#createdBy, currentUserId))
-- >         (filterWhere (#public, True))
-- >     |> fetch
-- > -- SELECT * FROM pages WHERE created_by = '..' OR public = True
queryOr :: (HasQueryBuilder queryBuilderProvider joinRegister, HasQueryBuilder queryBuilderProvider'' joinRegister'', HasQueryBuilder queryBuilderProvider''' joinRegister''') => (queryBuilderProvider model -> queryBuilderProvider''' model) -> (queryBuilderProvider model -> queryBuilderProvider'' model) -> queryBuilderProvider model -> queryBuilderProvider model
queryOr :: forall {k} {k} {k} (queryBuilderProvider :: Symbol -> *)
       (joinRegister :: k) (queryBuilderProvider'' :: Symbol -> *)
       (joinRegister'' :: k) (queryBuilderProvider''' :: Symbol -> *)
       (joinRegister''' :: k) (model :: Symbol).
(HasQueryBuilder queryBuilderProvider joinRegister,
 HasQueryBuilder queryBuilderProvider'' joinRegister'',
 HasQueryBuilder queryBuilderProvider''' joinRegister''') =>
(queryBuilderProvider model -> queryBuilderProvider''' model)
-> (queryBuilderProvider model -> queryBuilderProvider'' model)
-> queryBuilderProvider model
-> queryBuilderProvider model
queryOr queryBuilderProvider model -> queryBuilderProvider''' model
firstQuery queryBuilderProvider model -> queryBuilderProvider'' model
secondQuery queryBuilderProvider model
queryBuilder = QueryBuilder model -> queryBuilderProvider model
forall {k} (queryBuilderProvider :: Symbol -> *)
       (joinRegister :: k) (table :: Symbol).
HasQueryBuilder queryBuilderProvider joinRegister =>
QueryBuilder table -> queryBuilderProvider table
forall (table :: Symbol).
QueryBuilder table -> queryBuilderProvider table
injectQueryBuilder
    (UnionQueryBuilder {
        firstQueryBuilder :: QueryBuilder model
firstQueryBuilder = queryBuilderProvider''' model -> QueryBuilder model
forall {k} (queryBuilderProvider :: Symbol -> *)
       (joinRegister :: k) (table :: Symbol).
HasQueryBuilder queryBuilderProvider joinRegister =>
queryBuilderProvider table -> QueryBuilder table
forall (table :: Symbol).
queryBuilderProvider''' table -> QueryBuilder table
getQueryBuilder (queryBuilderProvider''' model -> QueryBuilder model)
-> queryBuilderProvider''' model -> QueryBuilder model
forall a b. (a -> b) -> a -> b
$ queryBuilderProvider model -> queryBuilderProvider''' model
firstQuery queryBuilderProvider model
queryBuilder,
        secondQueryBuilder :: QueryBuilder model
secondQueryBuilder = queryBuilderProvider'' model -> QueryBuilder model
forall {k} (queryBuilderProvider :: Symbol -> *)
       (joinRegister :: k) (table :: Symbol).
HasQueryBuilder queryBuilderProvider joinRegister =>
queryBuilderProvider table -> QueryBuilder table
forall (table :: Symbol).
queryBuilderProvider'' table -> QueryBuilder table
getQueryBuilder (queryBuilderProvider'' model -> QueryBuilder model)
-> queryBuilderProvider'' model -> QueryBuilder model
forall a b. (a -> b) -> a -> b
$ queryBuilderProvider model -> queryBuilderProvider'' model
secondQuery queryBuilderProvider model
queryBuilder}
    )
{-# INLINE queryOr #-}