{-# LANGUAGE LambdaCase #-} module Database.Beam.Postgres.Test.Select (tests) where import Data.Aeson import Data.ByteString (ByteString) import Data.Int import Data.List (sort) import qualified Data.Text as T import qualified Data.Vector as V import Test.Tasty import Test.Tasty.HUnit import Data.UUID (UUID, nil) import qualified Data.UUID.V5 as V5 import Database.Beam import Database.Beam.Backend.SQL.SQL92 import Database.Beam.Migrate import Database.Beam.Postgres import Database.Beam.Postgres.Extensions.UuidOssp import Database.Beam.Postgres.Test tests :: IO ByteString -> TestTree tests getConn = testGroup "Selection Tests" [ testGroup "JSON" [ testPgArrayToJSON getConn ] , testGroup "ARRAY functions" [ testArrayReplace getConn , testArrayShuffle getConn , testArraySample getConn , testArrayToStringBasic getConn , testArrayToStringWithNull getConn , testArrayAppend getConn , testArrayPrepend getConn , testArrayRemove getConn ] , testGroup "UUID" [ testUuidFunction getConn "uuid_nil" $ \ext -> pgUuidNil ext , testUuidFunction getConn "uuid_ns_dns" $ \ext -> pgUuidNsDns ext , testUuidFunction getConn "uuid_ns_url" $ \ext -> pgUuidNsUrl ext , testUuidFunction getConn "uuid_ns_oid" $ \ext -> pgUuidNsOid ext , testUuidFunction getConn "uuid_ns_x500" $ \ext -> pgUuidNsX500 ext , testUuidFunction getConn "uuid_generate_v1" $ \ext -> pgUuidGenerateV1 ext , testUuidFunction getConn "uuid_generate_v1mc" $ \ext -> pgUuidGenerateV1Mc ext , testUuidFunction getConn "uuid_generate_v3" $ \ext -> pgUuidGenerateV3 ext (val_ nil) "nil" , testUuidFunction getConn "uuid_generate_v4" $ \ext -> pgUuidGenerateV4 ext , testUuidFunction getConn "uuid_generate_v5" $ \ext -> pgUuidGenerateV5 ext (val_ nil) "nil" , testUuuidInValues getConn ] , testInRowValues getConn , testInSelect getConn , testReturningMany getConn , testPgUnnest getConn ] testPgArrayToJSON :: IO ByteString -> TestTree testPgArrayToJSON getConn = testFunction getConn "array_to_json" $ \conn -> do let values :: [Int32] = [1, 2, 3] actual :: [PgJSON Value] <- runBeamPostgres conn $ runSelectReturningList $ select $ return $ pgArrayToJson $ val_ $ V.fromList values assertEqual "JSON list" [PgJSON $ toJSON values] actual testArrayReplace :: IO ByteString -> TestTree testArrayReplace getConn = testFunction getConn "array_replace" $ \conn -> do let arr = V.fromList [1::Int32,2,5,4] res <- runBeamPostgres conn $ runSelectReturningList $ select $ do pure $ arrayReplace_ (val_ arr) (val_ (5::Int32)) (val_ (3::Int32)) assertEqual "array_replace" [V.fromList [1,2,3,4 :: Int32]] res testArrayShuffle :: IO ByteString -> TestTree testArrayShuffle getConn = testFunction getConn "array_shuffle" $ \conn -> do let arr = V.fromList [1::Int32,2,3,4,5] res <- runBeamPostgres conn $ runSelectReturningList $ select $ do pure $ arrayShuffle_ (val_ arr) -- shuffled result has same length and elements, order may change case res of [shuf] -> do assertEqual "length" (V.length arr) (V.length shuf) assertBool "is permutation" (sort (V.toList arr) == sort (V.toList shuf)) _ -> assertFailure "unexpected result" testArraySample :: IO ByteString -> TestTree testArraySample getConn = testFunction getConn "array_sample" $ \conn -> do let arr = V.fromList [1::Int32,2,3,4,5,6] res <- runBeamPostgres conn $ runSelectReturningList $ select $ do pure $ arraySample_ (val_ arr) (val_ (3::Int32)) case res of [samp] -> do assertEqual "length 3" 3 (V.length samp) assertBool "subset" (V.all (`V.elem` arr) samp) _ -> assertFailure "unexpected result" testArrayToStringBasic :: IO ByteString -> TestTree testArrayToStringBasic getConn = testFunction getConn "array_to_string_basic" $ \conn -> do let arr = V.fromList [1::Int32,2,3] res <- runBeamPostgres conn $ runSelectReturningList $ select $ do pure $ arrayToString_ (val_ arr) (val_ ",") assertEqual "join" ["1,2,3" :: T.Text] res testArrayToStringWithNull :: IO ByteString -> TestTree testArrayToStringWithNull getConn = testFunction getConn "array_to_string_with_null" $ \conn -> do let arr :: V.Vector (Maybe T.Text) arr = V.fromList [Just "a", Nothing, Just "b"] res <- runBeamPostgres conn $ runSelectReturningList $ select $ do pure $ arrayToStringWithNull_ (val_ arr) (val_ "-") (val_ "*") assertEqual "join with null" ["a-*-b" :: T.Text] res testArrayAppend :: IO ByteString -> TestTree testArrayAppend getConn = testFunction getConn "array_append" $ \conn -> do let arr = V.fromList [1::Int32,2] res <- runBeamPostgres conn $ runSelectReturningList $ select $ do pure $ arrayAppend_ (val_ arr) (val_ (3::Int32)) assertEqual "append" [V.fromList [1,2,3 :: Int32]] res testArrayPrepend :: IO ByteString -> TestTree testArrayPrepend getConn = testFunction getConn "array_prepend" $ \conn -> do let arr = V.fromList [2::Int32,3] res <- runBeamPostgres conn $ runSelectReturningList $ select $ do pure $ arrayPrepend_ (val_ (1::Int32)) (val_ arr) assertEqual "prepend" [V.fromList [1,2,3 :: Int32]] res testArrayRemove :: IO ByteString -> TestTree testArrayRemove getConn = testFunction getConn "array_remove" $ \conn -> do let arr = V.fromList [1::Int32,2,3,2] res <- runBeamPostgres conn $ runSelectReturningList $ select $ do pure $ arrayRemove_ (val_ arr) (val_ (2::Int32)) assertEqual "remove" [V.fromList [1,3 :: Int32]] res data UuidSchema f = UuidSchema { _uuidOssp :: f (PgExtensionEntity UuidOssp) } deriving (Generic, Database Postgres) testUuidFunction :: IO ByteString -> String -> (forall s. UuidOssp -> QExpr Postgres s UUID) -> TestTree testUuidFunction getConn name mkUuid = testFunction getConn name $ \conn -> runBeamPostgres conn $ do db <- executeMigration runNoReturn $ UuidSchema <$> pgCreateExtension @UuidOssp [_] <- runSelectReturningList $ select $ return $ mkUuid $ getPgExtension $ _uuidOssp $ unCheckDatabase db return () -- | Regression test for testUuuidInValues :: IO ByteString -> TestTree testUuuidInValues getConn = testCase "UUID in values_ works" $ withTestPostgres "uuid_values" getConn $ \conn -> do result <- runBeamPostgres conn $ do db <- executeMigration runNoReturn $ UuidSchema <$> pgCreateExtension @UuidOssp let ext = getPgExtension $ _uuidOssp $ unCheckDatabase db runSelectReturningList $ select $ do v <- values_ [val_ nil] return $ pgUuidGenerateV5 ext v "" assertEqual "result" [V5.generateNamed nil []] result data Pair f = Pair { _left :: C f Bool , _right :: C f Bool } deriving (Generic, Beamable) testInRowValues :: IO ByteString -> TestTree testInRowValues getConn = testCase "IN with row values works" $ withTestPostgres "db_in_row_values" getConn $ \conn -> do result <- runBeamPostgres conn $ runSelectReturningList $ select $ do let pair :: forall ctx s. Pair (QGenExpr ctx Postgres s) pair = val_ $ Pair False False return $ pair `in_` [pair, pair] assertEqual "result" [True] result testInSelect :: IO ByteString -> TestTree testInSelect getConn = testCase "IN (SELECT ...) works" $ withTestPostgres "db_in_row_values" getConn $ \conn -> do result <- runBeamPostgres conn $ runSelectReturningList $ select $ do let x = as_ @Int32 (val_ 1) return $ x `inQuery_` ( pgUnnestArray $ array_ $ (as_ @Int32 . val_) <$> [0..100]) assertEqual "result" [True] result testReturningMany :: IO ByteString -> TestTree testReturningMany getConn = testCase "runReturningMany (batching via cursor) works" $ withTestPostgres "run_returning_many_cursor" getConn $ \conn -> do result <- runBeamPostgres conn $ runSelectReturningMany (select $ pgUnnestArray $ array_ $ (as_ @Int32 . val_) <$> [0..rowCount - 1]) (\fetch -> let count n = fetch >>= \case Nothing -> pure n Just _ -> count (n + 1) in count 0) assertEqual "result" rowCount result where rowCount = 500 runSelectReturningMany :: (FromBackendRow Postgres x) => SqlSelect Postgres x -> (Pg (Maybe x) -> Pg a) -> Pg a runSelectReturningMany (SqlSelect s) = runReturningMany (selectCmd s) testFunction :: IO ByteString -> String -> (Connection -> Assertion) -> TestTree testFunction getConn name mkAssertion = testCase name $ withTestPostgres name getConn mkAssertion -- | Regression test for testPgUnnest :: IO ByteString -> TestTree testPgUnnest getConn = testCase "pgUnnest works" $ withTestPostgres "pg_unnest" getConn $ \conn -> do let values = [Bool True, Number 1] result <- runBeamPostgres conn $ runSelectReturningList $ select $ pgUnnest $ pgJsonArrayElements $ val_ $ PgJSONB $ Array $ V.fromList values assertEqual "result" (PgJSONB <$> values) $ pgJsonElement <$> result