@@ -1,12 +1,11 @@
1+{-# language BangPatterns #-}
12{-# language BlockArguments #-}
2−{-# language DataKinds #-}
33{-# language DeriveAnyClass #-}
44{-# language DeriveGeneric #-}
55{-# language DerivingStrategies #-}
6−{-# language DisambiguateRecordFields #-}
76{-# language FlexibleContexts #-}
87{-# language FlexibleInstances #-}
9−{-# language GADTs #-}
8+{-# language MonoLocalBinds #-}
109{-# language NamedFieldPuns #-}
1110{-# language OverloadedStrings #-}
1211{-# language RecordWildCards #-}
@@ -14,17 +13,15 @@
1413{-# language StandaloneDeriving #-}
1514{-# language TypeApplications #-}
1615
17−{-# options_ghc -fno-warn-redundant-constraints #-}
18−
1916module Main
2017 ( main
2118 )
2219where
2320
2421-- base
25−import Control.Applicative ( liftA2, liftA3 )
26−import Control.Monad (void)
27−import Control.Monad.IO.Class ( MonadIO, liftIO )
22+import Control.Applicative ( empty, liftA2, liftA3 )
23+import Control.Exception ( bracket, throwIO )
24+import Control.Monad ( (>=>), void )
2825import Data.Bifunctor ( bimap )
2926import Data.Foldable ( for_ )
3027import Data.Int ( Int32, Int64 )
@@ -38,25 +35,27 @@ import GHC.Generics ( Generic )
3835import qualified Data.ByteString.Lazy
3936
4037-- case-insensitive
41−import Data.CaseInsensitive (mk)
38+import Data.CaseInsensitive ( mk )
4239
4340-- containers
41+import Data.Containers.ListUtils ( nubOrdOn )
4442import qualified Data.Map.Strict as Map
4543
4644-- hasql
47−import Hasql.Connection ( Connection, acquire, release )
45+import Hasql.Connection ( Connection, acquire, release )
4846import Hasql.Session ( sql, run )
4947
48+-- hasql-transaction
49+import Hasql.Transaction ( Transaction, condemn, statement )
50+import qualified Hasql.Transaction.Sessions as Hasql
51+
5052-- hedgehog
5153import Hedgehog ( property, (===), forAll, cover, diff, evalM, PropertyT, TestT, test, Gen )
5254import qualified Hedgehog.Gen as Gen
5355import qualified Hedgehog.Range as Range
5456
55−-- lifted-base
56−import Control.Exception.Lifted ( bracket, throwIO, bracket_ )
57−
58−-- monad-control
59−import Control.Monad.Trans.Control ( MonadBaseControl )
57+-- mmorph
58+import Control.Monad.Morph ( hoist )
6059
6160-- rel8
6261import Rel8 ( Result )
@@ -72,12 +71,16 @@ import Test.Tasty
7271import Test.Tasty.Hedgehog ( testProperty )
7372
7473-- text
75−import Data.Text (Text, pack)
74+import Data.Text ( Text, pack, unpack )
7675import qualified Data.Text.Lazy
76+import Data.Text.Encoding ( decodeUtf8 )
7777
7878-- time
7979import Data.Time
8080
81+-- transformers
82+import Control.Monad.Trans.Class ( lift )
83+
8184-- tmp-postgres
8285import qualified Database.Postgres.Temp as TmpPostgres
8386
@@ -113,11 +116,13 @@ tests =
113116 , testCatMaybeTable getTestDatabase
114117 , testCatMaybe getTestDatabase
115118 , testMaybeTable getTestDatabase
119+ , testAggregateMaybeTable getTestDatabase
116120 , testNestedTables getTestDatabase
117121 , testMaybeTableApplicative getTestDatabase
118122 , testLogicalFixities getTestDatabase
119123 , testUpdate getTestDatabase
120124 , testDelete getTestDatabase
125+ , testUpsert getTestDatabase
121126 , testSelectNestedPairs getTestDatabase
122127 , testSelectArray getTestDatabase
123128 , testNestedMaybeTable getTestDatabase
@@ -133,6 +138,7 @@ tests =
133138 flip run conn do
134139 sql "CREATE EXTENSION citext"
135140 sql "CREATE TABLE test_table ( column1 text not null, column2 bool not null )"
141+ sql "CREATE TABLE unique_table ( \"key\" text not null unique, \"value\" text not null )"
136142 sql "CREATE SEQUENCE test_seq"
137143
138144 return db
@@ -140,19 +146,21 @@ tests =
140146 stopTestDatabase = TmpPostgres.stop
141147
142148
149+connect :: TmpPostgres.DB -> IO Connection
150+connect = acquire . TmpPostgres.toConnectionString >=> either (maybe empty (fail . unpack . decodeUtf8)) pure
151+
152+
143153databasePropertyTest
144154 :: TestName
145− -> (((Connection -> TestT IO ()) -> PropertyT IO ()) -> PropertyT IO ())
155+ -> ((TestT Transaction () -> PropertyT IO ()) -> PropertyT IO ())
146156 -> IO TmpPostgres.DB -> TestTree
147157databasePropertyTest testName f getTestDatabase =
148− withResource connect release $ \c ->
158+ withResource (connect =<< getTestDatabase) release $ \c ->
149159 testProperty testName $ property do
150− connection <- liftIO c
151− f \g -> test $ rollingBack connection $ g connection
152−
153− where
154−
155− connect = either (error . show) return =<< acquire . TmpPostgres.toConnectionString =<< getTestDatabase
160+ connection <- lift c
161+ f $ test . hoist \m -> do
162+ e <- run (Hasql.transaction Hasql.Serializable Hasql.Write (m <* condemn)) connection
163+ either throwIO pure e
156164
157165
158166data TestTable f = TestTable
@@ -184,17 +192,17 @@ testSelectTestTable :: IO TmpPostgres.DB -> TestTree
184192testSelectTestTable = databasePropertyTest "Can SELECT TestTable" \transaction -> do
185193 rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable
186194
187− transaction \connection -> do
188− liftIO $ Rel8.insert connection
189− Rel8.Insert
195+ transaction do
196+ selected <- lift do
197+ statement () $ Rel8.insert Rel8.Insert
190198 { into = testTableSchema
191199 , rows = Rel8.values $ map Rel8.lit rows
192200 , onConflict = Rel8.DoNothing
193201 , returning = pure ()
194202 }
195203
196− selected <- liftIO $ Rel8.select connection do
197− Rel8.each testTableSchema
204+ statement () $ Rel8.select do
205+ Rel8.each testTableSchema
198206
199207 sort selected === sort rows
200208
@@ -211,11 +219,12 @@ testWhere_ = databasePropertyTest "WHERE (Rel8.where_)" \transaction -> do
211219
212220 let expected = filter (\t -> testTableColumn2 t == magicBool) rows
213221
214− transaction \connection -> do
215− selected <- liftIO $ Rel8.select connection do
216− t <- Rel8.values $ Rel8.lit <$> rows
217− Rel8.where_ $ testTableColumn2 t Rel8.==. Rel8.lit magicBool
218− return t
222+ transaction do
223+ selected <- lift do
224+ statement () $ Rel8.select do
225+ t <- Rel8.values $ Rel8.lit <$> rows
226+ Rel8.where_ $ testTableColumn2 t Rel8.==. Rel8.lit magicBool
227+ return t
219228
220229 sort selected === sort expected
221230
@@ -228,11 +237,12 @@ testFilter :: IO TmpPostgres.DB -> TestTree
228237testFilter = databasePropertyTest "filter" \transaction -> do
229238 rows <- forAll $ Gen.list (Range.linear 1 10) genTestTable
230239
231− transaction \connection -> do
240+ transaction do
232241 let expected = filter testTableColumn2 rows
233242
234− selected <- liftIO $ Rel8.select connection
235− $ Rel8.filter testTableColumn2 =<< Rel8.values (Rel8.lit <$> rows)
243+ selected <- lift do
244+ statement () $ Rel8.select do
245+ Rel8.filter testTableColumn2 =<< Rel8.values (Rel8.lit <$> rows)
236246
237247 sort selected === sort expected
238248
@@ -247,9 +257,10 @@ testLimit = databasePropertyTest "LIMIT (Rel8.limit)" \transaction -> do
247257
248258 n <- forAll $ Gen.integral (Range.linear 0 10)
249259
250− transaction \connection -> do
251− selected <- liftIO $ Rel8.select connection do
252− Rel8.limit n $ Rel8.values (Rel8.lit <$> rows)
260+ transaction do
261+ selected <- lift do
262+ statement () $ Rel8.select do
263+ Rel8.limit n $ Rel8.values (Rel8.lit <$> rows)
253264
254265 diff (length selected) (<=) (fromIntegral n)
255266
@@ -267,9 +278,10 @@ testUnion = databasePropertyTest "UNION (Rel8.union)" \transaction -> evalM do
267278 left <- forAll $ Gen.list (Range.linear 0 10) genTestTable
268279 right <- forAll $ Gen.list (Range.linear 0 10) genTestTable
269280
270− transaction \connection -> do
271− selected <- liftIO $ Rel8.select connection do
272− Rel8.values (Rel8.lit <$> nub left) `Rel8.union` Rel8.values (Rel8.lit <$> nub right)
281+ transaction do
282+ selected <- lift do
283+ statement () $ Rel8.select do
284+ Rel8.values (Rel8.lit <$> nub left) `Rel8.union` Rel8.values (Rel8.lit <$> nub right)
273285
274286 sort selected === sort (nub (left ++ right))
275287
@@ -278,9 +290,11 @@ testDistinct :: IO TmpPostgres.DB -> TestTree
278290testDistinct = databasePropertyTest "DISTINCT (Rel8.distinct)" \transaction -> do
279291 rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable
280292
281− transaction \connection -> do
282− selected <- liftIO $ Rel8.select connection $ Rel8.distinct do
283− Rel8.values (Rel8.lit <$> rows)
293+ transaction do
294+ selected <- lift do
295+ statement () $ Rel8.select do
296+ Rel8.distinct do
297+ Rel8.values (Rel8.lit <$> rows)
284298
285299 sort selected === nub (sort rows)
286300
@@ -293,8 +307,10 @@ testExists :: IO TmpPostgres.DB -> TestTree
293307testExists = databasePropertyTest "EXISTS (Rel8.exists)" \transaction -> do
294308 rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable
295309
296− transaction \connection -> do
297− exists <- liftIO $ Rel8.select connection $ Rel8.exists $ Rel8.values $ Rel8.lit <$> rows
310+ transaction do
311+ exists <- lift do
312+ statement () $ Rel8.select do
313+ Rel8.exists $ Rel8.values $ Rel8.lit <$> rows
298314
299315 case rows of
300316 [] -> exists === [False]
@@ -305,9 +321,10 @@ testOptional :: IO TmpPostgres.DB -> TestTree
305321testOptional = databasePropertyTest "Rel8.optional" \transaction -> do
306322 rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable
307323
308− transaction \connection -> do
309− selected <- liftIO $ Rel8.select connection do
310− Rel8.optional $ Rel8.values (Rel8.lit <$> rows)
324+ transaction do
325+ selected <- lift do
326+ statement () $ Rel8.select do
327+ Rel8.optional $ Rel8.values (Rel8.lit <$> rows)
311328
312329 case rows of
313330 [] -> selected === [Nothing]
@@ -318,9 +335,10 @@ testAnd :: IO TmpPostgres.DB -> TestTree
318335testAnd = databasePropertyTest "AND (&&.)" \transaction -> do
319336 (x, y) <- forAll $ liftA2 (,) Gen.bool Gen.bool
320337
321− transaction \connection -> do
322− [result] <- liftIO $ Rel8.select connection $ pure $
323− Rel8.lit x Rel8.&&. Rel8.lit y
338+ transaction do
339+ [result] <- lift do
340+ statement () $ Rel8.select do
341+ pure $ Rel8.lit x Rel8.&&. Rel8.lit y
324342
325343 result === (x && y)
326344
@@ -329,9 +347,10 @@ testOr :: IO TmpPostgres.DB -> TestTree
329347testOr = databasePropertyTest "OR (||.)" \transaction -> do
330348 (x, y) <- forAll $ liftA2 (,) Gen.bool Gen.bool
331349
332− transaction \connection -> do
333− [result] <- liftIO $ Rel8.select connection $ pure $
334− Rel8.lit x Rel8.||. Rel8.lit y
350+ transaction do
351+ [result] <- lift do
352+ statement () $ Rel8.select $ pure $
353+ Rel8.lit x Rel8.||. Rel8.lit y
335354
336355 result === (x || y)
337356
@@ -340,9 +359,10 @@ testLogicalFixities :: IO TmpPostgres.DB -> TestTree
340359testLogicalFixities = databasePropertyTest "Logical operator fixities" \transaction -> do
341360 (u, v, w, x) <- forAll $ (,,,) <$> Gen.bool <*> Gen.bool <*> Gen.bool <*> Gen.bool
342361
343− transaction \connection -> do
344− [result] <- liftIO $ Rel8.select connection $ pure $
345− Rel8.lit u Rel8.||. Rel8.lit v Rel8.&&. Rel8.lit w Rel8.==. Rel8.lit x
362+ transaction do
363+ [result] <- lift do
364+ statement () $ Rel8.select do
365+ pure $ Rel8.lit u Rel8.||. Rel8.lit v Rel8.&&. Rel8.lit w Rel8.==. Rel8.lit x
346366
347367 result === (u || v && w == x)
348368
@@ -351,9 +371,10 @@ testNot :: IO TmpPostgres.DB -> TestTree
351371testNot = databasePropertyTest "NOT (not_)" \transaction -> do
352372 x <- forAll Gen.bool
353373
354− transaction \connection -> do
355− [result] <- liftIO $ Rel8.select connection $ pure $
356− Rel8.not_ $ Rel8.lit x
374+ transaction do
375+ [result] <- lift do
376+ statement () $ Rel8.select do
377+ pure $ Rel8.not_ $ Rel8.lit x
357378
358379 result === not x
359380
@@ -362,9 +383,10 @@ testBool :: IO TmpPostgres.DB -> TestTree
362383testBool = databasePropertyTest "ifThenElse_" \transaction -> do
363384 (x, y, z) <- forAll $ liftA3 (,,) Gen.bool Gen.bool Gen.bool
364385
365− transaction \connection -> do
366− [result] <- liftIO $ Rel8.select connection $ pure $
367− Rel8.bool (Rel8.lit z) (Rel8.lit y) (Rel8.lit x)
386+ transaction do
387+ [result] <- lift do
388+ statement () $ Rel8.select do
389+ pure $ Rel8.bool (Rel8.lit z) (Rel8.lit y) (Rel8.lit x)
368390
369391 result === if x then y else z
370392
@@ -376,9 +398,10 @@ testAp = databasePropertyTest "Cartesian product (<*>)" \transaction -> do
376398 (Gen.list (Range.linear 1 10) genTestTable)
377399 (Gen.list (Range.linear 1 10) genTestTable)
378400
379− transaction \connection -> do
380− result <- liftIO $ Rel8.select connection $ do
381− liftA2 (,) (Rel8.values (Rel8.lit <$> rows1)) (Rel8.values (Rel8.lit <$> rows2))
401+ transaction do
402+ result <- lift do
403+ statement () $ Rel8.select do
404+ liftA2 (,) (Rel8.values (Rel8.lit <$> rows1)) (Rel8.values (Rel8.lit <$> rows2))
382405
383406 sort result === sort (liftA2 (,) rows1 rows2)
384407
@@ -407,21 +430,22 @@ testDBType getTestDatabase = testGroup "DBType instances"
407430 where
408431 dbTypeTest :: (Eq a, Show a, Rel8.DBType a) => TestName -> Gen a -> TestTree
409432 dbTypeTest name generator = testGroup name
410− [ databasePropertyTest name (t (==) generator) getTestDatabase
411− , databasePropertyTest ("Maybe " <> name) (t (==) (Gen.maybe generator)) getTestDatabase
433+ [ databasePropertyTest name (t generator) getTestDatabase
434+ , databasePropertyTest ("Maybe " <> name) (t (Gen.maybe generator)) getTestDatabase
412435 ]
413436
414− t :: forall a b. (Show a, Rel8.Sql Rel8.DBType a)
415− => (a -> a -> Bool)
416− -> Gen a
417− -> ((Connection -> TestT IO ()) -> PropertyT IO b)
437+ t :: forall a b. (Eq a, Show a, Rel8.Sql Rel8.DBType a)
438+ => Gen a
439+ -> (TestT Transaction () -> PropertyT IO b)
418440 -> PropertyT IO b
419− t eq generator transaction = do
441+ t generator transaction = do
420442 x <- forAll generator
421443
422− transaction \connection -> do
423− [res] <- liftIO $ Rel8.select connection $ pure (Rel8.litExpr x)
424− diff res eq x
444+ transaction do
445+ [res] <- lift do
446+ statement () $ Rel8.select do
447+ pure (Rel8.litExpr x)
448+ diff res (==) x
425449
426450 genDay :: Gen Day
427451 genDay = do
@@ -463,13 +487,15 @@ testDBEq getTestDatabase = testGroup "DBEq instances"
463487
464488 t :: forall a. (Eq a, Show a, Rel8.Sql Rel8.DBEq a)
465489 => Gen a
466− -> ((Connection -> TestT IO ()) -> PropertyT IO ())
490+ -> (TestT Transaction () -> PropertyT IO ())
467491 -> PropertyT IO ()
468492 t generator transaction = do
469493 (x, y) <- forAll (liftA2 (,) generator generator)
470494
471− transaction \connection -> do
472− [res] <- liftIO $ Rel8.select connection $ pure $ Rel8.litExpr x Rel8.==. Rel8.litExpr y
495+ transaction do
496+ [res] <- lift do
497+ statement () $ Rel8.select do
498+ pure $ Rel8.litExpr x Rel8.==. Rel8.litExpr y
473499 res === (x == y)
474500
475501
@@ -477,9 +503,10 @@ testTableEquality :: IO TmpPostgres.DB -> TestTree
477503testTableEquality = databasePropertyTest "TestTable equality" \transaction -> do
478504 (x, y) <- forAll $ liftA2 (,) genTestTable genTestTable
479505
480− transaction \connection -> do
481− [eq] <- liftIO $ Rel8.select connection do
482− pure $ Rel8.lit x Rel8.==: Rel8.lit y
506+ transaction do
507+ [eq] <- lift do
508+ statement () $ Rel8.select do
509+ pure $ Rel8.lit x Rel8.==: Rel8.lit y
483510
484511 eq === (x == y)
485512
@@ -488,8 +515,10 @@ testFromString :: IO TmpPostgres.DB -> TestTree
488515testFromString = databasePropertyTest "FromString" \transaction -> do
489516 str <- forAll $ Gen.list (Range.linear 0 10) Gen.unicode
490517
491− transaction \connection -> do
492− [result] <- liftIO $ Rel8.select connection $ pure $ fromString str
518+ transaction do
519+ [result] <- lift do
520+ statement () $ Rel8.select do
521+ pure $ fromString str
493522 result === pack str
494523
495524
@@ -497,10 +526,11 @@ testCatMaybeTable :: IO TmpPostgres.DB -> TestTree
497526testCatMaybeTable = databasePropertyTest "catMaybeTable" \transaction -> do
498527 rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable
499528
500− transaction \connection -> do
501− selected <- liftIO $ Rel8.select connection do
502− testTable <- Rel8.values $ Rel8.lit <$> rows
503− Rel8.catMaybeTable $ Rel8.bool Rel8.nothingTable (pure testTable) (testTableColumn2 testTable)
529+ transaction do
530+ selected <- lift do
531+ statement () $ Rel8.select do
532+ testTable <- Rel8.values $ Rel8.lit <$> rows
533+ Rel8.catMaybeTable $ Rel8.bool Rel8.nothingTable (pure testTable) (testTableColumn2 testTable)
504534
505535 sort selected === sort (filter testTableColumn2 rows)
506536
@@ -509,9 +539,10 @@ testCatMaybe :: IO TmpPostgres.DB -> TestTree
509539testCatMaybe = databasePropertyTest "catMaybe" \transaction -> evalM do
510540 rows <- forAll $ Gen.list (Range.linear 0 10) $ Gen.maybe Gen.bool
511541
512− transaction \connection -> do
513− selected <- evalM $ liftIO $ Rel8.select connection do
514− Rel8.catNull =<< Rel8.values (map Rel8.lit rows)
542+ transaction do
543+ selected <- lift do
544+ statement () $ Rel8.select do
545+ Rel8.catNull =<< Rel8.values (map Rel8.lit rows)
515546
516547 sort selected === sort (catMaybes rows)
517548
@@ -520,15 +551,38 @@ testMaybeTable :: IO TmpPostgres.DB -> TestTree
520551testMaybeTable = databasePropertyTest "maybeTable" \transaction -> evalM do
521552 (rows, def) <- forAll $ liftA2 (,) (Gen.list (Range.linear 0 10) genTestTable) genTestTable
522553
523− transaction \connection -> do
524− selected <- liftIO $ Rel8.select connection $
525− Rel8.maybeTable (Rel8.lit def) id <$> Rel8.optional (Rel8.values (Rel8.lit <$> rows))
554+ transaction do
555+ selected <- lift do
556+ statement () $ Rel8.select do
557+ Rel8.maybeTable (Rel8.lit def) id <$> Rel8.optional (Rel8.values (Rel8.lit <$> rows))
526558
527559 case rows of
528560 [] -> selected === [def]
529561 _ -> sort selected === sort rows
530562
531563
564+testAggregateMaybeTable :: IO TmpPostgres.DB -> TestTree
565+testAggregateMaybeTable = databasePropertyTest "aggregateMaybeTable" \transaction -> evalM do
566+ rows <- forAll $ Gen.list (Range.linear 0 10) (Gen.maybe (Gen.int64 (Range.linear 0 10)))
567+
568+ let
569+ aggregate = go 0 False False
570+ where
571+ go !n nothing _ (Just a : as) = go (n + a) nothing True as
572+ go n _ just (Nothing : as) = go n True just as
573+ go _ False False [] = []
574+ go _ True False [] = [Nothing]
575+ go n False True [] = [Just n]
576+ go n True True [] = [Nothing, Just n]
577+
578+ transaction do
579+ selected <- lift do
580+ statement () $ Rel8.select do
581+ Rel8.aggregate $ Rel8.aggregateMaybeTable Rel8.sum <$> Rel8.values (Rel8.lit <$> rows)
582+
583+ sort selected === aggregate rows
584+
585+
532586data TwoTestTables f =
533587 TwoTestTables
534588 { testTable1 :: TestTable f
@@ -549,9 +603,10 @@ testNestedTables = databasePropertyTest "Nested TestTables" \transaction -> eval
549603 Gen.list (Range.linear 0 10) $
550604 liftA2 TwoTestTables genTestTable genTestTable
551605
552− transaction \connection -> do
553− selected <- liftIO $ Rel8.select connection do
554− Rel8.values (Rel8.lit <$> rows)
606+ transaction do
607+ selected <- lift do
608+ statement () $ Rel8.select do
609+ Rel8.values (Rel8.lit <$> rows)
555610
556611 sort selected === sort rows
557612
@@ -561,11 +616,12 @@ testMaybeTableApplicative = databasePropertyTest "MaybeTable (<*>)" \transaction
561616 rows1 <- genRows
562617 rows2 <- genRows
563618
564− transaction \connection -> do
565− selected <- liftIO $ Rel8.select connection do
566− as <- Rel8.optional (Rel8.values (Rel8.lit <$> rows1))
567− bs <- Rel8.optional (Rel8.values (Rel8.lit <$> rows2))
568− pure $ liftA2 (,) as bs
619+ transaction do
620+ selected <- lift do
621+ statement () $ Rel8.select do
622+ as <- Rel8.optional (Rel8.values (Rel8.lit <$> rows1))
623+ bs <- Rel8.optional (Rel8.values (Rel8.lit <$> rows2))
624+ pure $ liftA2 (,) as bs
569625
570626 case (rows1, rows2) of
571627 ([], []) -> selected === [Nothing]
@@ -577,14 +633,6 @@ testMaybeTableApplicative = databasePropertyTest "MaybeTable (<*>)" \transaction
577633 genRows = forAll do
578634 Gen.list (Range.linear 0 10) $ liftA2 TestTable (Gen.text (Range.linear 0 10) Gen.unicode) (pure True)
579635
580−rollingBack
581− :: (MonadBaseControl IO m, MonadIO m)
582− => Connection -> m a -> m a
583−rollingBack connection =
584− bracket_
585− (liftIO (run (sql "BEGIN") connection))
586− (liftIO (run (sql "ROLLBACK") connection))
587−
588636
589637genTestTable :: Gen (TestTable Result)
590638genTestTable = do
@@ -597,17 +645,16 @@ testUpdate :: IO TmpPostgres.DB -> TestTree
597645testUpdate = databasePropertyTest "Can UPDATE TestTable" \transaction -> do
598646 rows <- forAll $ Gen.map (Range.linear 0 5) $ liftA2 (,) genTestTable genTestTable
599647
600− transaction \connection -> do
601− liftIO $ Rel8.insert connection
602− Rel8.Insert
648+ transaction do
649+ selected <- lift do
650+ statement () $ Rel8.insert Rel8.Insert
603651 { into = testTableSchema
604652 , rows = Rel8.values $ map Rel8.lit $ Map.keys rows
605653 , onConflict = Rel8.DoNothing
606654 , returning = pure ()
607655 }
608656
609− liftIO $ Rel8.update connection
610− Rel8.Update
657+ statement () $ Rel8.update Rel8.Update
611658 { target = testTableSchema
612659 , from = pure ()
613660 , set = \_ r ->
@@ -628,8 +675,8 @@ testUpdate = databasePropertyTest "Can UPDATE TestTable" \transaction -> do
628675 , returning = pure ()
629676 }
630677
631− selected <- liftIO $ Rel8.select connection do
632− Rel8.each testTableSchema
678+ statement () $ Rel8.select do
679+ Rel8.each testTableSchema
633680
634681 sort selected === sort (Map.elems rows)
635682
@@ -642,30 +689,96 @@ testDelete :: IO TmpPostgres.DB -> TestTree
642689testDelete = databasePropertyTest "Can DELETE TestTable" \transaction -> do
643690 rows <- forAll $ Gen.list (Range.linear 0 5) genTestTable
644691
645− transaction \connection -> do
646− liftIO $ Rel8.insert connection
647− Rel8.Insert
692+ transaction do
693+ (deleted, selected) <- lift do
694+ statement () $ Rel8.insert Rel8.Insert
648695 { into = testTableSchema
649696 , rows = Rel8.values $ map Rel8.lit rows
650697 , onConflict = Rel8.DoNothing
651698 , returning = pure ()
652699 }
653700
654− deleted <-
655− liftIO $ Rel8.delete connection
656− Rel8.Delete
701+ deleted <- statement () $ Rel8.delete Rel8.Delete
657702 { from = testTableSchema
658703 , using = pure ()
659704 , deleteWhere = const testTableColumn2
660705 , returning = Rel8.Projection id
661706 }
662707
663− selected <- liftIO $ Rel8.select connection do
664− Rel8.each testTableSchema
708+ selected <- statement () $ Rel8.select do
709+ Rel8.each testTableSchema
710+
711+ pure (deleted, selected)
665712
666713 sort (deleted <> selected) === sort rows
667714
668715
716+data UniqueTable f = UniqueTable
717+ { uniqueTableKey :: Rel8.Column f Text
718+ , uniqueTableValue :: Rel8.Column f Text
719+ }
720+ deriving stock Generic
721+ deriving anyclass Rel8.Rel8able
722+
723+
724+deriving stock instance Eq (UniqueTable Result)
725+deriving stock instance Ord (UniqueTable Result)
726+deriving stock instance Show (UniqueTable Result)
727+
728+
729+uniqueTableSchema :: Rel8.TableSchema (UniqueTable Rel8.Name)
730+uniqueTableSchema =
731+ Rel8.TableSchema
732+ { name = "unique_table"
733+ , schema = Nothing
734+ , columns = UniqueTable
735+ { uniqueTableKey = "key"
736+ , uniqueTableValue = "value"
737+ }
738+ }
739+
740+
741+genUniqueTable :: Gen (UniqueTable Result)
742+genUniqueTable = do
743+ uniqueTableKey <- Gen.text (Range.linear 0 5) Gen.alphaNum
744+ uniqueTableValue <- Gen.text (Range.linear 0 5) Gen.alphaNum
745+ pure UniqueTable {..}
746+
747+
748+testUpsert :: IO TmpPostgres.DB -> TestTree
749+testUpsert = databasePropertyTest "Can UPSERT UniqueTable" \transaction -> do
750+ as <- unique $ forAll $ Gen.list (Range.linear 0 20) genUniqueTable
751+ bs <- unique $ forAll $ Gen.list (Range.linear 0 20) genUniqueTable
752+
753+ transaction do
754+ selected <- lift do
755+ statement () $ Rel8.insert Rel8.Insert
756+ { into = uniqueTableSchema
757+ , rows = Rel8.values $ Rel8.lit <$> as
758+ , onConflict = Rel8.DoNothing
759+ , returning = pure ()
760+ }
761+
762+ statement () $ Rel8.insert Rel8.Insert
763+ { into = uniqueTableSchema
764+ , rows = Rel8.values $ Rel8.lit <$> bs
765+ , onConflict = Rel8.DoUpdate Rel8.Upsert
766+ { index = uniqueTableKey
767+ , set = \UniqueTable {uniqueTableValue} old -> old {uniqueTableValue}
768+ , updateWhere = \_ _ -> Rel8.true
769+ }
770+ , returning = pure ()
771+ }
772+
773+ statement () $ Rel8.select do
774+ Rel8.each uniqueTableSchema
775+
776+ fromUniqueTables selected === fromUniqueTables bs <> fromUniqueTables as
777+ where
778+ unique = fmap (nubOrdOn uniqueTableKey)
779+ fromUniqueTables = Map.fromList . map \(UniqueTable key value) -> (key, value)
780+
781+
669782newtype HKNestedPair f = HKNestedPair { pairOne :: (TestTable f, TestTable f) }
670783 deriving stock Generic
671784 deriving anyclass Rel8.Rel8able
@@ -679,9 +792,10 @@ testSelectNestedPairs :: IO TmpPostgres.DB -> TestTree
679792testSelectNestedPairs = databasePropertyTest "Can SELECT nested pairs" \transaction -> do
680793 rows <- forAll $ Gen.list (Range.linear 0 10) $ HKNestedPair <$> liftA2 (,) genTestTable genTestTable
681794
682− transaction \connection -> do
683− selected <- liftIO $ Rel8.select connection do
684− Rel8.values $ map Rel8.lit rows
795+ transaction do
796+ selected <- lift do
797+ statement () $ Rel8.select do
798+ Rel8.values $ map Rel8.lit rows
685799
686800 sort selected === sort rows
687801
@@ -690,18 +804,20 @@ testSelectArray :: IO TmpPostgres.DB -> TestTree
690804testSelectArray = databasePropertyTest "Can SELECT Arrays (with aggregation)" \transaction -> do
691805 rows <- forAll $ Gen.list (Range.linear 1 10) Gen.bool
692806
693− transaction \connection -> do
694− selected <- liftIO $ Rel8.select connection do
695− Rel8.many $ Rel8.values (map Rel8.lit rows)
807+ transaction do
808+ selected <- lift do
809+ statement () $ Rel8.select do
810+ Rel8.many $ Rel8.values (map Rel8.lit rows)
696811
697812 selected === [foldMap pure rows]
698813
699− selected' <- liftIO $ Rel8.select connection $ do
700− a <- Rel8.catListTable =<< do
701− Rel8.many $ Rel8.values (map Rel8.lit rows)
702− b <- Rel8.catListTable =<< do
703− Rel8.many $ Rel8.values (map Rel8.lit rows)
704− pure (a, b)
814+ selected' <- lift do
815+ statement () $ Rel8.select do
816+ a <- Rel8.catListTable =<< do
817+ Rel8.many $ Rel8.values (map Rel8.lit rows)
818+ b <- Rel8.catListTable =<< do
819+ Rel8.many $ Rel8.values (map Rel8.lit rows)
820+ pure (a, b)
705821
706822 selected' === liftA2 (,) rows rows
707823
@@ -723,10 +839,11 @@ testNestedMaybeTable :: IO TmpPostgres.DB -> TestTree
723839testNestedMaybeTable = databasePropertyTest "Can nest MaybeTable within other tables" \transaction -> do
724840 let example = NestedMaybeTable { nmt1 = True, nmt2 = Just (TestTable "Hi" True) }
725841
726− transaction \connection -> do
727− selected <- liftIO $ Rel8.select connection do
728− x <- Rel8.values [Rel8.lit example]
729− pure $ Rel8.maybeTable (Rel8.lit False) (\_ -> Rel8.lit True) (nmt2 x)
842+ transaction do
843+ selected <- lift do
844+ statement () $ Rel8.select do
845+ x <- Rel8.values [Rel8.lit example]
846+ pure $ Rel8.maybeTable (Rel8.lit False) (\_ -> Rel8.lit True) (nmt2 x)
730847
731848 selected === [True]
732849
@@ -734,11 +851,12 @@ testNestedMaybeTable = databasePropertyTest "Can nest MaybeTable within other ta
734851testEvaluate :: IO TmpPostgres.DB -> TestTree
735852testEvaluate = databasePropertyTest "evaluate has the evaluation order we expect" \transaction -> do
736853
737− transaction \connection -> do
738− selected <- liftIO $ Rel8.select connection do
739− x <- Rel8.values (Rel8.lit <$> ['a', 'b', 'c'])
740− y <- Rel8.evaluate (Rel8.nextval "test_seq")
741− pure (x, (y, y))
854+ transaction do
855+ selected <- lift do
856+ statement () $ Rel8.select do
857+ x <- Rel8.values (Rel8.lit <$> ['a', 'b', 'c'])
858+ y <- Rel8.evaluate (Rel8.nextval "test_seq")
859+ pure (x, (y, y))
742860
743861 normalize selected ===
744862 [ ('a', (0, 0))
@@ -746,11 +864,12 @@ testEvaluate = databasePropertyTest "evaluate has the evaluation order we expect
746864 , ('c', (2, 2))
747865 ]
748866
749− selected' <- liftIO $ Rel8.select connection do
750− x <- Rel8.values (Rel8.lit <$> ['a', 'b', 'c'])
751− y <- Rel8.values (Rel8.lit <$> ['d', 'e', 'f'])
752− z <- Rel8.evaluate (Rel8.nextval "test_seq")
753− pure ((x, y), (z, z))
867+ selected' <- lift do
868+ statement () $ Rel8.select do
869+ x <- Rel8.values (Rel8.lit <$> ['a', 'b', 'c'])
870+ y <- Rel8.values (Rel8.lit <$> ['d', 'e', 'f'])
871+ z <- Rel8.evaluate (Rel8.nextval "test_seq")
872+ pure ((x, y), (z, z))
754873
755874 normalize selected' ===
756875 [ (('a', 'd'), (0, 0))
@@ -768,13 +887,3 @@ testEvaluate = databasePropertyTest "evaluate has the evaluation order we expect
768887 normalize :: [(x, (Int64, Int64))] -> [(x, (Int64, Int64))]
769888 normalize [] = []
770889 normalize xs@((_, (i, _)) : _) = map (fmap (\(a, b) -> (a - i, b - i))) xs
771−
772−{-
773− selected <- liftIO $ Rel8.select connection do
774− x <- Rel8.values (Rel8.lit <$> ['a', 'b', 'c'])
775− x <- Rel8.values (Rel8.lit <$> ['d', 'e', 'f'])
776− y <- Rel8.evaluate (Rel8.nextval "test_seq")
777− pure (x, y, y)
778−
779− selected === [('a', 1, 1), ('b', 2, 2), ('c', 3, 3)]
780−-}