packages feed

poppy-1.0.0: test/Schema/Include/Author.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoFieldSelectors #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wno-redundant-constraints #-}

module Schema.Include.Author
  ( AuthorInclude (..)
  , AuthorWith (..)
  , AuthorWithPicked (..)
  , AuthorPosts
  , AuthorResult
  , AuthorRead
  , LoadAuthor (..)
  , toAuthorWithPicked
  , PostInclude (..)
  , PostWith (..)
  , PostWithPicked (..)
  , PostAuthor
  , PostResult
  , PostRead
  , LoadPost (..)
  , toPostWithPicked
  )
where

import Poppy.Internal.Generated
  ( Db,
    IncludeFor,
    Load (..),
    Skip (..),
    Skipped,
    ValidEdge,
    skipped,
    OmitSelect (..),
    requireRelated,
    findByIn,
    indexByPk,
    indexHasMany,
    lookupByPk,
    lookupGroups
  )
import qualified Schema.Author as Author
import Schema.Author (AuthorPicked, AuthorRow (..), AuthorSelect, AuthorTable, toAuthorPicked)
import qualified Schema.Post as Post
import Schema.Post (PostPicked, PostRow (..), PostSelect, PostTable, toPostPicked)

data AuthorInclude posts = AuthorInclude
  { posts :: posts
  }
  deriving (Show, Eq)

data AuthorWith posts = AuthorWith
  { author :: AuthorRow,
    posts :: AuthorPosts posts
  }

deriving instance (Eq AuthorRow, Eq (AuthorPosts posts)) => Eq (AuthorWith posts)
deriving instance (Show AuthorRow, Show (AuthorPosts posts)) => Show (AuthorWith posts)

data AuthorWithPicked posts = AuthorWithPicked
  { author :: AuthorPicked,
    posts :: AuthorPosts posts
  }

deriving instance (Eq AuthorPicked, Eq (AuthorPosts posts)) => Eq (AuthorWithPicked posts)
deriving instance (Show AuthorPicked, Show (AuthorPosts posts)) => Show (AuthorWithPicked posts)

toAuthorWithPicked :: AuthorSelect -> AuthorWith posts -> AuthorWithPicked posts
toAuthorWithPicked select_ nested =
  AuthorWithPicked
    { author = toAuthorPicked select_ nested.author,
      posts = nested.posts
    }

type family AuthorPosts edge where
  AuthorPosts Skip = Skipped "posts" [PostRow]
  AuthorPosts (Load PostTable include) = [PostResult include]

type family AuthorResult include where
  AuthorResult () = AuthorRow
  AuthorResult (AuthorInclude posts) = AuthorWith posts

type family AuthorRead include select where
  AuthorRead () OmitSelect = AuthorRow
  AuthorRead () AuthorSelect = AuthorPicked
  AuthorRead (AuthorInclude posts) OmitSelect = AuthorWith posts
  AuthorRead (AuthorInclude posts) AuthorSelect = AuthorWithPicked posts

data PostInclude author = PostInclude
  { author :: author
  }
  deriving (Show, Eq)

data PostWith author = PostWith
  { post :: PostRow,
    author :: PostAuthor author
  }

deriving instance (Eq PostRow, Eq (PostAuthor author)) => Eq (PostWith author)
deriving instance (Show PostRow, Show (PostAuthor author)) => Show (PostWith author)

data PostWithPicked author = PostWithPicked
  { post :: PostPicked,
    author :: PostAuthor author
  }

deriving instance (Eq PostPicked, Eq (PostAuthor author)) => Eq (PostWithPicked author)
deriving instance (Show PostPicked, Show (PostAuthor author)) => Show (PostWithPicked author)

toPostWithPicked :: PostSelect -> PostWith author -> PostWithPicked author
toPostWithPicked select_ nested =
  PostWithPicked
    { post = toPostPicked select_ nested.post,
      author = nested.author
    }

type family PostAuthor edge where
  PostAuthor Skip = Skipped "author" AuthorRow
  PostAuthor (Load AuthorTable include) = AuthorResult include

