Skip to content

Commit 701406b

Browse files
Add new Case constraint which is more general than Table Expr
The `Case` constraint is used for functions like `bool`, `case_`, `maybeTable`, `nullable`, all of which ultimately compile down to a PostgreSQL `CASE` statement. `Case` has two instances: an overlapping `Table Expr a => Case a` instance, and a `Case b => Case (a -> b)` instance, that allows expressions like `maybeTable id (+)` which would not have been possible for.
1 parent 5eb5689 commit 701406b

File tree

12 files changed

+71
-50
lines changed

12 files changed

+71
-50
lines changed

rel8.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ library
162162
Rel8.Table.Aggregate
163163
Rel8.Table.Alternative
164164
Rel8.Table.Bool
165+
Rel8.Table.Case
165166
Rel8.Table.Cols
166167
Rel8.Table.Either
167168
Rel8.Table.Eq

src/Rel8.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ module Rel8
5050
, EqTable(..), (==:), (/=:)
5151
, OrdTable(..), (<:), (<=:), (>:), (>=:), ascTable, descTable, greatest, least
5252
, lit
53+
, Case
5354
, bool
5455
, case_
5556
, castTable
@@ -409,6 +410,7 @@ import Rel8.Table.ADT
409410
import Rel8.Table.Aggregate
410411
import Rel8.Table.Alternative
411412
import Rel8.Table.Bool
413+
import Rel8.Table.Case
412414
import Rel8.Table.Either
413415
import Rel8.Table.Eq
414416
import Rel8.Table.HKD

src/Rel8/Generic/Construction.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ import Rel8.Table
6666
( TTable, TColumns
6767
, Table, fromColumns, toColumns
6868
)
69-
import Rel8.Table.Bool ( case_ )
69+
import Rel8.Table.Case ( case_ )
7070
import Rel8.Type.Tag ( Tag )
7171

7272

src/Rel8/Query/Evaluate.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import Rel8.Expr.Opaleye ( fromPrimExpr )
2323
import Rel8.Query ( Query( Query ) )
2424
import Rel8.Query.Rebind ( rebind )
2525
import Rel8.Table ( Table )
26-
import Rel8.Table.Bool ( case_ )
26+
import Rel8.Table.Case ( case_ )
2727
import Rel8.Table.Undefined ( undefined )
2828

2929

