packages feed

poppy-1.0.0: test/Schema/Client/Book.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NoFieldSelectors #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

{- | Generated Client. Do not edit.

Nested writes live on 'create' / 'update'.
  create-time relation fields are [CreateChild | ConnectChild unique]
  update-time relation fields are Maybe <Rel>Update (replaceWith, create, connect, delete, update, upsert;
  disconnect when the child foreign key is nullable)
-}
module Schema.Client.Book
  ( findMany,
    findUnique,
    findUniqueOrFail,
    findFirst,
    findFirstOrFail,
    count,
    create,
    createMany,
    update,
    updateMany,
    upsert,
    delete,
    deleteMany,
    BookCreateScalars,
    BookUpdateScalars,
    ChapterNestedCreate (..),
    ChapterNestedUpsert (..),
    ChaptersUpdate (..),
    emptyChaptersUpdate,
    BookQuery (..),
    BookUnique (..),
    BookUniqueKey (..),
    BookUniqueQuery (..),
    emptyQuery,
    uniqueQuery,
    bookUniqueWhere,
    OmitSelect (..),
    Picked (..),
    BookCreate (..),
    BookRow (..),
    BookSelect (..),
    BookPicked (..),
    bookSelect,
    BookUpdate (..),
    BookTable,
    bookId
  )
where

import Data.Maybe (isJust)
import Data.Text (Text)
import Data.UUID (UUID)
import Poppy.Internal.Generated
  ( Db,
    transactionEither,
    fieldColumn,
    toField,
    ORMError (..),
    fromUniqueRows,
    requireFound,
    uniqueOrFail,
    OrderBy,
    applyQueryModifiers,
    matching,
    selectColumns,
    OmitSelect (..),
    Picked (..),
    prepareIncludeRootQuery,
    Where,
    and_,
    eq
  )
import qualified Poppy.Internal.Generated as Delete
  ( deleteMany,
    deleteWhere,
    whereDelete,
    emptyDelete
  )
import qualified Poppy.Internal.Generated as Insert
  ( insert,
    insertMany,
    upsert
  )
import qualified Poppy.Internal.Generated as Ops
  ( findMany,
    findManyWith,
    findFirst,
    findFirstWith,
    count
  )
import qualified Poppy.Internal.Generated as Update
  ( updateWhere,
    updateMany
  )

import Schema.Book (BookRow (..), BookSelect (..), BookPicked (..), bookSelect, bookSelectColumns, parseBookPicked, BookTable, bookId)
import qualified Schema.Book as BookSchema (BookCreate (..), BookUpdate (..))

import Schema.Include.Book (LoadBook (..), BookInclude (..), BookRead, toBookWithPicked)
import Schema.Chapter (ChapterRow (..), ChapterTable, chapterId, chapterBookRef, ChapterCreate (..), ChapterUpdate (..))
import qualified Schema.Client.Chapter as Chapter (ChapterUnique (..), chapterUniqueWhere)

data BookUnique
  = ById UUID
  deriving (Eq, Show)

data BookUniqueKey
  = OnId
  deriving (Eq, Show)

bookUniqueWhere :: BookUnique -> Where BookTable
bookUniqueWhere = \case
  ById v1 -> eq bookId v1

bookConflictCols :: BookUniqueKey -> [Text]
bookConflictCols = \case
  OnId -> ["id"]

data ChapterNestedCreate
  = CreateChapter
      { id :: Maybe UUID, heading :: Text
      }
  | ConnectChapter Chapter.ChapterUnique
  deriving (Show, Eq)
data ChapterNestedUpsert = ChapterNestedUpsert
  { where_ :: Chapter.ChapterUnique
  , create :: ChapterNestedCreate
  , update :: ChapterUpdate
  }
  deriving (Show, Eq)
data ChaptersUpdate = ChaptersUpdate
  { replaceWith :: Maybe [ChapterNestedCreate]
  , create :: [ChapterNestedCreate]
  , createMany :: [ChapterNestedCreate]
  , connect :: [Chapter.ChapterUnique]
  , delete :: [Chapter.ChapterUnique]
  , update :: [(Chapter.ChapterUnique, ChapterUpdate)]
  , upsert :: [ChapterNestedUpsert]
  }
  deriving (Show, Eq)

emptyChaptersUpdate :: ChaptersUpdate
emptyChaptersUpdate =
  ChaptersUpdate
    { replaceWith = Nothing
    , create = []
    , createMany = []
    , connect = []
    , delete = []
    , update = []
    , upsert = []
    }
data BookCreate = BookCreate
  { id :: Maybe UUID,
    shelfId :: UUID,
    title :: Text,
    chapters :: [ChapterNestedCreate]
  }
  deriving (Show, Eq)