type family PostResult include where
  PostResult () = PostRow
  PostResult (PostInclude author) = PostWith author

type family PostRead include select where
  PostRead () OmitSelect = PostRow
  PostRead () PostSelect = PostPicked
  PostRead (PostInclude author) OmitSelect = PostWith author
  PostRead (PostInclude author) PostSelect = PostWithPicked author

class LoadAuthorPosts edge where
  loadAuthorPosts :: edge -> [AuthorRow] -> Db [AuthorPosts edge]

instance LoadAuthorPosts Skip where
  loadAuthorPosts Skip roots = pure (map (const skipped) roots)

instance LoadAuthorPosts (Load PostTable ()) where
  loadAuthorPosts edge roots = do
    rows <- findByIn @PostTable @PostRow Post.postAuthorId (map (.id) roots) edge.where_ edge.orderBy_ edge.take_
    let grouped = indexHasMany (.authorId) rows
    pure [lookupGroups root.id grouped | root <- roots]

instance (LoadPost author) => LoadAuthorPosts (Load PostTable (PostInclude author)) where
  loadAuthorPosts edge roots = do
    rows <- findByIn @PostTable @PostRow Post.postAuthorId (map (.id) roots) edge.where_ edge.orderBy_ edge.take_
    loaded <- loadPost edge.include_ rows
    let grouped = indexHasMany ((.authorId) . (.post)) loaded
    pure [lookupGroups root.id grouped | root <- roots]

instance {-# OVERLAPPABLE #-} (ValidEdge "Post" edge) => LoadAuthorPosts edge where
  loadAuthorPosts _ roots = pure (map (const skipped) roots)

class LoadAuthor posts where
  loadAuthor :: AuthorInclude posts -> [AuthorRow] -> Db [AuthorWith posts]

instance (LoadAuthorPosts posts, ValidEdge "Post" posts) => LoadAuthor posts where
  loadAuthor include roots = do
    postsLoaded <- loadAuthorPosts include.posts roots
    pure
      [ AuthorWith
          { author = root,
            posts = postsLoaded !! n
          }
      | (n, root) <- zip [0 :: Int ..] roots
      ]

instance (ValidEdge "Post" posts) => IncludeFor "Author" (AuthorInclude posts)

class LoadPostAuthor edge where
  loadPostAuthor :: edge -> [PostRow] -> Db [PostAuthor edge]

instance LoadPostAuthor Skip where
  loadPostAuthor Skip roots = pure (map (const skipped) roots)

instance LoadPostAuthor (Load AuthorTable ()) where
  loadPostAuthor edge roots = do
    rows <- findByIn @AuthorTable @AuthorRow Author.authorId (map (.authorId) roots) edge.where_ edge.orderBy_ edge.take_
    let indexed = indexByPk (.id) rows
    pure [requireRelated "author" (lookupByPk root.authorId indexed) | root <- roots]

instance (LoadAuthor posts) => LoadPostAuthor (Load AuthorTable (AuthorInclude posts)) where
  loadPostAuthor edge roots = do
    rows <- findByIn @AuthorTable @AuthorRow Author.authorId (map (.authorId) roots) edge.where_ edge.orderBy_ edge.take_
    loaded <- loadAuthor edge.include_ rows
    let indexed = indexByPk ((.id) . (.author)) loaded
    pure [requireRelated "author" (lookupByPk root.authorId indexed) | root <- roots]

instance {-# OVERLAPPABLE #-} (ValidEdge "Author" edge) => LoadPostAuthor edge where
  loadPostAuthor _ roots = pure (map (const skipped) roots)

class LoadPost author where
  loadPost :: PostInclude author -> [PostRow] -> Db [PostWith author]

instance (LoadPostAuthor author, ValidEdge "Author" author) => LoadPost author where
  loadPost include roots = do
    authorLoaded <- loadPostAuthor include.author roots
    pure
      [ PostWith
          { post = root,
            author = authorLoaded !! n
          }
      | (n, root) <- zip [0 :: Int ..] roots
      ]

instance (ValidEdge "Author" author) => IncludeFor "Post" (PostInclude author)