on 7a526b6 help sign in
rel8/src/Rel8/Table/Maybe.hs
247 lines 7.4 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, undefined )
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
36 ( Labelable, HLabelable, hlabeler, hunlabeler
37 )
38import Rel8.Schema.Context.Nullify
39 ( Nullifiable, ConstrainTag
40 , HNullifiable, HConstrainTag
41 , hencodeTag, hdecodeTag
42 , hnullifier, hunnullifier
43 )
44import Rel8.Schema.HTable ( HTable )
45import Rel8.Schema.HTable.Identity ( HIdentity(..) )
46import Rel8.Schema.HTable.Label ( hlabel, hunlabel )
47import Rel8.Schema.HTable.Maybe ( HMaybeTable(..) )
48import Rel8.Schema.HTable.Nullify ( hnullify, hunnullify )
49import Rel8.Schema.Name ( Name )
50import Rel8.Schema.Null ( Nullify, Nullity( Null, NotNull ), Sql, nullable )
51import Rel8.Table
52 ( Table, Columns, Context, fromColumns, toColumns
53 , reify, unreify
54 )
55import Rel8.Table.Alternative
56 ( AltTable, (<|>:)
57 , AlternativeTable, emptyTable
58 )
59import Rel8.Table.Bool ( bool )
60import Rel8.Table.Eq ( EqTable, eqTable )
61import Rel8.Table.Ord ( OrdTable, ordTable )
62import Rel8.Table.Recontextualize ( Recontextualize )
63import Rel8.Table.Tag ( Tag(..), fromExpr, fromName )
64import Rel8.Table.Undefined ( undefined )
65import Rel8.Type ( DBType )
66import Rel8.Type.Tag ( MaybeTag )
68-- semigroupoids
69import Data.Functor.Apply ( Apply, (<.>), liftF2 )
70import Data.Functor.Bind ( Bind, (>>-) )
73-- | @MaybeTable t@ is the table @t@, but as the result of an outer join. If
74-- the outer join fails to match any rows, this is essentialy @Nothing@, and if
75-- the outer join does match rows, this is like @Just@. Unfortunately, SQL
76-- makes it impossible to distinguish whether or not an outer join matched any
77-- rows based generally on the row contents - if you were to join a row
78-- entirely of nulls, you can't distinguish if you matched an all null row, or
79-- if the match failed. For this reason @MaybeTable@ contains an extra field -
80-- a "nullTag" - to track whether or not the outer join produced any rows.
81type MaybeTable :: Type -> Type
82data MaybeTable a = MaybeTable
83 { tag :: Tag "isJust" (Maybe MaybeTag)
84 , just :: a
85 }
86 deriving stock Functor
89instance Apply MaybeTable where
90 MaybeTable tag f <.> MaybeTable tag' a = MaybeTable (tag <> tag') (f a)
93-- | Has the same behavior as the @Applicative@ instance for @Maybe@. See also:
94-- 'Rel8.traverseMaybeTable'.
95instance Applicative MaybeTable where
96 (<*>) = (<.>)
97 pure = justTable
100instance Bind MaybeTable where
101 MaybeTable tag a >>- f = case f a of
102 MaybeTable tag' b -> MaybeTable (tag <> tag') b
105-- | Has the same behavior as the @Monad@ instance for @Maybe@. See also:
106-- 'Rel8.bindMaybeTable'.
107instance Monad MaybeTable where
108 (>>=) = (>>-)
111instance AltTable MaybeTable where
112 ma@(MaybeTable tag a) <|>: MaybeTable tag' b = MaybeTable
113 { tag = (tag <> tag')
114 { expr = boolExpr (expr tag) (expr tag') condition
115 }
116 , just = bool a b condition
117 }
118 where
119 condition = isNothingTable ma
122instance AlternativeTable MaybeTable where
123 emptyTable = nothingTable
126instance (Table Expr a, Semigroup a) => Semigroup (MaybeTable a) where
127 ma <> mb = maybeTable mb (\a -> maybeTable ma (justTable . (a <>)) mb) ma
130instance (Table Expr a, Semigroup a) => Monoid (MaybeTable a) where
131 mempty = nothingTable
134instance
135 ( Table context a
136 , Labelable context, Nullifiable context
137 , ConstrainTag context MaybeTag
138 ) => Table context (MaybeTable a)
139 where
140 type Columns (MaybeTable a) = HMaybeTable (Columns a)
141 type Context (MaybeTable a) = Context a
143 toColumns = toColumns1 toColumns
144 fromColumns = fromColumns1 fromColumns
145 reify = fmap fmap reify
146 unreify = fmap fmap unreify
149instance
150 ( Labelable from, Nullifiable from, ConstrainTag from MaybeTag
151 , Labelable to, Nullifiable to, ConstrainTag to MaybeTag
152 , Recontextualize from to a b
153 )
154 => Recontextualize from to (MaybeTable a) (MaybeTable b)
157instance EqTable a => EqTable (MaybeTable a) where
158 eqTable = toColumns1 id (justTable (eqTable @a))
161instance OrdTable a => OrdTable (MaybeTable a) where
162 ordTable = toColumns1 id (justTable (ordTable @a))
165-- | Check if a @MaybeTable@ is absent of any row. Like 'Data.Maybe.isNothing'.
166isNothingTable :: MaybeTable a -> Expr Bool
167isNothingTable (MaybeTable tag _) = isNull (expr tag)
170-- | Check if a @MaybeTable@ contains a row. Like 'Data.Maybe.isJust'.
171isJustTable :: MaybeTable a -> Expr Bool
172isJustTable (MaybeTable tag _) = isNonNull (expr tag)
175-- | Perform case analysis on a 'MaybeTable'. Like 'maybe'.
176maybeTable :: Table Expr b => b -> (a -> b) -> MaybeTable a -> b
177maybeTable b f ma@(MaybeTable _ a) = bool (f a) b (isNothingTable ma)
178{-# INLINABLE maybeTable #-}
181-- | The null table. Like 'Nothing'.
182nothingTable :: Table Expr a => MaybeTable a
183nothingTable = MaybeTable (fromExpr null) undefined
186-- | Lift any table into 'MaybeTable'. Like 'Just'. Note you can also use
187-- 'pure'.
188justTable :: a -> MaybeTable a
189justTable = MaybeTable (fromExpr mempty)
192-- | Project a single expression out of a 'MaybeTable'. You can think of this
193-- operator like the '$' operator, but it also has the ability to return
194-- @null@.
195($?) :: forall a b. Sql DBType b
196 => (a -> Expr b) -> MaybeTable a -> Expr (Nullify b)
197f $? ma@(MaybeTable _ a) = case nullable @b of
198 Null -> boolExpr (f a) null (isNothingTable ma)
199 NotNull -> boolExpr (nullify (f a)) null (isNothingTable ma)
200infixl 4 $?
203aggregateMaybeTable :: ()
204 => (a -> Aggregate b) -> MaybeTable a -> Aggregate (MaybeTable b)
205aggregateMaybeTable f MaybeTable {tag = tag@Tag {aggregator, expr}, just} =
206 liftF2 MaybeTable (tag <$ aggregate) (f just)
207 where
208 aggregate = unsafeMakeAggregate toPrimExpr fromPrimExpr aggregator expr
211nameMaybeTable :: Name (Maybe MaybeTag) -> a -> MaybeTable a
212nameMaybeTable = MaybeTable . fromName
215toColumns1 ::
216 ( HTable t
217 , HConstrainTag context MaybeTag
218 , HLabelable context
219 , HNullifiable context
220 )
221 => (a -> t context)
222 -> MaybeTable a
223 -> HMaybeTable t context
224toColumns1 f MaybeTable {tag, just} = HMaybeTable
225 { htag
226 , hjust = hlabel hlabeler $ hnullify (hnullifier tag isNonNull) $ f just
227 }
228 where
229 htag = HIdentity (hencodeTag tag)
232fromColumns1 ::
233 ( HTable t
234 , HConstrainTag context MaybeTag
235 , HLabelable context
236 , HNullifiable context
237 )
238 => (t context -> a)
239 -> HMaybeTable t context
240 -> MaybeTable a
241fromColumns1 f HMaybeTable {htag = HIdentity htag, hjust} = MaybeTable
242 { tag
243 , just = f $ runIdentity $
244 hunnullify (\a -> pure . hunnullifier a) (hunlabel hunlabeler hjust)
245 }
246 where
247 tag = hdecodeTag htag