on 4e90af3 help sign in
rel8/src/Rel8/Table/Maybe.hs
228 lines 7.3 kB history raw
1{-# language DataKinds #-}
2{-# language DeriveFunctor #-}
3{-# language DerivingStrategies #-}
4{-# language FlexibleContexts #-}
5{-# language FlexibleInstances #-}
6{-# language MultiParamTypeClasses #-}
7{-# language NamedFieldPuns #-}
8{-# language ScopedTypeVariables #-}
9{-# language StandaloneKindSignatures #-}
10{-# language TypeApplications #-}
11{-# language TypeFamilies #-}
12{-# language UndecidableInstances #-}
14module Rel8.Table.Maybe
15 ( MaybeTable(..)
16 , maybeTable, nothingTable, justTable
17 , isNothingTable, isJustTable
18 , ($?)
19 , aggregateMaybeTable
20 , nameMaybeTable
21 )
22where
24-- base
25import Data.Functor.Identity ( runIdentity )
26import Data.Kind ( Type )
27import Prelude hiding ( null, repeat, undefined, zipWith )
29-- rel8
30import Rel8.Aggregate ( Aggregate, unsafeMakeAggregate )
31import Rel8.Expr ( Expr )
32import Rel8.Expr.Bool ( boolExpr )
33import Rel8.Expr.Null ( isNull, isNonNull, null, nullify )
34import Rel8.Expr.Opaleye ( fromPrimExpr, toPrimExpr )
35import Rel8.Schema.Context.Label ( Labelable, labeler, unlabeler, hlabeler )
36import Rel8.Schema.Context.Nullify
37 ( Nullifiable, ConstrainTag
38 , hencodeTag, hdecodeTag
39 , hnullifier, hunnullifier
40 )
41import Rel8.Schema.HTable.Identity ( HIdentity(..) )
42import Rel8.Schema.HTable.Label ( HLabel, hlabel, hunlabel )
43import Rel8.Schema.HTable.Maybe ( HMaybeTable(..), HMaybeNullifiable )
44import Rel8.Schema.HTable.Nullify ( hnullify, hunnullify )
45import Rel8.Schema.Name ( Name )
46import Rel8.Schema.Null ( Nullify, Nullity( Null, NotNull ), Sql, nullable )
47import Rel8.Table ( Table, Columns, Context, fromColumns, toColumns )
48import Rel8.Table.Alternative
49 ( AltTable, (<|>:)
50 , AlternativeTable, emptyTable
51 )
52import Rel8.Table.Bool ( bool )
53import Rel8.Table.Eq ( EqTable, eqTable )
54import Rel8.Table.Lifted
55 ( Table1, Columns1, ConstrainHContext1, fromColumns1, toColumns1
56 )
57import Rel8.Table.Ord ( OrdTable, ordTable )
58import Rel8.Table.Recontextualize ( Recontextualize )
59import Rel8.Table.Tag ( Tag(..), fromExpr, fromName )
60import Rel8.Table.Undefined ( undefined )
61import Rel8.Type ( DBType )
62import Rel8.Type.Tag ( MaybeTag )
64-- semigroupoids
65import Data.Functor.Apply ( Apply, (<.>), liftF2 )
66import Data.Functor.Bind ( Bind, (>>-) )
69-- | @MaybeTable t@ is the table @t@, but as the result of an outer join. If
70-- the outer join fails to match any rows, this is essentialy @Nothing@, and if
71-- the outer join does match rows, this is like @Just@. Unfortunately, SQL
72-- makes it impossible to distinguish whether or not an outer join matched any
73-- rows based generally on the row contents - if you were to join a row
74-- entirely of nulls, you can't distinguish if you matched an all null row, or
75-- if the match failed. For this reason @MaybeTable@ contains an extra field -
76-- a "nullTag" - to track whether or not the outer join produced any rows.
77type MaybeTable :: Type -> Type
78data MaybeTable a = MaybeTable
79 { tag :: Tag "isJust" (Maybe MaybeTag)
80 , just :: a
81 }
82 deriving stock Functor
85instance Apply MaybeTable where
86 MaybeTable tag f <.> MaybeTable tag' a = MaybeTable (tag <> tag') (f a)
89-- | Has the same behavior as the @Applicative@ instance for @Maybe@. See also:
90-- 'Rel8.traverseMaybeTable'.
91instance Applicative MaybeTable where
92 (<*>) = (<.>)
93 pure = justTable
96instance Bind MaybeTable where
97 MaybeTable tag a >>- f = case f a of
98 MaybeTable tag' b -> MaybeTable (tag <> tag') b
101-- | Has the same behavior as the @Monad@ instance for @Maybe@. See also:
102-- 'Rel8.bindMaybeTable'.
103instance Monad MaybeTable where
104 (>>=) = (>>-)
107instance AltTable MaybeTable where
108 ma@(MaybeTable tag a) <|>: MaybeTable tag' b = MaybeTable
109 { tag = (tag <> tag')
110 { expr = boolExpr (expr tag) (expr tag') condition
111 }
112 , just = bool a b condition
113 }
114 where
115 condition = isNothingTable ma
118instance AlternativeTable MaybeTable where
119 emptyTable = nothingTable
122instance (Table Expr a, Semigroup a) => Semigroup (MaybeTable a) where
123 ma <> mb = maybeTable mb (\a -> maybeTable ma (justTable . (a <>)) mb) ma
126instance (Table Expr a, Semigroup a) => Monoid (MaybeTable a) where
127 mempty = nothingTable
130instance Table1 MaybeTable where
131 type Columns1 MaybeTable = HMaybeTable
132 type ConstrainHContext1 MaybeTable = HMaybeNullifiable
134 toColumns1 f MaybeTable {tag, just} = HMaybeTable
135 { htag
136 , hjust = hnullify (hnullifier tag isNonNull) $ f just
137 }
138 where
139 htag = HIdentity (hencodeTag tag)
141 fromColumns1 f HMaybeTable {htag = HIdentity htag, hjust} = MaybeTable
142 { tag
143 , just = f $ runIdentity $
144 hunnullify (\a -> pure . hunnullifier a) hjust
145 }
146 where
147 tag = hdecodeTag htag
149 {-# INLINABLE fromColumns1 #-}
150 {-# INLINABLE toColumns1 #-}
153instance
154 ( Table context a
155 , Labelable context, Nullifiable context
156 , ConstrainTag context MaybeTag
157 ) => Table context (MaybeTable a)
158 where
159 type Columns (MaybeTable a) = HMaybeTable (HLabel "Just" (Columns a))
160 type Context (MaybeTable a) = Context a
162 toColumns = toColumns1 (hlabel labeler . toColumns)
163 fromColumns = fromColumns1 (fromColumns . hunlabel unlabeler)
166instance
167 ( Labelable from, Nullifiable from, ConstrainTag from MaybeTag
168 , Labelable to, Nullifiable to, ConstrainTag to MaybeTag
169 , Recontextualize from to a b
170 ) => Recontextualize from to (MaybeTable a) (MaybeTable b)
173instance EqTable a => EqTable (MaybeTable a) where
174 eqTable = toColumns1 (hlabel hlabeler) (justTable (eqTable @a))
177instance OrdTable a => OrdTable (MaybeTable a) where
178 ordTable = toColumns1 (hlabel hlabeler) (justTable (ordTable @a))
181-- | Check if a @MaybeTable@ is absent of any row. Like 'Data.Maybe.isNothing'.
182isNothingTable :: MaybeTable a -> Expr Bool
183isNothingTable (MaybeTable tag _) = isNull (expr tag)
186-- | Check if a @MaybeTable@ contains a row. Like 'Data.Maybe.isJust'.
187isJustTable :: MaybeTable a -> Expr Bool
188isJustTable (MaybeTable tag _) = isNonNull (expr tag)
191-- | Perform case analysis on a 'MaybeTable'. Like 'maybe'.
192maybeTable :: Table Expr b => b -> (a -> b) -> MaybeTable a -> b
193maybeTable b f ma@(MaybeTable _ a) = bool (f a) b (isNothingTable ma)
194{-# INLINABLE maybeTable #-}
197-- | The null table. Like 'Nothing'.
198nothingTable :: Table Expr a => MaybeTable a
199nothingTable = MaybeTable (fromExpr null) undefined
202-- | Lift any table into 'MaybeTable'. Like 'Just'. Note you can also use
203-- 'pure'.
204justTable :: a -> MaybeTable a
205justTable = MaybeTable (fromExpr mempty)
208-- | Project a single expression out of a 'MaybeTable'. You can think of this
209-- operator like the '$' operator, but it also has the ability to return
210-- @null@.
211($?) :: forall a b. Sql DBType b
212 => (a -> Expr b) -> MaybeTable a -> Expr (Nullify b)
213f $? ma@(MaybeTable _ a) = case nullable @b of
214 Null -> boolExpr (f a) null (isNothingTable ma)
215 NotNull -> boolExpr (nullify (f a)) null (isNothingTable ma)
216infixl 4 $?
219aggregateMaybeTable :: ()
220 => (a -> Aggregate b) -> MaybeTable a -> Aggregate (MaybeTable b)
221aggregateMaybeTable f MaybeTable {tag = tag@Tag {aggregator, expr}, just} =
222 liftF2 MaybeTable (tag <$ aggregate) (f just)
223 where
224 aggregate = unsafeMakeAggregate toPrimExpr fromPrimExpr aggregator expr
227nameMaybeTable :: Name (Maybe MaybeTag) -> a -> MaybeTable a
228nameMaybeTable = MaybeTable . fromName