packages feed

beam-postgres-0.6.2.0: test/Database/Beam/Postgres/Test/Windowing.hs

{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE StandaloneDeriving #-}

module Database.Beam.Postgres.Test.Windowing (tests) where

import Database.Beam
import Database.Beam.Backend.SQL.BeamExtensions
import Database.Beam.Migrate
import Database.Beam.Migrate.Simple (autoMigrate)
import Database.Beam.Postgres
import Database.Beam.Postgres.Migrate (migrationBackend)
import Database.Beam.Postgres.Test

import Control.Exception (SomeException (..), handle)

import Data.ByteString (ByteString)
import Data.Int
import Data.Text (Text)

import Control.Monad (void)
import Test.Tasty
import Test.Tasty.HUnit

tests :: IO ByteString -> TestTree
tests postgresConn =
    testGroup
        "Windowing unit tests"
        [ testLead1 postgresConn
        , testLag1 postgresConn
        , testLead postgresConn
        , testLag postgresConn
        , testLeadWithDefault postgresConn
        , testLagWithDefault postgresConn
        ]

testLead1 :: IO ByteString -> TestTree
testLead1 = testCase "lead1_" . windowingQueryTest query expectation
  where
    query =
        withWindow_
            ( \Person{name} ->
                frame_
                    noPartition_
                    (orderPartitionBy_ (asc_ name))
                    noBounds_
            )
            ( \Person{name} w ->
                (name, lead1_ name `over_` w)
            )
            (all_ $ persons db)
    expectation = [("Alice", Just "Bob"), ("Bob", Just "Claire"), ("Claire", Nothing)]

testLag1 :: IO ByteString -> TestTree
testLag1 = testCase "lag1_" . windowingQueryTest query expectation
  where
    query =
        withWindow_
            ( \Person{name} ->
                frame_
                    noPartition_
                    (orderPartitionBy_ (asc_ name))
                    noBounds_
            )
            ( \Person{name} w ->
                (name, lag1_ name `over_` w)
            )
            (all_ $ persons db)
    expectation = [("Alice", Nothing), ("Bob", Just "Alice"), ("Claire", Just "Bob")]

testLead :: IO ByteString -> TestTree
testLead getConnStr =
    testGroup
        "lead_"
        [ testCase "n=1" $ windowingQueryTest (query 1) [("Alice", Just "Bob"), ("Bob", Just "Claire"), ("Claire", Nothing)] getConnStr
        , testCase "n=2" $ windowingQueryTest (query 2) [("Alice", Just "Claire"), ("Bob", Nothing), ("Claire", Nothing)] getConnStr
        ]
  where
    query n =
        withWindow_
            ( \Person{name} ->
                frame_
                    noPartition_
                    (orderPartitionBy_ (asc_ name))
                    noBounds_
            )
            ( \Person{name} w ->
                (name, lead_ name (val_ (n :: Int32)) `over_` w)
            )
            (all_ $ persons db)
    expectation1 = []

testLag :: IO ByteString -> TestTree
testLag getConnStr =
    testGroup
        "lag_"
        [ testCase "n=1" $ windowingQueryTest (query 1) [("Alice", Nothing), ("Bob", Just "Alice"), ("Claire", Just "Bob")] getConnStr
        , testCase "n=2" $ windowingQueryTest (query 2) [("Alice", Nothing), ("Bob", Nothing), ("Claire", Just "Alice")] getConnStr
        ]
  where
    query n =
        withWindow_
            ( \Person{name} ->
                frame_
                    noPartition_
                    (orderPartitionBy_ (asc_ name))
                    noBounds_
            )
            ( \Person{name} w ->
                (name, lag_ name (val_ (n :: Int32)) `over_` w)
            )
            (all_ $ persons db)
    expectation = []


testLeadWithDefault :: IO ByteString -> TestTree
testLeadWithDefault getConnStr =
    testGroup
        "leadWithDefault_"
        [ testCase "n=1" $ windowingQueryTest (query 1 "default") [("Alice", "Bob"), ("Bob", "Claire"), ("Claire", "default")] getConnStr
        , testCase "n=2" $ windowingQueryTest (query 2 "default") [("Alice", "Claire"), ("Bob", "default"), ("Claire", "default")] getConnStr
        ]
  where
    query n def =
        withWindow_
            ( \Person{name} ->
                frame_
                    noPartition_
                    (orderPartitionBy_ (asc_ name))
                    noBounds_
            )
            ( \Person{name} w ->
                (name, leadWithDefault_ name (val_ (n :: Int32)) (val_ def) `over_` w)
            )
            (all_ $ persons db)
    expectation1 = []


testLagWithDefault :: IO ByteString -> TestTree
testLagWithDefault getConnStr =
    testGroup
        "lagWithDefault_"
        [ testCase "n=1" $ windowingQueryTest (query 1 "default") [("Alice", "default"), ("Bob", "Alice"), ("Claire", "Bob")] getConnStr
        , testCase "n=2" $ windowingQueryTest (query 2 "default") [("Alice", "default"), ("Bob", "default"), ("Claire", "Alice")] getConnStr
        ]
  where
    query n def =
        withWindow_
            ( \Person{name} ->
                frame_
                    noPartition_
                    (orderPartitionBy_ (asc_ name))
                    noBounds_
            )
            ( \Person{name} w ->
                (name, lagWithDefault_ name (val_ (n :: Int32)) (val_ def) `over_` w)
            )
            (all_ $ persons db)
    expectation = []



data PersonT f = Person
    { name :: C f Text
    }
    deriving (Generic)

type Person = PersonT Identity

type PersonExpr s = PersonT (QExpr Postgres s)

deriving instance Show Person
deriving instance Eq Person

instance Beamable PersonT

instance Table PersonT where
    data PrimaryKey PersonT f = PersonKey (C f Text)
        deriving stock (Generic)
        deriving anyclass (Beamable)

    primaryKey Person{name} = PersonKey name

data Db f = Db
    { persons :: f (TableEntity PersonT)
    }
    deriving (Generic)

instance Database Postgres Db

db :: DatabaseSettings Postgres Db
db = defaultDbSettings

windowingQueryTest ::
    (Eq a, Show a, Eq b, Show b, FromBackendRow Postgres a, FromBackendRow Postgres b) =>
    Q Postgres Db QBaseScope (QExpr Postgres s a, QExpr Postgres s b) ->
    [(a, b)] ->
    IO ByteString ->
    Assertion
windowingQueryTest query expectation getConnStr =
    withTestPostgres "db_windowing_psql" getConnStr $
        \conn -> do
            prepareTable conn
            results <-
                runBeamPostgres conn $
                    runSelectReturningList $
                        select query

            assertEqual "Unexpected" expectation results

prepareTable :: Connection -> IO ()
prepareTable conn =
    runBeamPostgres conn $ do
        void $ autoMigrate migrationBackend (defaultMigratableDbSettings @Postgres @Db)
        runInsert $
            insert (persons db) $
                insertValues
                    [ Person "Alice"
                    , Person "Bob"
                    , Person "Claire"
                    ]