type BookCreateScalars = BookSchema.BookCreate

toBookCreateScalars :: BookCreate -> BookCreateScalars
toBookCreateScalars input =
  BookSchema.BookCreate
    { id = input.id,
      shelfId = input.shelfId,
      title = input.title
    }
data BookUpdate = BookUpdate
  { shelfId :: Maybe UUID,
    title :: Maybe Text,
    chapters :: Maybe ChaptersUpdate
  }
  deriving (Show, Eq)

type BookUpdateScalars = BookSchema.BookUpdate

toBookUpdateScalars :: BookUpdate -> BookUpdateScalars
toBookUpdateScalars input =
  BookSchema.BookUpdate
    { shelfId = input.shelfId,
      title = input.title
    }

create :: BookCreate -> Db (Either ORMError BookRow)
create input =
  if hasBookNestedCreate input
    then transactionEither (createWithNested input)
    else Insert.insert @BookTable @BookRow (toBookCreateScalars input)

hasBookNestedCreate :: BookCreate -> Bool
hasBookNestedCreate input =
  not (null input.chapters)

createWithNested :: BookCreate -> Db (Either ORMError BookRow)
createWithNested input = do
  rootResult <- Insert.insert @BookTable @BookRow (toBookCreateScalars input)
  case rootResult of
    Left err -> pure (Left err)
    Right row -> do
      nestedResult <-
        sequenceNested
          [ applyChaptersCreate row.id input.chapters
          ]
      case nestedResult of
        Left err -> pure (Left err)
        Right () -> pure (Right row)

createMany :: [BookCreateScalars] -> Db (Either ORMError Int)
createMany = Insert.insertMany @BookTable

update :: BookUnique -> BookUpdate -> Db (Either ORMError BookRow)
update key input =
  if hasBookNestedUpdate input
    then transactionEither (updateWithNested key input)
    else Update.updateWhere @BookTable @BookRow (bookUniqueWhere key) (toBookUpdateScalars input)

hasBookNestedUpdate :: BookUpdate -> Bool
hasBookNestedUpdate input =
  isJust input.chapters

updateWithNested :: BookUnique -> BookUpdate -> Db (Either ORMError BookRow)
updateWithNested key input = do
  updateResult <- Update.updateWhere @BookTable @BookRow (bookUniqueWhere key) (toBookUpdateScalars input)
  case updateResult of
    Left err -> pure (Left err)
    Right row -> do
      nestedResult <-
        sequenceNested
          [ maybe (pure (Right ())) (applyChaptersUpdate row.id) input.chapters
          ]
      case nestedResult of
        Left err -> pure (Left err)
        Right () -> pure (Right row)

updateMany :: Where BookTable -> BookUpdateScalars -> Db (Either ORMError Int)
updateMany = Update.updateMany @BookTable

upsert :: BookUniqueKey -> BookCreateScalars -> BookUpdateScalars -> Db (Either ORMError BookRow)
upsert key createInput updateInput =
  Insert.upsert @BookTable @BookRow (bookConflictCols key) createInput updateInput

sequenceNested :: [Db (Either ORMError ())] -> Db (Either ORMError ())
sequenceNested [] = pure (Right ())
sequenceNested (action : rest) = do
  result <- action
  case result of
    Left err -> pure (Left err)
    Right () -> sequenceNested rest
applyChaptersCreate :: UUID -> [ChapterNestedCreate] -> Db (Either ORMError ())
applyChaptersCreate = insertChapters
applyChaptersUpdate :: UUID -> ChaptersUpdate -> Db (Either ORMError ())
applyChaptersUpdate parentId ops = do
  replaced <- case ops.replaceWith of
    Nothing -> pure (Right ())
    Just items -> replaceChapters parentId items
  case replaced of
    Left err -> pure (Left err)
    Right () ->
      sequenceNested
        [ deleteChapters parentId ops.delete
        , updateChaptersRows parentId ops.update
        , upsertChapters parentId ops.upsert
        , insertChapters parentId ops.create
        , insertChapters parentId ops.createMany
        , connectChapters parentId ops.connect
        ]
replaceChapters :: UUID -> [ChapterNestedCreate] -> Db (Either ORMError ())
replaceChapters parentId items = do
  result <-
    Delete.deleteWhere $
      Delete.whereDelete (fieldColumn chapterBookRef <> " = ?") [toField parentId] (Delete.emptyDelete @ChapterTable)
  case result of
    Left err -> pure (Left err)
    Right _ -> insertChapters parentId items
