on master help sign in
Remove doctest for now

Now that we're on Read the Docs, I think that will be a better home for these examples. That, and nothing actually passes at the moment.

commit9216dbd13d3fce4e2bbe9650325caf85e0a8dd21 authorOllie Charles <ollie@ocharles.org.uk> dateFri 2 Apr 2021, 19:19 (6 years ago) parent0857ffb
29 files changed+7−535
Doctest.hs−53 Setup.hs−6 rel8.cabal+1−20 src/Rel8/Expr/Bool.hs−40 src/Rel8/Expr/Eq.hs−18 src/Rel8/Expr/Function.hs−31 src/Rel8/Expr/Null.hs−12 src/Rel8/Expr/Order.hs−12 src/Rel8/Order.hs+1−11 src/Rel8/Query/Distinct.hs−3 src/Rel8/Query/Each.hs−5 src/Rel8/Query/Exists.hs−32 src/Rel8/Query/Filter.hs−11 src/Rel8/Query/Limit.hs−6 src/Rel8/Query/Maybe.hs+4−61 src/Rel8/Query/Null.hs−15 src/Rel8/Query/Set.hs−18 src/Rel8/Query/Values.hs−6 src/Rel8/Schema/Column.hs−26 src/Rel8/Schema/Table.hs+1−14 src/Rel8/Statement/Delete.hs−18 src/Rel8/Statement/Insert.hs−10 src/Rel8/Statement/Update.hs−20 src/Rel8/Table/Maybe.hs−3 src/Rel8/Type.hs−12 src/Rel8/Type/Eq.hs−30 src/Rel8/Type/Information.hs−18 src/Rel8/Type/JSONEncoded.hs−13 src/Rel8/Type/ReadShow.hs−11
Doctest.hsdeleted−53
@@ -1,53 +0,0 @@
1−{-# language BlockArguments #-}
2−{-# language OverloadedStrings #-}
3−
4−module Main where
5−
6−-- base
7−import Control.Exception ( bracket, throwIO )
8−import Data.Foldable ( traverse_ )
9−import System.Environment ( lookupEnv, setEnv )
10−
11−-- base-compat
12−import System.Environment.Compat ( unsetEnv )
13−
14−-- bytestring
15−import Data.ByteString.Char8 ( unpack )
16−
17−-- doctest
18−import Test.DocTest ( doctest )
19−
20−-- hasql
21−import Hasql.Connection ( acquire, release )
22−import Hasql.Session ( run, sql )
23−
24−-- rel8
25−import Build_doctests ( flags, module_sources, pkgs )
26−
27−-- tmp-postgres
28−import Database.Postgres.Temp ( toConnectionString, verboseConfig, with, withConfig )
29−
30−
31−main :: IO ()
32−main = do
33− nixGhcLibdir <- lookupEnv "NIX_GHC_LIBDIR"
34− unsetEnv "GHC_ENVIRONMENT"
35− either throwIO return =<< with \db -> do
36− setEnv "TEST_DATABASE_URL" (unpack (toConnectionString db))
37− bracket (either (error . show) return =<< acquire (toConnectionString db)) release \conn -> do
38− flip run conn do
39− sql "create table author ( author_id serial primary key, name text not null, url text )"
40− sql "create table project ( author_id int not null references author (author_id), name text not null )"
41−
42− sql "insert into author ( name, url ) values ( 'Ollie', 'https://ocharles.org.uk' )"
43− sql "insert into author ( name, url ) values ( 'Bryan O''Sullivan', null )"
44− sql "insert into author ( name, url ) values ( 'Emily Pillmore', 'https://cohomolo.gy' )"
45− sql "insert into project ( author_id, name ) values ( 1, 'rel8' )"
46− sql "insert into project ( author_id, name ) values ( 2, 'aeson' )"
47− sql "insert into project ( author_id, name ) values ( 2, 'text' )"
48−
49− doctest (args nixGhcLibdir)
50−
51− where
52− args nixGhcLibdir =
53− flags ++ pkgs ++ foldMap (\x -> ["-package-db" <> x <> "/package.conf.d"]) nixGhcLibdir ++ module_sources
Setup.hsdeleted−6
@@ -1,6 +0,0 @@
1−module Main where
2−
3−import Distribution.Extra.Doctest (defaultMainWithDoctests)
4−
5−main :: IO ()
6−main = defaultMainWithDoctests "doctests"
rel8.cabal+1−20
@@ -6,13 +6,7 @@ license: BSD3
66license-file: LICENSE
77author: Oliver Charles
88maintainer: ollie@ocharles.org.uk
9−build-type: Custom
10−
11−custom-setup
12− setup-depends:
13− base >= 4 && <5,
14− Cabal,
15− cabal-doctest >= 1 && <1.1
9+build-type: Simple
1610
1711library
1812 build-depends:
@@ -201,16 +195,3 @@ test-suite tests
201195 -Wno-missing-import-lists -Wno-prepositive-qualified-module
202196 -Wno-deprecations -Wno-monomorphism-restriction
203197 -Wno-missing-local-signatures -Wno-implicit-prelude
204−
205−test-suite doctests
206− type: exitcode-stdio-1.0
207− build-depends:
208− base
209− , base-compat
210− , bytestring
211− , doctest
212− , hasql
213− , tmp-postgres
214−
215− main-is: Doctest.hs
216− default-language: Haskell2010
src/Rel8/Expr/Bool.hs−40
@@ -32,68 +32,28 @@ true = litExpr True
3232
3333
3434-- | The SQL @AND@ operator.
35−--
36−-- >>> :{
37−-- mapM_ print =<< select c do
38−-- x <- values [lit True, lit False]
39−-- y <- values [lit True, lit False]
40−-- return (x, y, x &&. y)
41−-- :}
42−-- (True,True,True)
43−-- (True,False,False)
44−-- (False,True,False)
45−-- (False,False,False)
4635(&&.) :: Expr Bool -> Expr Bool -> Expr Bool
4736(&&.) = zipPrimExprsWith (Opaleye.BinExpr Opaleye.OpAnd)
4837infixr 3 &&.
4938
5039
5140-- | The SQL @OR@ operator.
52−--
53−-- >>> :{
54−-- mapM_ print =<< select c do
55−-- x <- values [lit True, lit False]
56−-- y <- values [lit True, lit False]
57−-- return (x, y, x ||. y)
58−-- :}
59−-- (True,True,True)
60−-- (True,False,True)
61−-- (False,True,True)
62−-- (False,False,False)
6341(||.) :: Expr Bool -> Expr Bool -> Expr Bool
6442(||.) = zipPrimExprsWith (Opaleye.BinExpr Opaleye.OpOr)
6543infixr 2 ||.
6644
6745
6846-- | The SQL @NOT@ operator.
69−--
70−-- >>> select c $ pure $ not_ $ lit True
71−-- [False]
72−--
73−-- >>> select c $ pure $ not_ $ lit False
74−-- [True]
7547not_ :: Expr Bool -> Expr Bool
7648not_ = mapPrimExpr (Opaleye.UnExpr Opaleye.OpNot)
7749
7850
7951-- | Fold @AND@ over a collection of expressions.
80−--
81−-- >>> select c $ pure $ and_ [ lit True ==. lit False, lit False, lit True ]
82−-- [False]
83−--
84−-- >>> select c $ pure $ and_ []
85−-- [True]
8652and_ :: Foldable f => f (Expr Bool) -> Expr Bool
8753and_ = foldl' (&&.) true
8854
8955
9056-- | Fold @OR@ over a collection of expressions.
91−--
92−-- >>> select c $ pure $ or_ [ lit True ==. lit False, lit False, lit True ]
93−-- [True]
94−--
95−-- >>> select c $ pure $ or_ []
96−-- [False]
9757or_ :: Foldable f => f (Expr Bool) -> Expr Bool
9858or_ = foldl' (||.) false
9959
src/Rel8/Expr/Eq.hs−18
@@ -74,12 +74,6 @@ infix 4 /=.
7474--
7575-- This corresponds to the SQL @=@ operator, though it will always return a
7676-- 'Bool'.
77−--
78−-- >>> select c $ pure $ lit Nothing ==? lit True
79−-- False
80−--
81−-- >>> select c $ pure $ lit Nothing ==? lit Nothing
82−-- False
8377(==?) :: DBEq a => Expr (Maybe a) -> Expr (Maybe a) -> Expr Bool
8478a ==? b = coalesce $ unsafeLiftOpNullable eq a b
8579infix 4 ==?
@@ -89,12 +83,6 @@ infix 4 ==?
8983--
9084-- This corresponds to the SQL @<>@ operator, though it will always return a
9185-- 'Bool'.
92−--
93−-- >>> select c $ pure $ lit Nothing /=? lit True
94−-- True
95−--
96−-- >>> select c $ pure $ lit Nothing /=? lit Nothing
97−-- False
9886(/=?) :: DBEq a => Expr (Maybe a) -> Expr (Maybe a) -> Expr Bool
9987a /=? b = coalesce $ unsafeLiftOpNullable ne a b
10088infix 4 /=?
@@ -102,12 +90,6 @@ infix 4 /=?
10290
10391-- | Like the SQL @IN@ operator, but implemented by folding over a list with
10492-- '==.' and '||.'.
105−--
106−-- >>> select c $ return $ lit (5 :: Int32) `in_` [ lit x | x <- [1..5] ]
107−-- [True]
108−--
109−-- >>> select c $ return $ lit (42 :: Int32) `in_` [ lit x | x <- [1..5] ]
110−-- [False]
11193in_ :: forall a f. (Sql DBEq a, Foldable f)
11294 => Expr a -> f (Expr a) -> Expr Bool
11395in_ a (toList -> as) = case nullabilization @a of
src/Rel8/Expr/Function.hs−31
@@ -47,42 +47,11 @@ instance (arg ~ Expr a, Function args res) => Function arg (args -> res) where
4747
4848-- | Construct an n-ary function that produces an 'Expr' that when called runs
4949-- a SQL function.
50−--
51−-- For example, here's how we can wrap PostgreSQL's @factorial@ function:
52−--
53−-- >>> :{
54−-- factorial :: Expr Int64 -> Expr Data.Scientific.Scientific
55−-- factorial = function "factorial"
56−-- :}
57−--
58−-- >>> select c $ pure $ factorial 5
59−-- [120.0]
60−--
61−-- The same approach works for any number of arguments:
62−--
63−-- >>> :{
64−-- power :: Expr Float -> Expr Float -> Expr Double
65−-- power = function "power"
66−-- :}
67−--
68−-- >>> select c $ pure $ power 9 3
69−-- [729.0]
7050function :: Function args result => String -> args -> result
7151function = applyArgument . Opaleye.FunExpr
7252
7353
7454-- | Construct a function call for functions with no arguments.
75−--
76−-- For example, we can call the database function @pi()@ by using
77−-- @nullaryFunction@:
78−--
79−-- >>> :{
80−-- sqlPi :: Expr Double
81−-- sqlPi = nullaryFunction "pi"
82−-- :}
83−--
84−-- >>> select c $ pure $ sqlPi
85−-- [3.141592653589793]
8655nullaryFunction :: Sql DBType a => String -> Expr a
8756nullaryFunction name = castExpr $ Expr (Opaleye.FunExpr name [])
8857
src/Rel8/Expr/Null.hs−12
@@ -40,12 +40,6 @@ unsafeUnnullify (Expr a) = Expr a
4040
4141
4242-- | Like 'maybe', but to eliminate @null@.
43−--
44−-- >>> select c $ pure $ null 0 id (nullExpr :: Expr (Maybe Int32))
45−-- [0]
46−--
47−-- >>> select c $ pure $ null 0 id (lit (Just 42) :: Expr (Maybe Int32))
48−-- [42]
4943nullable :: Expr b -> (Expr a -> Expr b) -> Expr (Maybe a) -> Expr b
5044nullable b f ma = boolExpr (f (unsafeUnnullify ma)) b (isNull ma)
5145
@@ -55,12 +49,6 @@ nullableOf = maybe null nullify
5549
5650
5751-- | Like 'isNothing', but for @null@.
58−--
59−-- >>> select c $ pure $ isNull (nullExpr :: Expr (Maybe Int32))
60−-- [True]
61−--
62−-- >>> select c $ pure $ isNull (lit (Just 42) :: Expr (Maybe Int32))
63−-- [False]
6452isNull :: Expr (Maybe a) -> Expr Bool
6553isNull = mapPrimExpr (Opaleye.UnExpr Opaleye.OpIsNull)
6654
src/Rel8/Expr/Order.hs−12
@@ -28,9 +28,6 @@ import Rel8.Type.Ord ( DBOrd )
2828
2929
3030-- | Sort a column in ascending order.
31−--
32−-- >>> select c $ orderBy asc $ values [ lit x | x <- [1..5 :: Int32] ]
33−-- [1,2,3,4,5]
3431asc :: DBOrd a => Order (Expr a)
3532asc = Order $ Opaleye.Order (\expr -> [(orderOp, toPrimExpr expr)])
3633 where
@@ -42,9 +39,6 @@ asc = Order $ Opaleye.Order (\expr -> [(orderOp, toPrimExpr expr)])
4239
4340
4441-- | Sort a column in descending order.
45−--
46−-- >>> select c $ orderBy desc $ values [ lit x | x <- [1..5 :: Int32] ]
47−-- [5,4,3,2,1]
4842desc :: DBOrd a => Order (Expr a)
4943desc = Order $ Opaleye.Order (\expr -> [(orderOp, toPrimExpr expr)])
5044 where
@@ -57,9 +51,6 @@ desc = Order $ Opaleye.Order (\expr -> [(orderOp, toPrimExpr expr)])
5751
5852-- | Transform an ordering so that @null@ values appear first. This corresponds
5953-- to @NULLS FIRST@ in SQL.
60−--
61−-- >>> select c $ orderBy (nullsFirst desc) $ values $ [ nullExpr, nullExpr ] <> [ lit (Just x) | x <- [1..5 :: Int32] ]
62−-- [Nothing,Nothing,Just 5,Just 4,Just 3,Just 2,Just 1]
6354nullsFirst :: Order (Expr a) -> Order (Expr (Maybe a))
6455nullsFirst (Order (Opaleye.Order f)) =
6556 Order $ Opaleye.Order $ fmap (first g) . f . unsafeUnnullify
@@ -70,9 +61,6 @@ nullsFirst (Order (Opaleye.Order f)) =
7061
7162-- | Transform an ordering so that @null@ values appear first. This corresponds
7263-- to @NULLS LAST@ in SQL.
73−--
74−-- >>> select c $ orderBy (nullsLast desc) $ values $ [ nullExpr, nullExpr ] <> [ lit (Just x) | x <- [1..5 :: Int32] ]
75−-- [Just 5,Just 4,Just 3,Just 2,Just 1,Nothing,Nothing]
7664nullsLast :: Order (Expr a) -> Order (Expr (Maybe a))
7765nullsLast (Order (Opaleye.Order f)) =
7866 Order $ Opaleye.Order $ fmap (first g) . f . unsafeUnnullify
src/Rel8/Order.hs+1−11
@@ -23,17 +23,7 @@ import qualified Opaleye.Internal.Order as Opaleye
2323-- and 'desc', and you can combine @Order@ via its various instances.
2424--
2525-- A common pattern is to use '<>' to combine multiple orderings in sequence,
26−-- and '>$<' (from 'Contravariant') to select individual columns. For example,
27−-- to sort a @Query@ on two columns, we could do:
28−--
29−-- >>> import Data.Functor.Contravariant ((>$<))
30−-- >>> :{
31−-- select c $ orderBy (mconcat [fst >$< asc, snd >$< desc]) $ do
32−-- x <- values [ lit x | x <- [1..3 :: Int32 ] ]
33−-- y <- values [ lit x | x <- [1..3 :: Int32 ] ]
34−-- return (x, y)
35−-- :}
36−-- [(1,3),(1,2),(1,1),(2,3),(2,2),(2,1),(3,3),(3,2),(3,1)]
26+-- and '>$<' (from 'Contravariant') to select individual columns.
3727type Order :: Type -> Type
3828newtype Order a = Order (Opaleye.Order a)
3929 deriving newtype (Contravariant, Divisible, Decidable, Semigroup, Monoid)
src/Rel8/Query/Distinct.hs−3
@@ -25,9 +25,6 @@ import Rel8.Table.Opaleye ( distinctspec, unpackspec )
2525
2626-- | Select all distinct rows from a query, removing duplicates. @distinct q@
2727-- is equivalent to the SQL statement @SELECT DISTINCT q@.
28−--
29−-- >>> select c $ distinct $ values [ lit True, lit True, lit False ]
30−-- [False,True]
3128distinct :: EqTable a => Query a -> Query a
3229distinct = mapOpaleye (Opaleye.distinctExplicit distinctspec)
3330
src/Rel8/Query/Each.hs−5
@@ -22,11 +22,6 @@ import Rel8.Table.Opaleye ( table, unpackspec )
2222
2323-- | Select each row from a table definition. This is equivalent to @FROM
2424-- table@.
25−--
26−-- >>> mapM_ print =<< select c (each projectSchema)
27−-- Project {projectAuthorId = 1, projectName = "rel8"}
28−-- Project {projectAuthorId = 2, projectName = "aeson"}
29−-- Project {projectAuthorId = 2, projectName = "text"}
3025each :: Selects names exprs => TableSchema names -> Query exprs
3126each =
3227 fmap fromColumns .
src/Rel8/Query/Exists.hs−32
@@ -24,18 +24,6 @@ import Rel8.Table.Maybe ( isJustTable )
2424
2525
2626-- | Checks if a query returns at least one row.
27−--
28−-- >>> :{
29−-- mapM_ print =<< select c do
30−-- author <- each authorSchema
31−-- hasProjects <- exists do
32−-- project <- each projectSchema
33−-- where_ $ authorId author ==. projectAuthorId project
34−-- return (authorName author, hasProjects)
35−-- :}
36−-- ("Ollie",True)
37−-- ("Bryan O'Sullivan",True)
38−-- ("Emily Pillmore",False)
3927exists :: Query a -> Query (Expr Bool)
4028exists = fmap isJustTable . optional . whereExists
4129-- FIXME: change this when b7aacc07c6392654cae439fc3b997620c3aa7a87 makes it
@@ -48,32 +36,12 @@ inQuery a = exists . (>>= filter (a ==:))
4836
4937-- | Produce the empty query if the given query returns no rows. @whereExists@
5038-- is equivalent to @WHERE EXISTS@ in SQL.
51−--
52−-- >>> :{
53−-- select c do
54−-- author <- each authorSchema
55−-- whereExists do
56−-- project <- each projectSchema
57−-- where_ $ projectAuthorId project ==. authorId author
58−-- return $ authorName author
59−-- :}
60−-- ["Ollie","Bryan O'Sullivan"]
6139whereExists :: Query a -> Query ()
6240whereExists = mapOpaleye Opaleye.restrictExists
6341
6442
6543-- | Produce the empty query if the given query returns rows. @whereNotExists@
6644-- is equivalent to @WHERE NOT EXISTS@ in SQL.
67−--
68−-- >>> :{
69−-- select c do
70−-- author <- each authorSchema
71−-- whereNotExists do
72−-- project <- each projectSchema
73−-- where_ $ projectAuthorId project ==. authorId author
74−-- return $ authorName author
75−-- :}
76−-- ["Emily Pillmore"]
7745whereNotExists :: Query a -> Query ()
7846whereNotExists = mapOpaleye Opaleye.restrictNotExists
7947
src/Rel8/Query/Filter.hs−11
@@ -24,23 +24,12 @@ import Rel8.Query.Opaleye ( fromOpaleye )
2424-- return @x@ unchanged when @f x@ is @True@. This is similar to
2525-- 'Control.Monad.guard', but as the predicate is separate from the argument,
2626-- it is easy to use in a pipeline of 'Query' transformations.
27−--
28−-- >>> select c $ values [ lit x | x <- [ 1..5 :: Int32 ] ] >>= filter (>. 3)
29−-- [4,5]
3027filter :: (a -> Expr Bool) -> a -> Query a
3128filter f a = a <$ where_ (f a)
3229
3330
3431-- | Drop any rows that don't match a predicate. @where_ expr@ is equivalent
3532-- to the SQL @WHERE expr@.
36−--
37−-- >>> :{
38−-- select c $ do
39−-- x <- values [ lit x | x <- [ 1..5 :: Int32 ] ]
40−-- where_ $ x >. lit 2
41−-- return x
42−-- :}
43−-- [3,4,5]
4433where_ :: Expr Bool -> Query ()
4534where_ condition =
4635 fromOpaleye $ lmap (\_ -> toColumn $ toPrimExpr condition) Opaleye.restrict
src/Rel8/Query/Limit.hs−6
@@ -17,17 +17,11 @@ import Rel8.Query.Opaleye ( mapOpaleye )
1717
1818-- | @limit n@ select at most @n@ rows from a query. @limit n@ is equivalent
1919-- to the SQL @LIMIT n@.
20−--
21−-- >>> select c $ limit 3 $ values [ lit x | x <- [ 1..5 :: Int32 ] ]
22−-- [1,2,3]
2320limit :: Word -> Query a -> Query a
2421limit = mapOpaleye . Opaleye.limit . fromIntegral
2522
2623
2724-- | @offset n@ drops the first @n@ rows from a query. @offset n@ is equivalent
2825-- to the SQL @OFFSET n@.
29−--
30−-- >>> select c $ offset 3 $ values [ lit x | x <- [ 1..5 :: Int32 ] ]
31−-- [4,5]
3226offset :: Word -> Query a -> Query a
3327offset = mapOpaleye . Opaleye.offset . fromIntegral
src/Rel8/Query/Maybe.hs+4−61
@@ -47,34 +47,6 @@ optional = mapOpaleye $ Opaleye.QueryArr . go
4747-- This operation can be used to "undo" the effect of 'optional', which
4848-- operationally is like turning a @LEFT JOIN@ back into a full @JOIN@. You
4949-- can think of this as analogous to 'Data.Maybe.catMaybes'.
50−--
51−-- To see this in action, first consider the following 'optional' query:
52−--
53−-- >>> :{
54−-- select c $ do
55−-- author <- each authorSchema
56−-- maybeRel8 <- optional $
57−-- each projectSchema
58−-- >>= filter (\p -> projectAuthorId p ==. authorId author)
59−-- >>= filter (\p -> projectName p ==. "rel8")
60−-- return (authorName author, projectName <$> maybeRel8)
61−-- :}
62−-- [("Ollie",Just "rel8"),("Bryan O'Sullivan",Nothing),("Emily Pillmore",Nothing)]
63−--
64−-- Here @optional@ is acting as a @LEFT JOIN@. We can turn this into a proper
65−-- join by using @catMaybeTable@ to filter out rows where the join failed:
66−--
67−-- >>> :{
68−-- select c $ do
69−-- author <- each authorSchema
70−-- maybeRel8 <- optional $
71−-- each projectSchema
72−-- >>= filter (\p -> projectAuthorId p ==. authorId author)
73−-- >>= filter (\p -> projectName p ==. "rel8")
74−-- rel8 <- catMaybeTable maybeRel8
75−-- return (authorName author, projectName rel8)
76−-- :}
77−-- [("Ollie","rel8")]
7850catMaybeTable :: MaybeTable a -> Query a
7951catMaybeTable ma@(MaybeTable _ a) = do
8052 where_ $ isJustTable ma
@@ -87,15 +59,6 @@ catMaybeTable ma@(MaybeTable _ a) = do
8759--
8860-- This is similar to 'traverseMaybeTable', followed by a @join@ on the
8961-- resulting @MaybeTable@s.
90−--
91−-- >>> select c $ bindMaybeTable (optional . values . pure . not_) =<< optional (values [lit True])
92−-- [Just False]
93−--
94−-- >>> select c $ bindMaybeTable (\_ -> return (noTable :: MaybeTable (Expr Bool))) =<< optional (values [lit True])
95−-- [Nothing]
96−--
97−-- >>> select c $ bindMaybeTable (optional . values . pure . not_) =<< return (noTable :: MaybeTable (Expr Bool))
98−-- [Nothing]
9962bindMaybeTable :: Monad m
10063 => (a -> m (MaybeTable b)) -> MaybeTable a -> m (MaybeTable b)
10164bindMaybeTable query (MaybeTable input a) = do
@@ -107,30 +70,10 @@ bindMaybeTable query (MaybeTable input a) = do
10770-- to step through multiple @LEFT JOINs@.
10871--
10972-- Note that @traverseMaybeTable@ takes a @a -> Query b@ function, which means
110−-- you also have the ability to "expand" one row into multiple rows.
111−--
112−-- >>> :{
113−-- duplicate :: Expr Bool -> Query (Expr Bool)
114−-- duplicate x = unionAll (return x) (return x)
115−-- :}
116−--
117−-- >>> select c $ traverseMaybeTable duplicate =<< optional (values [lit True])
118−-- [Just True,Just True]
119−--
120−-- Note that if the @a -> Query b@ function returns no rows, then the resulting
121−-- query will also have no rows:
122−--
123−-- >>> select c $ traverseMaybeTable (limit 0 . pure) =<< optional (values [lit True])
124−-- []
125−--
126−-- However, regardless of the given @a -> Query b@ function, if the input is
127−-- @noTable@, you will always get exactly one @noTable@ back:
128−--
129−-- >>> select c $ traverseMaybeTable duplicate (noTable :: MaybeTable (Expr Bool))
130−-- [Nothing]
131−--
132−-- >>> select c $ traverseMaybeTable (limit 0 . pure) (noTable :: MaybeTable (Expr Bool))
133−-- [Nothing]
73+-- you also have the ability to "expand" one row into multiple rows. If the
74+-- @a -> Query b@ function returns no rows, then the resulting query will also
75+-- have no rows. However, regardless of the given @a -> Query b@ function, if
76+-- the input is @noTable@, you will always get exactly one @noTable@ back.
13477traverseMaybeTable :: (a -> Query b) -> MaybeTable a -> Query (MaybeTable b)
13578traverseMaybeTable query ma@(MaybeTable input _) = do
13679 MaybeTable output b <- optional (query =<< catMaybeTable ma)
src/Rel8/Query/Null.hs−15
@@ -17,21 +17,6 @@ import Rel8.Query.Filter ( where_ )
1717-- @null@s.
1818--
1919-- Corresponds to 'Data.Maybe.catMaybes'.
20−--
21−-- >>> select c $ pure (nullExpr :: Expr (Maybe Bool))
22−-- [Nothing]
23−--
24−-- >>> select c $ catNullable (nullExpr :: Expr (Maybe Bool))
25−-- []
26−--
27−-- >>> select c $ catNullable (lit (Just True))
28−-- [True]
29−--
30−-- Notice how in the last example a @Bool@ is returned (rather than @Maybe
31−-- Bool@):
32−--
33−-- >>> :t catNullable (lit (Just True))
34−-- catMaybe (lit (Just True)) :: Query (Expr Bool)
3520catNullable :: Expr (Maybe a) -> Query (Expr a)
3621catNullable a = do
3722 where_ $ isNonNull a
src/Rel8/Query/Set.hs−18
@@ -24,53 +24,35 @@ import Rel8.Table.Opaleye ( binaryspec )
2424
2525-- | Combine the results of two queries of the same type, collapsing
2626-- duplicates. @union a b@ is the same as the SQL statement @x UNION b@.
27−--
28−-- >>> select c $ values [lit True, lit True, lit False] `union` values [lit True]
29−-- [False,True]
3027union :: EqTable a => Query a -> Query a -> Query a
3128union = zipOpaleyeWith (Opaleye.unionExplicit binaryspec)
3229
3330
3431-- | Combine the results of two queries of the same type, retaining duplicates.
3532-- @unionAll a b@ is the same as the SQL statement @x UNION ALL b@.
36−--
37−-- >>> select c $ values [lit True, lit True, lit False] `unionAll` values [lit True]
38−-- [True,True,False,True]
3933unionAll :: Table Expr a => Query a -> Query a -> Query a
4034unionAll = zipOpaleyeWith (Opaleye.unionAllExplicit binaryspec)
4135
4236
4337-- | Find the intersection of two queries, collapsing duplicates. @intersect a
4438-- b@ is the same as the SQL statement @x INTERSECT b@.
45−--
46−-- >>> select c $ values [lit True, lit True, lit False] `intersect` values [lit True]
47−-- [True]
4839intersect :: EqTable a => Query a -> Query a -> Query a
4940intersect = zipOpaleyeWith (Opaleye.intersectExplicit binaryspec)
5041
5142
5243-- | Find the intersection of two queries, retaining duplicates. @intersectAll
5344-- a b@ is the same as the SQL statement @x INTERSECT ALL b@.
54−--
55−-- >>> select c $ values [lit True, lit True, lit False] `intersectAll` values [lit True, lit True]
56−-- [True,True]
5745intersectAll :: EqTable a => Query a -> Query a -> Query a
5846intersectAll = zipOpaleyeWith (Opaleye.intersectAllExplicit binaryspec)
5947
6048
6149-- | Find the difference of two queries, collapsing duplicates @except a b@ is
6250-- the same as the SQL statement @x INTERSECT b@.
63−--
64−-- >>> select c $ values [lit True, lit False, lit False] `except` values [lit True]
65−-- [False]
6651except :: EqTable a => Query a -> Query a -> Query a
6752except = zipOpaleyeWith (Opaleye.exceptExplicit binaryspec)
6853
6954
7055-- | Find the difference of two queries, retaining duplicates. @exceptAll a b@
7156-- is the same as the SQL statement @x EXCEPT ALL b@.
72−--
73−-- >>> select c $ values [lit True, lit False, lit False] `exceptAll` values [lit True]
74−-- [False,False]
7557exceptAll :: EqTable a => Query a -> Query a -> Query a
7658exceptAll = zipOpaleyeWith (Opaleye.exceptAllExplicit binaryspec)
src/Rel8/Query/Values.hs−6
@@ -23,11 +23,5 @@ import Rel8.Table.Opaleye ( valuesspec )
2323-- | Construct a query that returns the given input list of rows. This is like
2424-- folding a list of 'return' statements under 'union', but uses the SQL
2525-- @VALUES@ expression for efficiency.
26−--
27−-- Typically @values@ will be used with 'lit':
28−--
29−-- >>> mapM_ Data.Text.IO.putStrLn =<< select c (values [ lit "Hello", lit "World!" ])
30−-- Hello
31−-- World!
3226values :: (Table Expr a, Foldable f) => f a -> Query a
3327values = fromOpaleye . Opaleye.valuesExplicit valuesspec . toList
src/Rel8/Schema/Column.hs−26
@@ -80,32 +80,6 @@ type family UnwrapDefault a where
8080-- data types are single columns in queries. This type family has special
8181-- support when a query is executed, allowing you to use a single data type for
8282-- both query data and rows decoded to Haskell.
83−--
84−-- To understand why this type family is special, let's consider a simple
85−-- higher-kinded data type of Haskell packages:
86−--
87−-- >>> :{
88−-- data Package f = Package
89−-- { packageName :: Column f Text
90−-- , packageAuthor :: Column f Text
91−-- }
92−-- :}
93−--
94−-- In queries, @f@ will be some type of 'Expr', and @Column Expr a@ reduces to
95−-- just @Expr a@:
96−--
97−-- >>> :t packageName (undefined :: Package Expr)
98−-- packageName (undefined :: Package Expr) :: Expr Text
99−--
100−-- When we 'select' queries of this type, @f@ will be instantiated as
101−-- @Identity@, at which point all wrapping entire disappears:
102−--
103−-- >>> :t packageName (undefined :: Package Identity)
104−-- packageName (undefined :: Package Identity) :: Text
105−--
106−-- In @rel8@ we try hard to always know what @f@ is, which means holes should
107−-- mention precise types, rather than the @Column@ type family. You should only
108−-- need to be aware of the type family when defining your table types.
10983type Column :: K.Context -> Type -> Type
11084type Column context a =
11185 Field context (GetLabel a)
src/Rel8/Schema/Table.hs+1−14
@@ -15,20 +15,7 @@ import Prelude
1515-- the columns within this table.
1616--
1717-- For each selectable table in your database, you should provide a
18−-- @TableSchema@ in order to interact with the table via Rel8. For a table
19−-- storing a list of projects (as defined in the introduction):
20−--
21−-- >>> :{
22−-- projectSchema :: TableSchema (Project ColumnSchema)
23−-- projectSchema = TableSchema
24−-- { tableName = "project"
25−-- , tableSchema = Nothing -- Assumes that the 'project' table is reachable from your connection's search_path
26−-- , tableColumns = Project
27−-- { projectAuthorId = "author_id"
28−-- , projectName = "name"
29−-- }
30−-- }
31−-- :}
18+-- @TableSchema@ in order to interact with the table via Rel8.
3219data TableSchema names = TableSchema
3320 { name :: String
3421 -- ^ The name of the table.
src/Rel8/Statement/Delete.hs−18
@@ -56,24 +56,6 @@ data Delete a where
5656
5757
5858-- | Run a @DELETE@ statement.
59−--
60−-- >>> mapM_ print =<< select c (each projectSchema)
61−-- Project {projectAuthorId = 1, projectName = "rel8"}
62−-- Project {projectAuthorId = 2, projectName = "aeson"}
63−-- Project {projectAuthorId = 2, projectName = "text"}
64−--
65−-- >>> :{
66−-- delete c Delete
67−-- { from = projectSchema
68−-- , deleteWhere = \p -> projectName p ==. lit "rel8"
69−-- , returning = Projection projectName
70−-- }
71−-- :}
72−-- ["rel8"]
73−--
74−-- >>> mapM_ print =<< select c (each projectSchema)
75−-- Project {projectAuthorId = 2, projectName = "aeson"}
76−-- Project {projectAuthorId = 2, projectName = "text"}
7759delete :: Connection -> Delete a -> IO a
7860delete c Delete {from, deleteWhere, returning} =
7961 case returning of
src/Rel8/Statement/Insert.hs−10
@@ -39,16 +39,6 @@ import Data.Text.Encoding ( encodeUtf8 )
3939
4040
4141-- | Run an @INSERT@ statement
42−--
43−-- >>> :{
44−-- insert c Insert
45−-- { into = authorSchema
46−-- , rows = [ lit Author{ authorName = "Gabriel Gonzales", authorId = AuthorId 4, authorUrl = Just "https://haskellforall.com" } ]
47−-- , onConflict = Abort
48−-- , returning = NumberOfRowsAffected
49−-- }
50−-- :}
51−-- 1
5242insert :: Connection -> Insert a -> IO a
5343insert c Insert {into, rows, onConflict, returning} =
5444 case (rows, returning) of
src/Rel8/Statement/Update.hs−20
@@ -61,26 +61,6 @@ data Update a where
6161
6262
6363-- | Run an @UPDATE@ statement.
64−--
65−-- >>> mapM_ print =<< select c (each projectSchema)
66−-- Project {projectAuthorId = 1, projectName = "rel8"}
67−-- Project {projectAuthorId = 2, projectName = "aeson"}
68−-- Project {projectAuthorId = 2, projectName = "text"}
69−--
70−-- >>> :{
71−-- update c Update
72−-- { target = projectSchema
73−-- , set = \p -> p { projectName = "Rel8!" }
74−-- , updateWhere = \p -> projectName p ==. lit "rel8"
75−-- , returning = NumberOfRowsAffected
76−-- }
77−-- :}
78−-- 1
79−--
80−-- >>> mapM_ print =<< select c (each projectSchema)
81−-- Project {projectAuthorId = 2, projectName = "aeson"}
82−-- Project {projectAuthorId = 2, projectName = "text"}
83−-- Project {projectAuthorId = 1, projectName = "Rel8!"}
8464update :: Connection -> Update a -> IO a
8565update c Update {target, set, updateWhere, returning} =
8666 case returning of
src/Rel8/Table/Maybe.hs−3
@@ -212,9 +212,6 @@ justTable = MaybeTable (fromExpr mempty)
212212-- | Project a single expression out of a 'MaybeTable'. You can think of this
213213-- operator like the '$' operator, but it also has the ability to return
214214-- @null@.
215−--
216−-- >>> select c $ fmap (fst $?) (optional (values [lit (True, False)]))
217−-- [Just True]
218215($?) :: forall a b. Sql DBType b
219216 => (a -> Expr b) -> MaybeTable a -> Expr (Nullify b)
220217f $? ma@(MaybeTable _ a) = case nullabilization @b of
src/Rel8/Type.hs−12
@@ -79,18 +79,6 @@ import qualified Data.UUID as UUID
7979-- should only need to derive instances of this class for custom database
8080-- types, such as types defined in PostgreSQL extensions, or custom domain
8181-- types.
82−--
83−-- [ Creating @DBType@s using @newtype@ ]
84−--
85−-- Generalized newtype deriving can be used when you want use a @newtype@
86−-- around a database type for clarity and accuracy in your Haskell code. A
87−-- common example is to @newtype@ row id types:
88−--
89−-- >>> newtype UserId = UserId { toInt32 :: Int32 } deriving newtype (DBType)
90−--
91−-- You can now write queries using @UserId@ instead of @Int32@, which may help
92−-- avoid making bad joins. However, when SQL is generated, it will be as if you
93−-- just used integers (the type distinction does not impact query generation).
9482type DBType :: Type -> Constraint
9583class NotNull a => DBType a where
9684 typeInformation :: TypeInformation a
src/Rel8/Type/Eq.hs−30
@@ -49,36 +49,6 @@ import Data.UUID ( UUID )
4949-- | Database types that can be compared for equality in queries. If a type is
5050-- an instance of 'DBEq', it means we can compare expressions for equality
5151-- using the SQL @=@ operator.
52−--
53−-- [ @DBEq@ with @newtype@s ]
54−--
55−-- Like with 'Rel8.DBType', @DBEq@ plays well with generalized newtype
56−-- deriving. The example given for @DBType@ added a @UserId@ @newtype@, but
57−-- without a @DBEq@ instance won't actually be able to use that in joins or
58−-- where-clauses, because it lacks equality. We can add this by changing our
59−-- @newtype@ definition to:
60−--
61−-- >>> newtype UserId = UserId { toInt32 :: Int32 } deriving newtype (DBType, DBEq)
62−--
63−-- This will re-use the equality logic for @Int32@, which is to just use the
64−-- @=@ operator.
65−--
66−-- [ @DBEq@ with @DeriveAnyType@ ]
67−--
68−-- You can also use @DBEq@ with the @DeriveAnyType@ extension to easily add
69−-- equality to your type, assuming that @=@ is sufficient on @DBType@ encoded
70−-- values. Extending the example from 'Rel8.ReadShow''s 'Rel8.DBType' instance,
71−-- we could add equality to @Color@ by writing:
72−--
73−-- >>> :{
74−-- data Color = Red | Green | Blue | Purple | Gold
75−-- deriving (Generic, Show, Read, DBEq)
76−-- deriving DBType via ReadShow Color
77−-- :}
78−--
79−-- This means @Color@s will be treated as the literal strings @"Red"@,
80−-- @"Green"@, etc, in the database, and they can be compared for equality by
81−-- just using @=@.
8252type DBEq :: Type -> Constraint
8353class DBType a => DBEq a
8454
src/Rel8/Type/Information.hs−18
@@ -56,24 +56,6 @@ mapTypeInformation = parseTypeInformation . fmap pure
5656-- a given 'TypeInformation'. The parser is applied when deserializing rows
5757-- returned - the encoder assumes that the input data is already in the
5858-- appropriate form.
59−--
60−-- One example where this may be useful is with a database that stores data in
61−-- some legacy encoding:
62−--
63−-- >>> import Data.Text (Text)
64−--
65−-- >>> data Color = Red | Green | Blue
66−-- >>> :{
67−-- instance DBType Color where
68−-- typeInformation = parseTypeInformation parseLegacy toLegacy typeInformation
69−-- where
70−-- parseLegacy :: Text -> Either String Color
71−-- parseLegacy "red" = Right Red
72−-- parseLegacy "green" = Right Green
73−-- parseLegacy _ = Left "Unexpected Color"
74−-- toLegacy Red = "red"
75−-- toLegacy Green = "green"
76−-- :}
7759parseTypeInformation :: ()
7860 => (a -> Either String b) -> (b -> a)
7961 -> TypeInformation a -> TypeInformation b
src/Rel8/Type/JSONEncoded.hs−13
@@ -15,19 +15,6 @@ import Rel8.Type.Information ( parseTypeInformation )
1515-- | A deriving-via helper type for column types that store a Haskell value
1616-- using a JSON encoding described by @aeson@'s 'ToJSON' and 'FromJSON' type
1717-- classes.
18−--
19−-- The declaration:
20−--
21−-- >>> import Data.Aeson
22−--
23−-- >>> :{
24−-- data Pet = Pet { petName :: String, petAge :: Int }
25−-- deriving (Generic, ToJSON, FromJSON)
26−-- deriving PrimitiveType via JSONEncoded Pet
27−-- :}
28−--
29−-- will allow you to store @Pet@ values in a single SQL column (stored as
30−-- @json@ values).
3118newtype JSONEncoded a = JSONEncoded { fromJSONEncoded :: a }
3219
3320
src/Rel8/Type/ReadShow.hs−11
@@ -14,17 +14,6 @@ import qualified Data.Text as Text
1414
1515-- | A deriving-via helper type for column types that store a Haskell value
1616-- using a Haskell's 'Read' and 'Show' type classes.
17−--
18−-- The declaration:
19−--
20−-- >>> :{
21−-- data Color = Red | Green | Blue
22−-- deriving (Read, Show)
23−-- deriving PrimitiveType via ReadShow Color
24−-- :}
25−--
26−-- will allow you to store @Color@ values in a single SQL column (stored as
27−-- @text@).
2817newtype ReadShow a = ReadShow { fromReadShow :: a }
2918
3019