beam-large-records-0.1.3: test/Test/Record/Beam/Tutorial1.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE OverloadedLabels #-}
{-# OPTIONS_GHC -fplugin=Data.Record.Plugin #-}
module Test.Record.Beam.Tutorial1 (
tests
-- * Exported for the benefit of follow-up tutorials
, UserT(..)
, User
, UserId
, PrimaryKey(..)
) where
import Data.Functor.Identity
import Data.Int
import Data.Kind
import Data.Record.Beam ()
import Data.Text (Text)
import Database.Beam hiding (Generic, countAll_)
import Database.Beam.Schema.Tables
import Optics.Core ((^.))
import qualified Data.List.NonEmpty as NE
import qualified Database.SQLite.Simple as SQLite
import qualified GHC.Generics as GHC
import Test.Tasty
import Test.Tasty.HUnit
import Test.Record.Beam.Util.Compat
import Test.Record.Beam.Util.SQLite
import Test.Record.Beam.Util.Orphans ()
{-------------------------------------------------------------------------------
We replicate the beam tutorial, but using large-records
See <https://haskell-beam.github.io/beam/>
-------------------------------------------------------------------------------}
{-# ANN type UserT largeRecord #-}
data UserT (f :: Type -> Type) = User {
userEmail :: Columnar f Text
, userFirstName :: Columnar f Text
, userLastName :: Columnar f Text
, userPassword :: Columnar f Text
}
deriving stock (Show, Eq)
deriving anyclass (Beamable)
type User = UserT Identity
type UserId = PrimaryKey UserT Identity
james, betty, sam :: User
james = User "james@example.com" "James" "Smith" "b4cc344d25a2efe540adbf2678e2304c"
betty = User "betty@example.com" "Betty" "Jones" "82b054bd83ffad9b6cf8bdb98ce3cc2f"
sam = User "sam@example.com" "Sam" "Taylor" "332532dcfaa1cbf61e2a266bd723612c"
james2, betty2, james3, sam2, sam3 :: User
james2 = User "james@pallo.com" "James" "Pallo" "b4cc344d25a2efe540adbf2678e2304c"
betty2 = User "betty@sims.com" "Betty" "Sims" "82b054bd83ffad9b6cf8bdb98ce3cc2f"
james3 = User "james@oreily.com" "James" "O'Reily" "b4cc344d25a2efe540adbf2678e2304c"
sam2 = User "sam@sophitz.com" "Sam" "Sophitz" "332532dcfaa1cbf61e2a266bd723612c"
sam3 = User "sam@jely.com" "Sam" "Jely" "332532dcfaa1cbf61e2a266bd723612c"
instance Table UserT where
newtype PrimaryKey UserT f = UserId (Columnar f Text)
deriving stock (GHC.Generic)
deriving anyclass (Beamable)
primaryKey tbl = UserId (tbl ^. #userEmail)
deriving instance Show (Columnar f Text) => Show (PrimaryKey UserT f)
deriving instance Eq (Columnar f Text) => Eq (PrimaryKey UserT f)
{-------------------------------------------------------------------------------
Example DB
-------------------------------------------------------------------------------}
{-# ANN type ShoppingCartDb largeRecord #-}
data ShoppingCartDb (f :: Type -> Type) = ShoppingCartDb {
shoppingCartUsers :: f (TableEntity UserT)
}
deriving (Show, Eq)
instance Database be ShoppingCartDb
shoppingCartDb :: DatabaseSettings be ShoppingCartDb
shoppingCartDb = defaultDbSettings
{-------------------------------------------------------------------------------
Tests proper
-------------------------------------------------------------------------------}
tests :: TestTree
tests = testGroup "Test.Record.Beam.Tutorial1" [
testCase "tblSkeleton" test_tutorial1_tblSkeleton
, testCase "defaultDbSettings" test_tutorial1_defaultDbSettings
, testCase "insertSelect" test_tutorial1_insertSelect
, testCase "recordDotSyntax" test_tutorial1_recordDotSyntax
]
test_tutorial1_tblSkeleton :: Assertion
test_tutorial1_tblSkeleton =
assertEqual "" expected (tblSkeleton :: TableSkeleton UserT)
where
expected :: TableSkeleton UserT
expected = User {
userEmail = Ignored
, userFirstName = Ignored
, userLastName = Ignored
, userPassword = Ignored
}
test_tutorial1_defaultDbSettings :: Assertion
test_tutorial1_defaultDbSettings =
assertEqual "" expected shoppingCartDb
where
expected :: DatabaseSettings be ShoppingCartDb
expected = ShoppingCartDb {
shoppingCartUsers = DatabaseEntity (
DatabaseTable {
dbTableSchema = Nothing
, dbTableOrigName = "shoppingCartUsers"
, dbTableCurrentName = "cart_users"
, dbTableSettings = User {
userEmail = TableField { _fieldPath = NE.fromList ["userEmail"] , _fieldName = "email"}
, userFirstName = TableField { _fieldPath = NE.fromList ["userFirstName"] , _fieldName = "first_name"}
, userLastName = TableField { _fieldPath = NE.fromList ["userLastName"] , _fieldName = "last_name"}
, userPassword = TableField { _fieldPath = NE.fromList ["userPassword"] , _fieldName = "password"}
}
}
)
}
test_tutorial1_insertSelect :: Assertion
test_tutorial1_insertSelect = runInMemory $ \conn -> do
liftIO $ SQLite.execute_ conn $
"CREATE TABLE cart_users (email VARCHAR NOT NULL, first_name VARCHAR NOT NULL, last_name VARCHAR NOT NULL, password VARCHAR NOT NULL, PRIMARY KEY( email ));"
runInsert $ insert (shoppingCartDb ^. #shoppingCartUsers) $ insertValues [
james
, betty
, sam
]
let allUsers = all_ (shoppingCartDb ^. #shoppingCartUsers)
users <- runSelectReturningList $ select allUsers
liftIO $ assertEqual "users" [james, betty, sam] users
let sortUsersByFirstName = orderBy_ (\u -> (asc_ $ u ^. #userFirstName, desc_ $ u ^. #userLastName)) (all_ $ shoppingCartDb ^. #shoppingCartUsers)
sorted <- runSelectReturningList $ select sortUsersByFirstName
liftIO $ assertEqual "sorted" [betty, james, sam] sorted
let boundedQuery = limit_ 1 $ offset_ 1 $
orderBy_ (\u -> asc_ (u ^. #userFirstName)) $
all_ (shoppingCartDb ^. #shoppingCartUsers)
bounded <- runSelectReturningList (select boundedQuery)
liftIO $ assertEqual "bounded" [james] bounded
-- Tutorial has Int32 here, but that doesn't typecheck
-- Don't think that is related to beam-large-records though..?
-- (Maybe due to beam version mismatch between tutorial and our beam branch.)
let userCount = aggregate_ (\_u -> as_ @Int32 countAll_) (all_ (shoppingCartDb ^. #shoppingCartUsers))
Just c <- runSelectReturningOne $ select userCount
liftIO $ assertEqual "userCount" 3 c
runInsert $ insert (shoppingCartDb ^. #shoppingCartUsers) $ insertValues [
james2
, betty2
, james3
, sam2
, sam3
]
let numberOfUsersByName = aggregate_ (\u -> (group_ (u ^. #userFirstName), as_ @Int32 countAll_)) $
all_ (shoppingCartDb ^. #shoppingCartUsers)
countedByName <- runSelectReturningList $ select numberOfUsersByName
liftIO $ assertEqual "countedByName" [("Betty",2), ("James",3), ("Sam",3)] countedByName
-- Just a sanity check that RDS is working
-- (NOTE: RDS gets confused by nested multiline comments.)
test_tutorial1_recordDotSyntax :: Assertion
test_tutorial1_recordDotSyntax =
assertEqual "" "a@b.c" (u ^. #userEmail)
where
u :: User
u = User {
userEmail = "a@b.c"
, userFirstName = "John"
, userLastName = "Doe"
, userPassword = "secret"
}