src/Rel8/Table/Bool.hs

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
{-# language FlexibleContexts #-}
2-
{-# language TypeFamilies #-}
3-
{-# language ViewPatterns #-}
1+
{-# language MonoLocalBinds #-}
42

53
module Rel8.Table.Bool
64
( bool
7-
, case_
85
, nullable
96
)
107
where
@@ -14,35 +11,18 @@ import Prelude
1411

1512
-- rel8
1613
import Rel8.Expr ( Expr )
17-
import Rel8.Expr.Bool ( boolExpr, caseExpr )
1814
import Rel8.Expr.Null ( isNull, unsafeUnnullify )
19-
import Rel8.Schema.HTable ( htabulate, hfield )
20-
import Rel8.Table ( Table, fromColumns, toColumns )
15+
import Rel8.Table.Case (Case, case_)
2116

2217

2318
-- | An if-then-else expression on tables.
2419
--
2520
-- @bool x y p@ returns @x@ if @p@ is @False@, and returns @y@ if @p@ is
2621
-- @True@.
27-
bool :: Table Expr a => a -> a -> Expr Bool -> a
28-
bool (toColumns -> false) (toColumns -> true) condition =
29-
fromColumns $ htabulate $ \field ->
30-
case (hfield false field, hfield true field) of
31-
(falseExpr, trueExpr) -> boolExpr falseExpr trueExpr condition
32-
{-# INLINABLE bool #-}
33-
34-
35-
-- | Produce a table expression from a list of alternatives. Returns the first
36-
-- table where the @Expr Bool@ expression is @True@. If no alternatives are
37-
-- true, the given default is returned.
38-
case_ :: Table Expr a => [(Expr Bool, a)] -> a -> a
39-
case_ (map (fmap toColumns) -> branches) (toColumns -> fallback) =
40-
fromColumns $ htabulate $ \field -> case hfield fallback field of
41-
fallbackExpr ->
42-
case map (fmap (`hfield` field)) branches of
43-
branchExprs -> caseExpr branchExprs fallbackExpr
22+
bool :: Case a => a -> a -> Expr Bool -> a
23+
bool ifFalse ifTrue condition = case_ [(condition, ifTrue)] ifFalse
4424

4525

4626
-- | Like 'maybe', but to eliminate @null@.
47-
nullable :: Table Expr b => b -> (Expr a -> b) -> Expr (Maybe a) -> b
27+
nullable :: Case b => b -> (Expr a -> b) -> Expr (Maybe a) -> b
4828
nullable b f ma = bool (f (unsafeUnnullify ma)) b (isNull ma)

src/Rel8/Table/Case.hs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{-# language FlexibleContexts #-}
2+
{-# language FlexibleInstances #-}
3+
{-# language NamedFieldPuns #-}
4+
{-# language TypeFamilies #-}
5+
{-# language UndecidableInstances #-}
6+
{-# language ViewPatterns #-}
7+
8+
module Rel8.Table.Case
9+
( Case
10+
, case_
11+
, undefined
12+
)
13+
where
14+
15+
-- base
16+
import Prelude hiding ( undefined )
17+
18+
-- rel8
19+
import Rel8.Expr ( Expr )
20+
import Rel8.Expr.Bool ( caseExpr )
21+
import Rel8.Expr.Null ( snull, unsafeUnnullify )
22+
import Rel8.Schema.HTable ( hfield, htabulate, hspecs )
23+
import Rel8.Schema.Null ( Nullity( Null, NotNull ) )
24+
import Rel8.Schema.Spec ( Spec(..) )
25+
import Rel8.Table ( Table, fromColumns, toColumns )
26+
27+
28+
class Case a where
29+
-- | Produce a table expression from a list of alternatives. Returns the
30+
-- first table where the @Expr Bool@ expression is @True@. If no
31+
-- alternatives are true, the given default is returned.
32+
case_ :: [(Expr Bool, a)] -> a -> a
33+
34+
undefined :: a
35+
36+
37+
instance {-# INCOHERENT #-} Table Expr a => Case a where
38+
case_ (map (fmap toColumns) -> branches) (toColumns -> fallback) =
39+
fromColumns $ htabulate $ \field -> case hfield fallback field of
40+
fallbackExpr ->
41+
case map (fmap (`hfield` field)) branches of
42+
branchExprs -> caseExpr branchExprs fallbackExpr
43+
undefined = fromColumns $ htabulate $ \field -> case hfield hspecs field of
44+
Spec {nullity, info} -> case nullity of
45+
Null -> snull info
46+
NotNull -> unsafeUnnullify (snull info)
47+
48+
49+
instance Case b => Case (a -> b) where
50+
case_ branches fallback a = case_ (map (fmap ($ a)) branches) (fallback a)
51+
undefined = const undefined

src/Rel8/Table/Either.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import Rel8.Table
5151
, Transpose
5252
)
5353
import Rel8.Table.Bool ( bool )
54+
import Rel8.Table.Case ( Case )
5455
import Rel8.Table.Eq ( EqTable, eqTable )
5556
import Rel8.Table.Nullify ( Nullify, aggregateNullify, guard )
5657
import Rel8.Table.Ord ( OrdTable, ordTable )
@@ -198,7 +199,7 @@ isRightTable EitherTable {tag} = isRight tag
198199

199200
-- | Pattern match/eliminate an 'EitherTable', by providing mappings from a
200201
-- 'leftTable' and 'rightTable'.
201-
eitherTable :: Table Expr c
202+
eitherTable :: Case c
202203
=> (a -> c) -> (b -> c) -> EitherTable Expr a b -> c
203204
eitherTable f g EitherTable {tag, left, right} =
204205
bool (f (extract left)) (g (extract right)) (isRight tag)

src/Rel8/Table/Maybe.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import Rel8.Table.Alternative
5757
, AlternativeTable, emptyTable
5858
)
5959
import Rel8.Table.Bool ( bool )
60+
import Rel8.Table.Case ( Case )
6061
import Rel8.Table.Eq ( EqTable, eqTable )
6162
import Rel8.Table.Ord ( OrdTable, ordTable )
6263
import Rel8.Table.Projection ( Projectable, project )
@@ -191,7 +192,7 @@ isJustTable (MaybeTable tag _) = isNonNull tag
191192

192193

193194
-- | Perform case analysis on a 'MaybeTable'. Like 'maybe'.
194-
maybeTable :: Table Expr b => b -> (a -> b) -> MaybeTable Expr a -> b
195+
maybeTable :: Case b => b -> (a -> b) -> MaybeTable Expr a -> b
195196
maybeTable b f ma@(MaybeTable _ a) = bool (f (extract a)) b (isNothingTable ma)
196197
{-# INLINABLE maybeTable #-}
197198

src/Rel8/Table/Null.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import Rel8.Table.Alternative
4040
, AlternativeTable, emptyTable
4141
)
4242
import Rel8.Table.Bool ( bool )
43+
import Rel8.Table.Case ( Case )
4344
import Rel8.Table.Eq ( EqTable, eqTable )
4445
import Rel8.Table.Maybe ( MaybeTable, justTable, maybeTable, nothingTable )
4546
import Rel8.Table.Nullify ( Nullify, isNull )
@@ -110,7 +111,7 @@ isNonNullTable = not_ . isNullTable
110111

111112

112113
-- | Like 'Rel8.nullable'.
113-
nullableTable :: (Table Expr a, Table Expr b)
114+
nullableTable :: (Table Expr a, Case b)
114115
=> b -> (a -> b) -> NullTable Expr a -> b
115116
nullableTable b f ma@(NullTable a) = bool (f (extract a)) b (isNullTable ma)
116117

src/Rel8/Table/Nullify.hs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,7 @@ instance (Table context a, Reifiable context, context ~ context') =>
161161
fromResult = fmap (fromResult @_ @a) . hunnullify R.unnullifier
162162

163163
toResult =
164-
maybe (hnulls (const R.null)) (hnullify R.nullifier) .
165-
fmap (toResult @_ @a)
164+
maybe (hnulls (const R.null)) (hnullify R.nullifier . toResult @_ @a)
166165

167166

168167
instance (EqTable a, context ~ Expr) => EqTable (Nullify context a) where

src/Rel8/Table/These.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import Rel8.Table
5151
, FromExprs, fromResult, toResult
5252
, Transpose
5353
)
54+
import Rel8.Table.Case ( Case )
5455
import Rel8.Table.Eq ( EqTable, eqTable )
5556
import Rel8.Table.Maybe
5657
( MaybeTable(..)
@@ -315,7 +316,7 @@ thoseTable a b = TheseTable (justTable a) (justTable b)
315316

316317

317318
-- | Pattern match on a 'TheseTable'. Corresponds to 'these'.
318-
theseTable :: Table Expr c
319+
theseTable :: Case c
319320
=> (a -> c) -> (b -> c) -> (a -> b -> c) -> TheseTable Expr a b -> c
320321
theseTable f g h TheseTable {here, there} =
321322
maybeTable

src/Rel8/Table/Undefined.hs

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
{-# language FlexibleContexts #-}
2-
{-# language NamedFieldPuns #-}
3-
{-# language TypeFamilies #-}
4-
51
module Rel8.Table.Undefined
62
( undefined
73
)
@@ -11,16 +7,4 @@ where
117
import Prelude hiding ( undefined )
128

139
-- rel8
14-
import Rel8.Expr ( Expr )
15-
import Rel8.Expr.Null ( snull, unsafeUnnullify )
16-
import Rel8.Schema.HTable ( htabulate, hfield, hspecs )
17-
import Rel8.Schema.Null ( Nullity( Null, NotNull ) )
18-
import Rel8.Schema.Spec ( Spec(..) )
19-
import Rel8.Table ( Table, fromColumns )
20-
21-
22-
undefined :: Table Expr a => a
23-
undefined = fromColumns $ htabulate $ \field -> case hfield hspecs field of
24-
Spec {nullity, info} -> case nullity of
25-
Null -> snull info
26-
NotNull -> unsafeUnnullify (snull info)
10+
import Rel8.Table.Case ( undefined )

0 commit comments

Comments
 (0)