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

This module provides functions for joining tables in queries.
-}
module IHP.QueryBuilder.Join
( innerJoin
, innerJoinThirdTable
, labelResults
) where

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

-- | Joins a table to an existing QueryBuilder (or something holding a QueryBuilder) on the specified columns. Example:
-- >    query @Posts
-- > |> innerJoin @Users (#author, #id)
-- > -- SELECT users.* FROM users INNER JOIN posts ON users.id = posts.author ...
innerJoin :: forall model' table' name' value' model table name value queryBuilderProvider joinRegister.
                            (
                                KnownSymbol name,
                                KnownSymbol table,
                                HasField name model value,
                                KnownSymbol name',
                                KnownSymbol table',
                                HasQueryBuilder queryBuilderProvider joinRegister,
                                ModelList joinRegister,
                                HasField name' model' value',
                                value ~ value',
                                model ~ GetModelByTableName table,
                                table' ~ GetTableName model'
                            ) => (Proxy name, Proxy name') -> queryBuilderProvider table -> JoinQueryBuilderWrapper (ConsModelList model' joinRegister) table
innerJoin :: forall {k1} model' (table' :: Symbol) (name' :: Symbol) value'
       model (table :: Symbol) (name :: Symbol) value
       (queryBuilderProvider :: Symbol -> *) (joinRegister :: k1).
(KnownSymbol name, KnownSymbol table, HasField name model value,
 KnownSymbol name', KnownSymbol table',
 HasQueryBuilder queryBuilderProvider joinRegister,
 ModelList joinRegister, HasField name' model' value',
 value ~ value', model ~ GetModelByTableName table,
 table' ~ GetTableName model') =>
(Proxy name, Proxy name')
-> queryBuilderProvider table
-> JoinQueryBuilderWrapper
     (ConsModelList model' joinRegister) table
innerJoin (Proxy name
name, Proxy name'
name') queryBuilderProvider table
queryBuilderProvider = QueryBuilder table
-> JoinQueryBuilderWrapper
     (ConsModelList model' joinRegister) table
forall {k} (queryBuilderProvider :: Symbol -> *)
       (joinRegister :: k) (table :: Symbol).
HasQueryBuilder queryBuilderProvider joinRegister =>
QueryBuilder table -> queryBuilderProvider table
forall (table :: Symbol).
QueryBuilder table
-> JoinQueryBuilderWrapper
     (ConsModelList model' joinRegister) table
injectQueryBuilder (QueryBuilder table
 -> JoinQueryBuilderWrapper
      (ConsModelList model' joinRegister) table)
-> QueryBuilder table
-> JoinQueryBuilderWrapper
     (ConsModelList model' joinRegister) table
forall a b. (a -> b) -> a -> b
$ QueryBuilder table -> Join -> QueryBuilder table
forall (table :: Symbol).
QueryBuilder table -> Join -> QueryBuilder table
JoinQueryBuilder (queryBuilderProvider table -> QueryBuilder table
forall {k} (queryBuilderProvider :: Symbol -> *)
       (joinRegister :: k) (table :: Symbol).
HasQueryBuilder queryBuilderProvider joinRegister =>
queryBuilderProvider table -> QueryBuilder table
forall (table :: Symbol).
queryBuilderProvider table -> QueryBuilder table
getQueryBuilder queryBuilderProvider table
queryBuilderProvider) (Join -> QueryBuilder table) -> Join -> QueryBuilder table
forall a b. (a -> b) -> a -> b
$ Text -> Text -> Text -> Join
Join Text
joinTableName Text
leftJoinColumn Text
rightJoinColumn
    where
        baseTableName :: Text
baseTableName = forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @table
        joinTableName :: Text
joinTableName = forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @table'
        leftJoinColumn :: Text
leftJoinColumn = Text -> Text -> Text
qualifiedColumnName Text
baseTableName (forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @name)
        rightJoinColumn :: Text
rightJoinColumn = Text -> Text
fieldNameToColumnName (forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @name')
{-# INLINE innerJoin #-}

-- | Index the values from a table with values of a field from a table joined by 'innerJoin' or 'innerJoinThirdTable'. Useful to get, e.g., the tags to a set of posts in such a way that the assignment of tags to posts is preserved.
--
--
-- __Example:__ Fetch a list of all comments, each paired with the id of the post it belongs to.
--
-- > labeledTags <-
-- >  query @Tag
-- >     |> innerJoin @Tagging (#id, #tagId)
-- >     |> innerJoinThirdTable @Post @Tagging (#id, #postId)
-- >     |> labelResults @Post #id
-- >     |> fetch
-- > -- SELECT posts.id, tags.* FROM comments INNER JOIN taggings ON tags.id = taggings.tagId INNER JOIN posts ON posts.id = taggings.postId
--
-- labeledTags is then a list of type ['LabeledData' (Id' "posts") Tag] such that "LabeledData postId tag" is contained in that list if "tag" is a tag of the post with id postId.
--
labelResults :: forall foreignModel baseModel foreignTable baseTable name value queryBuilderProvider joinRegister.
                (
                    KnownSymbol foreignTable,
                    KnownSymbol baseTable,
                    foreignTable ~ GetTableName foreignModel,
                    baseModel ~ GetModelByTableName baseTable,
                    HasField name foreignModel value,
                    HasQueryBuilder queryBuilderProvider joinRegister,
                    KnownSymbol name,
                    IsJoined foreignModel joinRegister
                ) => Proxy name -> queryBuilderProvider baseTable -> LabeledQueryBuilderWrapper foreignTable name value baseTable
labelResults :: forall {k1} foreignModel baseModel (foreignTable :: Symbol)
       (baseTable :: Symbol) (name :: Symbol) value
       (queryBuilderProvider :: Symbol -> *) (joinRegister :: k1).
(KnownSymbol foreignTable, KnownSymbol baseTable,
 foreignTable ~ GetTableName foreignModel,
 baseModel ~ GetModelByTableName baseTable,
 HasField name foreignModel value,
 HasQueryBuilder queryBuilderProvider joinRegister,
 KnownSymbol name, IsJoined foreignModel joinRegister) =>
Proxy name
-> queryBuilderProvider baseTable
-> LabeledQueryBuilderWrapper foreignTable name value baseTable
labelResults Proxy name
name queryBuilderProvider baseTable
queryBuilderProvider = QueryBuilder baseTable
-> LabeledQueryBuilderWrapper foreignTable name value baseTable
forall {k} {k1} {k2} (foreignTable :: k) (indexColumn :: k1)
       (indexValue :: k2) (table :: Symbol).
QueryBuilder table
-> LabeledQueryBuilderWrapper
     foreignTable indexColumn indexValue table
LabeledQueryBuilderWrapper (QueryBuilder baseTable
 -> LabeledQueryBuilderWrapper foreignTable name value baseTable)
-> QueryBuilder baseTable
-> LabeledQueryBuilderWrapper foreignTable name value baseTable
forall a b. (a -> b) -> a -> b
$ queryBuilderProvider baseTable -> QueryBuilder baseTable
forall {k} (queryBuilderProvider :: Symbol -> *)
       (joinRegister :: k) (table :: Symbol).
HasQueryBuilder queryBuilderProvider joinRegister =>
queryBuilderProvider table -> QueryBuilder table
forall (table :: Symbol).
queryBuilderProvider table -> QueryBuilder table
getQueryBuilder queryBuilderProvider baseTable
queryBuilderProvider

-- | Joins a table on a column held by a previously joined table. Example:
-- > query @Posts
-- > |> innerJoin @Users (#author, #id)
-- > |> innerJoinThirdTable @City @Users (#id, #homeTown)
-- > -- SELECT posts.* FROM posts INNER JOIN users ON posts.author = users.id INNER JOIN cities ON user.home_town = cities.id
--
innerJoinThirdTable :: forall model model' name name' value value' table table' baseTable baseModel queryBuilderProvider joinRegister.
                        (
                            KnownSymbol name,
                            KnownSymbol table,
                            HasField name model value,
                            KnownSymbol name',
                            KnownSymbol table',
                            HasQueryBuilder queryBuilderProvider joinRegister,
                            ModelList joinRegister,
                            HasField name' model' value',
                            value ~ value',
                            table ~ GetTableName model,
                            table' ~ GetTableName model',
                            baseModel ~ GetModelByTableName baseTable
                        ) => (Proxy name, Proxy name') -> queryBuilderProvider baseTable -> JoinQueryBuilderWrapper (ConsModelList model joinRegister) baseTable
innerJoinThirdTable :: forall {k1} model model' (name :: Symbol) (name' :: Symbol) value
       value' (table :: Symbol) (table' :: Symbol) (baseTable :: Symbol)
       baseModel (queryBuilderProvider :: Symbol -> *)
       (joinRegister :: k1).
(KnownSymbol name, KnownSymbol table, HasField name model value,
 KnownSymbol name', KnownSymbol table',
 HasQueryBuilder queryBuilderProvider joinRegister,
 ModelList joinRegister, HasField name' model' value',
 value ~ value', table ~ GetTableName model,
 table' ~ GetTableName model',
 baseModel ~ GetModelByTableName baseTable) =>
(Proxy name, Proxy name')
-> queryBuilderProvider baseTable
-> JoinQueryBuilderWrapper
     (ConsModelList model joinRegister) baseTable
innerJoinThirdTable (Proxy name
name, Proxy name'
name') queryBuilderProvider baseTable
queryBuilderProvider = QueryBuilder baseTable
-> JoinQueryBuilderWrapper
     (ConsModelList model joinRegister) baseTable
forall {k} (queryBuilderProvider :: Symbol -> *)
       (joinRegister :: k) (table :: Symbol).
HasQueryBuilder queryBuilderProvider joinRegister =>
QueryBuilder table -> queryBuilderProvider table
forall (table :: Symbol).
QueryBuilder table
-> JoinQueryBuilderWrapper (ConsModelList model joinRegister) table
injectQueryBuilder (QueryBuilder baseTable
 -> JoinQueryBuilderWrapper
      (ConsModelList model joinRegister) baseTable)
-> QueryBuilder baseTable
-> JoinQueryBuilderWrapper
     (ConsModelList model joinRegister) baseTable
forall a b. (a -> b) -> a -> b
$ QueryBuilder baseTable -> Join -> QueryBuilder baseTable
forall (table :: Symbol).
QueryBuilder table -> Join -> QueryBuilder table
JoinQueryBuilder (queryBuilderProvider baseTable -> QueryBuilder baseTable
forall {k} (queryBuilderProvider :: Symbol -> *)
       (joinRegister :: k) (table :: Symbol).
HasQueryBuilder queryBuilderProvider joinRegister =>
queryBuilderProvider table -> QueryBuilder table
forall (table :: Symbol).
queryBuilderProvider table -> QueryBuilder table
getQueryBuilder queryBuilderProvider baseTable
queryBuilderProvider) (Join -> QueryBuilder baseTable) -> Join -> QueryBuilder baseTable
forall a b. (a -> b) -> a -> b
$ Text -> Text -> Text -> Join
Join Text
joinTableName Text
leftJoinColumn Text
rightJoinColumn
     where
        baseTableName :: Text
baseTableName = forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @table'
        joinTableName :: Text
joinTableName = forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @table
        leftJoinColumn :: Text
leftJoinColumn = Text -> Text -> Text
qualifiedColumnName Text
baseTableName (forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @name')
        rightJoinColumn :: Text
rightJoinColumn = Text -> Text
fieldNameToColumnName (forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @name)
{-# INLINE innerJoinThirdTable #-}