insertChapters :: UUID -> [ChapterNestedCreate] -> Db (Either ORMError ())
insertChapters parentId = go
  where
    go [] = pure (Right ())
    go (nested : rest) = do
      result <- case nested of
        ConnectChapter key -> connectChapters parentId [key]
        CreateChapter {id, heading} -> do
          inserted <- Insert.insert @ChapterTable @ChapterRow ChapterCreate
            { id = id,
              heading = heading,
              bookRef = parentId
            }
          pure $ case inserted of
            Left err -> Left err
            Right _ -> Right ()
      case result of
        Left err -> pure (Left err)
        Right () -> go rest
deleteChapters :: UUID -> [Chapter.ChapterUnique] -> Db (Either ORMError ())
deleteChapters _ [] = pure (Right ())
deleteChapters parentId keys = sequenceNested (map deleteOne keys)
  where
    deleteOne key = do
      result <- Delete.deleteMany @ChapterTable
        (Chapter.chapterUniqueWhere key `and_` eq chapterBookRef parentId)
      pure $ case result of
        Left err -> Left err
        Right _ -> Right ()
updateChaptersRows :: UUID -> [(Chapter.ChapterUnique, ChapterUpdate)] -> Db (Either ORMError ())
updateChaptersRows parentId = go
  where
    go [] = pure (Right ())
    go ((key, nested) : rest) = do
      let patched = ChapterUpdate { bookRef = Nothing, heading = nested.heading }
      result <-
        Update.updateWhere @ChapterTable @ChapterRow
          (Chapter.chapterUniqueWhere key `and_` eq chapterBookRef parentId)
          patched
      case result of
        Left err -> pure (Left err)
        Right _ -> go rest
upsertChapters :: UUID -> [ChapterNestedUpsert] -> Db (Either ORMError ())
upsertChapters parentId = go
  where
    go [] = pure (Right ())
    go (item : rest) = do
      existing <- Ops.findMany @ChapterTable @ChapterRow (matching (Chapter.chapterUniqueWhere item.where_))
      result <- case fromUniqueRows existing of
        Left err -> pure (Left err)
        Right Nothing -> insertChapters parentId [item.create]
        Right (Just row) ->
          if row.bookRef == parentId
            then do
              let patched = ChapterUpdate { bookRef = Nothing, heading = item.update.heading }
              updated <-
                Update.updateWhere @ChapterTable @ChapterRow
                  (Chapter.chapterUniqueWhere item.where_ `and_` eq chapterBookRef parentId)
                  patched
              pure $ case updated of
                Left err -> Left err
                Right _ -> Right ()
            else pure (Left (UniqueViolation "nested upsert would reparent a row owned by another parent"))
      case result of
        Left err -> pure (Left err)
        Right () -> go rest
connectChapters :: UUID -> [Chapter.ChapterUnique] -> Db (Either ORMError ())
connectChapters _ [] = pure (Right ())
connectChapters parentId keys = sequenceNested (map connectOne keys)
  where
    connectOne key = do
      result <-
        Update.updateWhere @ChapterTable @ChapterRow
          (Chapter.chapterUniqueWhere key)
          (ChapterUpdate
            { bookRef = Just parentId,
              heading = Nothing
            })
      pure $ case result of
        Left err -> Left err
        Right _ -> Right ()

data BookQuery include select = BookQuery
  { include_ :: include
  , select_ :: select
  , where_ :: Maybe (Where BookTable)
  , orderBy_ :: [OrderBy BookTable]
  , limit_ :: Maybe Int
  , offset_ :: Maybe Int
  }

data BookUniqueQuery include select = BookUniqueQuery
  { include_ :: include
  , select_ :: select
  , where_ :: BookUnique
  }

emptyQuery :: BookQuery () OmitSelect
emptyQuery =
  BookQuery {include_ = (), select_ = OmitSelect, where_ = Nothing, orderBy_ = [], limit_ = Nothing, offset_ = Nothing}

uniqueQuery :: BookUnique -> BookUniqueQuery () OmitSelect
uniqueQuery key =
  BookUniqueQuery {include_ = (), select_ = OmitSelect, where_ = key}

class ReadBook include select where
  findMany :: BookQuery include select -> Db [BookRead include select]
  findUnique :: BookUniqueQuery include select -> Db (Either ORMError (Maybe (BookRead include select)))
  findUniqueOrFail :: BookUniqueQuery include select -> Db (Either ORMError (BookRead include select))
  findFirst :: BookQuery include select -> Db (Maybe (BookRead include select))
  findFirstOrFail :: BookQuery include select -> Db (Either ORMError (BookRead include select))

instance (LoadBook chapters) => ReadBook (BookInclude chapters) OmitSelect where
  findMany BookQuery {include_, where_, orderBy_, limit_, offset_} = do
    roots <- Ops.findMany @BookTable @BookRow (prepareIncludeRootQuery @BookTable (applyQueryModifiers where_ orderBy_ limit_ offset_))
    loadBook include_ roots
  findUnique BookUniqueQuery {where_, include_} = do
    let w = bookUniqueWhere where_
    roots <- Ops.findMany @BookTable @BookRow (prepareIncludeRootQuery @BookTable (matching w))
    rows <- loadBook include_ roots
    pure (fromUniqueRows rows)
  findUniqueOrFail q = uniqueOrFail <$> findUnique q
  findFirst BookQuery {include_, where_, orderBy_, offset_} = do
    roots <- Ops.findMany @BookTable @BookRow (prepareIncludeRootQuery @BookTable (applyQueryModifiers where_ orderBy_ (Just 1) offset_))
    loaded <- loadBook include_ roots
    pure $ case loaded of
      [] -> Nothing
      (row : _) -> Just row
  findFirstOrFail q = do
    result <- findFirst q
    pure $ requireFound result (RecordNotFound "No record found matching query")

instance ReadBook () OmitSelect where
  findMany BookQuery {where_, orderBy_, limit_, offset_} =
    Ops.findMany @BookTable @BookRow (applyQueryModifiers where_ orderBy_ limit_ offset_)
  findUnique BookUniqueQuery {where_} = do
    let w = bookUniqueWhere where_
    rows <- Ops.findMany @BookTable @BookRow (matching w)
    pure (fromUniqueRows rows)
  findUniqueOrFail q = uniqueOrFail <$> findUnique q
  findFirst BookQuery {where_, orderBy_, limit_, offset_} =
    Ops.findFirst @BookTable @BookRow (applyQueryModifiers where_ orderBy_ limit_ offset_)
  findFirstOrFail q = do
    result <- findFirst q
    pure $ requireFound result (RecordNotFound "No record found matching query")

instance (LoadBook chapters) => ReadBook (BookInclude chapters) BookSelect where
  findMany BookQuery {include_, select_, where_, orderBy_, limit_, offset_} = do
    roots <- Ops.findMany @BookTable @BookRow (prepareIncludeRootQuery @BookTable (applyQueryModifiers where_ orderBy_ limit_ offset_))
    loaded <- loadBook include_ roots
    pure $ map (toBookWithPicked select_) loaded
  findUnique BookUniqueQuery {where_, include_, select_} = do
    let w = bookUniqueWhere where_
    roots <- Ops.findMany @BookTable @BookRow (prepareIncludeRootQuery @BookTable (matching w))
    loaded <- loadBook include_ roots
    let rows = map (toBookWithPicked select_) loaded
    pure (fromUniqueRows rows)
  findUniqueOrFail q = uniqueOrFail <$> findUnique q
  findFirst BookQuery {select_, include_, where_, orderBy_, offset_} = do
    roots <- Ops.findMany @BookTable @BookRow (prepareIncludeRootQuery @BookTable (applyQueryModifiers where_ orderBy_ (Just 1) offset_))
    loaded <- loadBook include_ roots
    pure $ case loaded of
      [] -> Nothing
      (row : _) -> Just (toBookWithPicked select_ row)
  findFirstOrFail q = do
    result <- findFirst q
    pure $ requireFound result (RecordNotFound "No record found matching query")

instance ReadBook () BookSelect where
  findMany BookQuery {select_, where_, orderBy_, limit_, offset_} =
    Ops.findManyWith
      (parseBookPicked select_)
      (selectColumns (bookSelectColumns select_) . applyQueryModifiers where_ orderBy_ limit_ offset_)
  findUnique BookUniqueQuery {where_, select_} = do
    let w = bookUniqueWhere where_
    rows <-
      Ops.findManyWith
        (parseBookPicked select_)
        (selectColumns (bookSelectColumns select_) . matching w)
    pure (fromUniqueRows rows)
  findUniqueOrFail q = uniqueOrFail <$> findUnique q
  findFirst BookQuery {select_, where_, orderBy_, limit_, offset_} =
    Ops.findFirstWith
      (parseBookPicked select_)
      (selectColumns (bookSelectColumns select_) . applyQueryModifiers where_ orderBy_ limit_ offset_)
  findFirstOrFail q = do
    result <- findFirst q
    pure $ requireFound result (RecordNotFound "No record found matching query")

count :: BookQuery include select -> Db Int
count BookQuery {where_, orderBy_, limit_, offset_} =
  Ops.count @BookTable (applyQueryModifiers where_ orderBy_ limit_ offset_)

delete :: BookUnique -> Db (Either ORMError Int)
delete key =
  Delete.deleteMany @BookTable (bookUniqueWhere key)

deleteMany :: Where BookTable -> Db (Either ORMError Int)
deleteMany = Delete.deleteMany @BookTable