on master help sign in
Lots more docs
commit6e001f88f23924eac5e0aed14edb0e1190b2840e authorOliver Charles <ollie@ocharles.org.uk> dateFri 2 Apr 2021, 21:27 (6 years ago) parent1ab51d8
17 files changed+230−11
src/Rel8/Expr/Aggregate.hs+3 src/Rel8/Expr/Bool.hs+4 src/Rel8/Expr/Num.hs+20 src/Rel8/Expr/Opaleye.hs+2 src/Rel8/Expr/Serialize.hs+4 src/Rel8/Expr/Text.hs+45−10 src/Rel8/Query/Aggregate.hs+3 src/Rel8/Query/Exists.hs+7−1 src/Rel8/Query/Maybe.hs+5 src/Rel8/Schema/Nullability/Internal.hs+5 src/Rel8/Statement/View.hs+3 src/Rel8/Table/Bool.hs+7 src/Rel8/Table/Eq.hs+3 src/Rel8/Table/Serialize.hs+2 src/Rel8/Tabulate.hs+113 src/Rel8/Type/Array1D.hs+1 src/Rel8/Type/Num.hs+3
src/Rel8/Expr/Aggregate.hs+3
@@ -130,6 +130,7 @@ sum = unsafeMakeAggregate toPrimExpr (castExpr . fromPrimExpr) $
130130 }
131131
132132
133+-- | Take the sum of all expressions that satisfy a predicate.
133134sumWhere :: (Sql DBNum a, Sql DBSum a)
134135 => Expr Bool -> Expr a -> Aggregate (Expr a)
135136sumWhere condition a = sum (caseExpr [(condition, a)] 0)
@@ -152,6 +153,7 @@ groupByExpr :: Sql DBEq a => Expr a -> Aggregate (Expr a)
152153groupByExpr = unsafeMakeAggregate toPrimExpr fromPrimExpr Nothing
153154
154155
156+-- | Collect expressions values as a list.
155157listAggExpr :: Expr a -> Aggregate (Expr [a])
156158listAggExpr = unsafeMakeAggregate toPrimExpr from $ Just
157159 Aggregator
@@ -163,6 +165,7 @@ listAggExpr = unsafeMakeAggregate toPrimExpr from $ Just
163165 from = fromPrimExpr . fromPrimArray
164166
165167
168+-- | Collect expressions values as a non-empty list.
166169nonEmptyAggExpr :: Expr a -> Aggregate (Expr (NonEmpty a))
167170nonEmptyAggExpr = unsafeMakeAggregate toPrimExpr from $ Just
168171 Aggregator
src/Rel8/Expr/Bool.hs+4
@@ -78,6 +78,10 @@ caseExpr branches (Expr fallback) =
7878 go (Expr condition, Expr value) = (condition, value)
7979
8080
81+-- | Convert a @Expr (Maybe Bool)@ to a @Expr Bool@ by treating @Nothing@ as
82+-- @False@. This can be useful when combined with 'where_', which expects a
83+-- @Bool@, and produces expressions that optimize better than general case
84+-- analysis.
8185coalesce :: Expr (Maybe Bool) -> Expr Bool
8286coalesce (Expr a) = Expr a &&. Expr (Opaleye.FunExpr "COALESCE" [a, untrue])
8387 where
src/Rel8/Expr/Num.hs+20
@@ -19,39 +19,59 @@ import Rel8.Schema.Nullability ( Homonullable, Sql )
1919import Rel8.Type.Num ( DBFractional, DBIntegral, DBNum )
2020
2121
22+-- | Cast 'DBIntegral' types to 'DBNum' types. For example, this can be useful
23+-- if you need to turn an @Expr Int32@ into an @Expr Double@.
2224fromIntegral :: (Sql DBIntegral a, Sql DBNum b, Homonullable a b)
2325 => Expr a -> Expr b
2426fromIntegral (Expr a) = castExpr (Expr a)
2527
2628
29+-- | Cast 'DBNum' types to 'DBFractional' types. For example, his can be useful
30+-- to convert @Expr Float@ to @Expr Double@.
2731realToFrac :: (Sql DBNum a, Sql DBFractional b, Homonullable a b)
2832 => Expr a -> Expr b
2933realToFrac (Expr a) = castExpr (Expr a)
3034
3135
36+-- | Round a 'DBFractional' to a 'DBIntegral' by rounding to the nearest larger
37+-- integer.
38+--
39+-- Corresponds to the @ceiling()@ function.
3240ceiling :: (Sql DBFractional a, Sql DBIntegral b, Homonullable a b)
3341 => Expr a -> Expr b
3442ceiling = function "CEILING"
3543
3644
45+-- | Perform integral division. Corresponds to the @div()@ function.
3746div :: Sql DBIntegral a => Expr a -> Expr a -> Expr a
3847div = function "DIV"
3948
4049
50+-- | Corresponds to the @mod()@ function.
4151mod :: Sql DBIntegral a => Expr a -> Expr a -> Expr a
4252mod = function "MOD"
4353
4454
55+-- | Round a 'DFractional' to a 'DBIntegral' by rounding to the nearest smaller
56+-- integer.
57+--
58+-- Corresponds to the @floor()@ function.
4559floor :: (Sql DBFractional a, Sql DBIntegral b, Homonullable a b)
4660 => Expr a -> Expr b
4761floor = function "FLOOR"
4862
4963
64+-- | Round a 'DBFractional' to a 'DBIntegral' by rounding to the nearest
65+-- integer.
66+--
67+-- Corresponds to the @round()@ function.
5068round :: (Sql DBFractional a, Sql DBIntegral b, Homonullable a b)
5169 => Expr a -> Expr b
5270round = function "ROUND"
5371
5472
73+-- | Round a 'DBFractional' to a 'DBIntegral' by rounding to the nearest
74+-- integer towards zero.
5575truncate :: (Sql DBFractional a, Sql DBIntegral b, Homonullable a b)
5676 => Expr a -> Expr b
5777truncate = function "TRUNC"
src/Rel8/Expr/Opaleye.hs+2
@@ -34,6 +34,8 @@ castExpr :: Sql DBType a => Expr a -> Expr a
3434castExpr = scastExpr typeInformation
3535
3636
37+-- | Cast an expression to a different type. Corresponds to a @CAST()@ function
38+-- call.
3739unsafeCastExpr :: Sql DBType b => Expr a -> Expr b
3840unsafeCastExpr = sunsafeCastExpr typeInformation
3941
src/Rel8/Expr/Serialize.hs+4
@@ -30,6 +30,10 @@ import Rel8.Type ( DBType, typeInformation )
3030import Rel8.Type.Information ( TypeInformation(..) )
3131
3232
33+-- | Produce an expression from a literal.
34+--
35+-- Note that you can usually use 'lit', but @litExpr@ can solve problems of
36+-- inference in polymorphic code.
3337litExpr :: Sql DBType a => a -> Expr a
3438litExpr = slitExpr nullabilization typeInformation
3539
src/Rel8/Expr/Text.hs+45−10
@@ -16,7 +16,7 @@ module Rel8.Expr.Text
1616 , initcap, left, length, lengthEncoding, lpad, ltrim, md5
1717 , pgClientEncoding, quoteIdent, quoteLiteral, quoteNullable, regexpReplace
1818 , regexpSplitToArray, repeat, replace, reverse, right, rpad, rtrim
19− , splitPart, strpos, substr, toAscii, toHex, translate
19+ , splitPart, strpos, substr, translate
2020 )
2121where
2222
@@ -50,24 +50,32 @@ infixr 6 ++.
5050
5151
5252-- | Matches regular expression, case sensitive
53+--
54+-- Corresponds to the @~.@ operator.
5355(~.) :: Expr Text -> Expr Text -> Expr Bool
5456(~.) = binaryOperator "~."
5557infix 2 ~.
5658
5759
5860-- | Matches regular expression, case insensitive
61+--
62+-- Corresponds to the @~*@ operator.
5963(~*) :: Expr Text -> Expr Text -> Expr Bool
6064(~*) = binaryOperator "~*"
6165infix 2 ~*
6266
6367
6468-- | Does not match regular expression, case sensitive
69+--
70+-- Corresponds to the @!~@ operator.
6571(!~) :: Expr Text -> Expr Text -> Expr Bool
6672(!~) = binaryOperator "!~"
6773infix 2 !~
6874
6975
7076-- | Does not match regular expression, case insensitive
77+--
78+-- Corresponds to the @!~*@ operator.
7179(!~*) :: Expr Text -> Expr Text -> Expr Bool
7280(!~*) = binaryOperator "!~*"
7381infix 2 !~*
@@ -78,164 +86,191 @@ infix 2 !~*
7886-- * Standard SQL functions
7987
8088
89+-- | Corresponds to the @bit_length@ function.
8190bitLength :: Expr Text -> Expr Int32
8291bitLength = function "bit_length"
8392
8493
94+-- | Corresponds to the @char_length@ function.
8595charLength :: Expr Text -> Expr Int32
8696charLength = function "char_length"
8797
8898
99+-- | Corresponds to the @lower@ function.
89100lower :: Expr Text -> Expr Text
90101lower = function "lower"
91102
92103
104+-- | Corresponds to the @octet_length@ function.
93105octetLength :: Expr Text -> Expr Int32
94106octetLength = function "octet_length"
95107
96108
109+-- | Corresponds to the @upper@ function.
97110upper :: Expr Text -> Expr Text
98111upper = function "upper"
99112
100113
101−-- * PostgreSQL functions
114+-- | Corresponds to the @ascii@ function.
102115ascii :: Expr Text -> Expr Int32
103116ascii = function "ascii"
104117
105118
119+-- | Corresponds to the @btrim@ function.
106120btrim :: Expr Text -> Maybe (Expr Text) -> Expr Text
107121btrim a (Just b) = function "btrim" a b
108122btrim a Nothing = function "btrim" a
109123
110124
125+-- | Corresponds to the @chr@ function.
111126chr :: Expr Int32 -> Expr Text
112127chr = function "chr"
113128
114129
130+-- | Corresponds to the @convert@ function.
115131convert :: Expr ByteString -> Expr Text -> Expr Text -> Expr ByteString
116132convert = function "convert"
117133
118134
135+-- | Corresponds to the @convert_from@ function.
119136convertFrom :: Expr ByteString -> Expr Text -> Expr Text
120137convertFrom = function "convert_from"
121138
122139
140+-- | Corresponds to the @convert_to@ function.
123141convertTo :: Expr Text -> Expr Text -> Expr ByteString
124142convertTo = function "convert_to"
125143
126144
145+-- | Corresponds to the @decode@ function.
127146decode :: Expr Text -> Expr Text -> Expr ByteString
128147decode = function "decode"
129148
130149
150+-- | Corresponds to the @encode@ function.
131151encode :: Expr ByteString -> Expr Text -> Expr Text
132152encode = function "encode"
133153
134154
155+-- | Corresponds to the @initcap@ function.
135156initcap :: Expr Text -> Expr Text
136157initcap = function "initcap"
137158
138159
160+-- | Corresponds to the @left@ function.
139161left :: Expr Text -> Expr Int32 -> Expr Text
140162left = function "left"
141163
142164
165+-- | Corresponds to the @length@ function.
143166length :: Expr Text -> Expr Int32
144167length = function "length"
145168
146169
170+-- | Corresponds to the @length@ function.
147171lengthEncoding :: Expr ByteString -> Expr Text -> Expr Int32
148172lengthEncoding = function "length"
149173
150174
175+-- | Corresponds to the @lpad@ function.
151176lpad :: Expr Text -> Expr Int32 -> Maybe (Expr Text) -> Expr Text
152177lpad a b (Just c) = function "lpad" a b c
153178lpad a b Nothing = function "lpad" a b
154179
155180
181+-- | Corresponds to the @ltrim@ function.
156182ltrim :: Expr Text -> Maybe (Expr Text) -> Expr Text
157183ltrim a (Just b) = function "ltrim" a b
158184ltrim a Nothing = function "ltrim" a
159185
160186
187+-- | Corresponds to the @md5@ function.
161188md5 :: Expr Text -> Expr Text
162189md5 = function "md5"
163190
164191
192+-- | Corresponds to the @pg_client_encoding()@ expression.
165193pgClientEncoding :: Expr Text
166194pgClientEncoding = nullaryFunction "pg_client_encoding"
167195
168196
197+-- | Corresponds to the @quote_ident@ function.
169198quoteIdent :: Expr Text -> Expr Text
170199quoteIdent = function "quote_ident"
171200
172201
202+-- | Corresponds to the @quote_literal@ function.
173203quoteLiteral :: Expr Text -> Expr Text
174204quoteLiteral = function "quote_literal"
175205
176206
207+-- | Corresponds to the @quote_nullable@ function.
177208quoteNullable :: Expr Text -> Expr Text
178209quoteNullable = function "quote_nullable"
179210
180211
212+-- | Corresponds to the @regexp_replace@ function.
181213regexpReplace :: ()
182214 => Expr Text -> Expr Text -> Expr Text -> Maybe (Expr Text) -> Expr Text
183215regexpReplace a b c (Just d) = function "regexp_replace" a b c d
184216regexpReplace a b c Nothing = function "regexp_replace" a b c
185217
186218
219+-- | Corresponds to the @regexp_split_to_array@ function.
187220regexpSplitToArray :: ()
188221 => Expr Text -> Expr Text -> Maybe (Expr Text) -> Expr (Array1D Text)
189222regexpSplitToArray a b (Just c) = function "regexp_split_to_array" a b c
190223regexpSplitToArray a b Nothing = function "regexp_split_to_array" a b
191224
192225
226+-- | Corresponds to the @repeat@ function.
193227repeat :: Expr Text -> Expr Int32 -> Expr Text
194228repeat = function "repeat"
195229
196230
231+-- | Corresponds to the @replace@ function.
197232replace :: Expr Text -> Expr Text -> Expr Text -> Expr Text
198233replace = function "replace"
199234
200235
236+-- | Corresponds to the @reverse@ function.
201237reverse :: Expr Text -> Expr Text
202238reverse = function "reverse"
203239
204240
241+-- | Corresponds to the @right@ function.
205242right :: Expr Text -> Expr Int32 -> Expr Text
206243right = function "right"
207244
208245
246+-- | Corresponds to the @rpad@ function.
209247rpad :: Expr Text -> Expr Int32 -> Maybe (Expr Text) -> Expr Text
210248rpad a b (Just c) = function "rpad" a b c
211249rpad a b Nothing = function "rpad" a b
212250
213251
252+-- | Corresponds to the @rtrim@ function.
214253rtrim :: Expr Text -> Maybe (Expr Text) -> Expr Text
215254rtrim a (Just b) = function "rtrim" a b
216255rtrim a Nothing = function "rtrim" a
217256
218257
258+-- | Corresponds to the @split_part@ function.
219259splitPart :: Expr Text -> Expr Text -> Expr Int32 -> Expr Text
220260splitPart = function "split_part"
221261
222262
263+-- | Corresponds to the @strpos@ function.
223264strpos :: Expr Text -> Expr Text -> Expr Int32
224265strpos = function "strpos"
225266
226267
268+-- | Corresponds to the @substr@ function.
227269substr :: Expr Text -> Expr Int32 -> Maybe (Expr Int32) -> Expr Text
228270substr a b (Just c) = function "substr" a b c
229271substr a b Nothing = function "substr" a b
230272
231273
232−toAscii :: Expr Text -> Expr Text -> Expr Text
233−toAscii = function "toAscii"
234−
235−
236−toHex :: Expr Int32 -> Expr Text
237−toHex = function "toHex"
238−
239−
274+-- | Corresponds to the @translate@ function.
240275translate :: Expr Text -> Expr Text -> Expr Text -> Expr Text
241276translate = function "translate"
src/Rel8/Query/Aggregate.hs+3
@@ -29,5 +29,8 @@ aggregate :: Query (Aggregate exprs) -> Query exprs
2929aggregate = mapOpaleye (Opaleye.aggregate aggregator)
3030
3131
32+-- | Count the number of rows returned by a query. Note that this is different
33+-- from @countStar@, as even if the given query yields no rows, @countRows@
34+-- will return @0@.
3235countRows :: Query a -> Query (Expr Int64)
3336countRows = fmap (maybeTable 0 id) . optional . aggregate . fmap (const countStar)
src/Rel8/Query/Exists.hs+7−1
@@ -46,17 +46,23 @@ whereNotExists :: Query a -> Query ()
4646whereNotExists = mapOpaleye Opaleye.restrictNotExists
4747
4848
49+-- | @with@ is similar to 'filter', but allows the predicate to be a full query.
50+--
51+-- @with f a = a <$ whereExists (f a)@, but this form matches 'filter'.
4952with :: (a -> Query b) -> a -> Query a
5053with f a = a <$ whereExists (f a)
5154
5255
53−withBy :: (a -> b -> Expr Bool) -> Query b -> a -> Query a
56+-- | Like @with@, but with a custom membership test.
57+withBy :: (a -> b -> Expr Bool) -> Query b -> a -> Query a
5458withBy predicate bs = with $ \a -> bs >>= filter (predicate a)
5559
5660
61+-- | Filter rows where @a -> Query b@ yields no rows.
5762without :: (a -> Query b) -> a -> Query a
5863without f a = a <$ whereNotExists (f a)
5964
6065
66+-- | Like @without@, but with a custom membership test.
6167withoutBy :: (a -> b -> Expr Bool) -> Query b -> a -> Query a
6268withoutBy predicate bs = without $ \a -> bs >>= filter (predicate a)
src/Rel8/Query/Maybe.hs+5
@@ -28,6 +28,11 @@ import Rel8.Table.Opaleye ( unpackspec )
2828import Rel8.Table.Tag ( Tag(..), fromExpr )
2929
3030
31+-- | Convert a query that might return zero rows to a query that always returns
32+-- at least one row.
33+--
34+-- To speak in more concrete terms, 'optional' is most useful to write @LEFT
35+-- JOIN@s.
3136optional :: Query a -> Query (MaybeTable a)
3237optional = mapOpaleye $ Opaleye.QueryArr . go
3338 where
src/Rel8/Schema/Nullability/Internal.hs+5
@@ -57,12 +57,15 @@ type Nullify :: Type -> Type
5757type Nullify a = Maybe (Unnullify a)
5858
5959
60+-- | @NotNull a@ means @a@ cannot take @null@ as a value.
6061type NotNull :: Type -> Constraint
6162class (HasNullability a, IsMaybe a ~ 'False) => NotNull a
6263instance (HasNullability a, IsMaybe a ~ 'False) => NotNull a
6364instance {-# OVERLAPPING #-} NotNull Opaque
6465
6566
67+-- | @Homonullable a b@ means that both @a@ and @b@ can be @null@, or neither
68+-- @a@ or @b@ can be @null@.
6669type Homonullable :: Type -> Type -> Constraint
6770class IsMaybe a ~ IsMaybe b => Homonullable a b
6871instance IsMaybe a ~ IsMaybe b => Homonullable a b
@@ -93,6 +96,8 @@ instance IsMaybe a ~ 'False => HasNullability' 'True (Maybe a) where
9396 nullabilization' = Nullable
9497
9598
99+-- | @HasNullability a@ means that @rel8@ is able to check if the type @a@ is a
100+-- type that can take @null@ values or not.
96101type HasNullability :: Type -> Constraint
97102class HasNullability' (IsMaybe a) a => HasNullability a
98103instance HasNullability' (IsMaybe a) a => HasNullability a
src/Rel8/Statement/View.hs+3
@@ -31,6 +31,9 @@ import qualified Data.Text as Text
3131import Data.Text.Encoding ( encodeUtf8 )
3232
3333
34+-- | Given a 'TableSchema' and 'Query', @createView@ runs a @CREATE VIEW@
35+-- statement that will save the given query as a view. This can be useful if
36+-- you want to share Rel8 queries with other applications.
3437createView :: Selects names exprs
3538 => TableSchema names -> Query exprs -> Connection -> IO ()
3639createView (TableSchema name mschema names) query =
src/Rel8/Table/Bool.hs+7
@@ -18,6 +18,10 @@ import Rel8.Schema.HTable ( htabulate, hfield )
1818import Rel8.Table ( Table, fromColumns, toColumns )
1919
2020
21+-- | An if-then-else expression on tables.
22+--
23+-- @bool x y p@ returns @x@ if @p@ is @False@, and returns @y@ if @p@ is
24+-- @True@.
2125bool :: Table Expr a => a -> a -> Expr Bool -> a
2226bool (toColumns -> false) (toColumns -> true) condition =
2327 fromColumns $ htabulate $ \field ->
@@ -27,6 +31,9 @@ bool (toColumns -> false) (toColumns -> true) condition =
2731{-# INLINABLE bool #-}
2832
2933
34+-- | Produce a table expression from a list of alternatives. Returns the first
35+-- table where the @Expr Bool@ expression is @True@. If no alternatives are
36+-- true, the given default is returned.
3037case_ :: Table Expr a => [(Expr Bool, a)] -> a -> a
3138case_ (map (fmap toColumns) -> branches) (toColumns -> fallback) =
3239 fromColumns $ htabulate $ \field -> case hfield fallback field of
src/Rel8/Table/Eq.hs+3
@@ -48,6 +48,9 @@ import Rel8.Type.Eq ( DBEq )
4848import Rel8.Schema.Nullability ( Sql )
4949
5050
51+-- | The class of 'Table's that can be compared for equality. Equality on
52+-- tables is defined by equality of all columns all columns, so this class
53+-- means "all columns in a 'Table' have an instance of 'DBEq'".
5154type EqTable :: Type -> Constraint
5255class Table Expr a => EqTable a where
5356 eqTable :: Columns a (Dict (ConstrainDBType DBEq))
src/Rel8/Table/Serialize.hs+2
@@ -279,6 +279,8 @@ instance (ToExprs a exprs, a ~ FromExprs exprs) => Serializable exprs a
279279instance {-# OVERLAPPING #-} Sql DBType a => Serializable (Expr a) a
280280
281281
282+-- | Use @lit@ to turn literal Haskell values into expressions. @lit@ is
283+-- capable of lifting single @Expr@s to full tables.
282284lit :: forall exprs a. Serializable exprs a => a -> exprs
283285lit = fromColumns . litHTable . toIdentity' @exprs
284286
src/Rel8/Tabulate.hs+113
@@ -60,6 +60,13 @@ import qualified Data.Functor.Bind
6060import Data.Semigroup.Traversable.Class ( bitraverse1 )
6161
6262
63+-- | @'Tabulation' k a@ is denotionally a @MultiMap k a@ — a @Map@ where each
64+-- key @k@ corresponds to potentially multiple @a@ (i.e., @'Query' a@). This
65+-- @MultiMap@ supports 'lookup' and other operations you would expect it to.
66+--
67+-- \"Identity\" 'Tabulation's are created using 'tabulate'. 'Tabulation's can
68+-- be composed with 'Query's with 'prebind' or 'postbind' to form new
69+-- 'Tabulation's.
6370newtype Tabulation k a = Tabulation (k -> Query (Maybe k, a))
6471 deriving stock Functor
6572
@@ -93,6 +100,16 @@ instance EqTable k => Monad (Tabulation k) where
93100 pure (mk', b)
94101
95102
103+-- | This "undoes" 'fromQuery'.
104+--
105+-- @
106+-- 'runTabulation' . 'fromQuery' = id
107+-- 'fromQuery' . 'runTabulation' = id
108+-- @
109+--
110+-- Note however that it produces nonsense when passed a 'Tabulation' that was
111+-- made with from 'liftQuery' or 'pure'. In such cases the returned 'Query'
112+-- will always produce zero rows.
96113runTabulation :: EqTable k => Tabulation k a -> Query (k, a)
97114runTabulation (Tabulation f) = do
98115 (mk, a) <- f i
@@ -111,14 +128,41 @@ liftQuery :: Query a -> Tabulation k a
111128liftQuery query = Tabulation $ const $ fmap (Nothing,) query
112129
113130
131+-- | 'tabulate' creates an \"identity\" @'Tabulation' k a@ that allows @a@ be
132+-- indexed by one or more of its columns @k@. Some examples:
133+--
134+-- [Tabulation by primary key]:
135+-- @
136+-- projectsById :: Project 'Expr' -> 'Tabulation' ('Expr' ProjectId) (Project 'Expr')
137+-- projectsById = 'tabulate' projectId
138+-- @
139+--
140+-- Note: the nature of primary keys means that each key will be mapped to a
141+-- singleton value in this case.
142+--
143+-- [Tabulation by other unique key]:
144+-- @
145+-- projectsByName :: Project 'Expr' -> 'Tabulation' ('Expr' Text) (Project 'Expr')
146+-- projectsByName = 'tabulate' projectName
147+-- @
148+--
149+-- [Tabulation by foreign key (tabulate a child table by parent key)]:
150+-- @
151+-- revisionsByProjectId :: Revision 'Expr' -> 'Tabulation' ('Expr' ProjectId) (Revision 'Expr')
152+-- revisionsByProjectId = 'tabulate' revisionProjectId
153+-- @
114154tabulate :: (a -> k) -> a -> Tabulation k a
115155tabulate key a = fromQuery $ pure (key a, a)
116156
117157
158+-- | Like 'tabulate' but takes a monadic 'Query' function instead of a pure
159+-- one. This means you can filter rows while calculating the key, which is
160+-- useful in conjunction with 'Rel8.Extra.catNulls'.
118161tabulateA :: (a -> Query k) -> a -> Tabulation k a
119162tabulateA key a = fromQuery $ (,a) <$> key a
120163
121164
165+-- | Analgous to 'Data.Map.Strict.fromList'.
122166fromQuery :: Query (k, a) -> Tabulation k a
123167fromQuery = Tabulation . const . fmap (first Just)
124168
@@ -133,6 +177,13 @@ ifilter f tabulation = snd <$> do
133177 filter (uncurry f) `postbind` indexed tabulation
134178
135179
180+-- | Map a 'Query' over the input side of a 'Tabulation'. In particular, you
181+-- can turn a @'Tabulation' k a@ into a @'Tabulation' k a@ (for use with
182+-- 'leftAlign' and friends) by 'prebind'ing with 'queryTable', like this:
183+--
184+-- @
185+-- tabulation `'prebind'` 'queryTable'
186+-- @
136187prebind :: (a -> Tabulation k b) -> Query a -> Tabulation k b
137188prebind f as = Tabulation $ \k -> do
138189 a <- as
@@ -141,6 +192,7 @@ prebind f as = Tabulation $ \k -> do
141192infixr 1 `prebind`
142193
143194
195+-- | Map a 'Query' over the output side of a 'Tabulation'.
144196postbind :: (a -> Query b) -> Tabulation k a -> Tabulation k b
145197postbind f (Tabulation as) = Tabulation $ \i -> do
146198 (k, a) <- as i
@@ -151,6 +203,8 @@ postbind f (Tabulation as) = Tabulation $ \i -> do
151203infixr 1 `postbind`
152204
153205
206+-- | Note that because 'Tabulation' is a @MultiMap@, the 'Query' returned by
207+-- 'lookup' can and often does contain multiple results.
154208lookup :: EqTable k => k -> Tabulation k a -> Query a
155209lookup key (Tabulation query) = do
156210 (mk, a) <- query key
@@ -158,11 +212,19 @@ lookup key (Tabulation query) = do
158212 pure a
159213
160214
215+-- | Analagous to
216+-- [@align@](https://hackage.haskell.org/package/semialign/docs/Data-Semialign.html#v:align).
217+--
218+-- If 'zip' makes an @INNER JOIN@, then 'align' makes a @FULL OUTER JOIN@.
161219align :: (EqTable k, Table Expr a, Table Expr b)
162220 => Tabulation k a -> Tabulation k b -> Tabulation k (TheseTable a b)
163221align = alignWith id
164222
165223
224+-- | Analagous to
225+-- [@alignWith@](https://hackage.haskell.org/package/semialign/docs/Data-Semialign.html#v:alignWith).
226+--
227+-- See 'zipWith' and 'align'.
166228alignWith :: (EqTable k, Table Expr a, Table Expr b)
167229 => (TheseTable a b -> c)
168230 -> Tabulation k a -> Tabulation k b -> Tabulation k c
@@ -177,27 +239,73 @@ alignWith f kas kbs = do
177239 pure (k', f tab)
178240
179241
242+-- | If 'zip' makes an @INNER JOIN@, then 'leftAlign' makes a @LEFT JOIN@.
243+-- This means it will return at least one row for every row in the left
244+-- 'Tabulation', even if there is no corresponding row in the right (hence the
245+-- 'MaybeTable').
246+--
247+-- Analagous to
248+-- [@rpadZip@](https://hackage.haskell.org/package/semialign/docs/Data-Semialign.html#v:rpadZip).
180249leftAlign :: EqTable k
181250 => Tabulation k a -> Tabulation k b -> Tabulation k (a, MaybeTable b)
182251leftAlign = leftAlignWith (,)
183252
184253
254+-- | See 'zipWith' and 'leftAlign'.
255+--
256+-- Analagous to
257+-- [@rpadZipWith@](https://hackage.haskell.org/package/semialign/docs/Data-Semialign.html#v:rpadZipWith).
185258leftAlignWith :: EqTable k
186259 => (a -> MaybeTable b -> c)
187260 -> Tabulation k a -> Tabulation k b -> Tabulation k c
188261leftAlignWith f left right = liftA2 f left (optionalTabulation right)
189262
190263
264+-- | Analagous to
265+-- [@zip@](https://hackage.haskell.org/package/semialign/docs/Data-Semialign.html#v:zip).
266+--
267+-- There are multiple correct ways of understanding what this does.
268+--
269+-- You can think of it as @'Data.Map.Strict.intersectionWith'
270+-- ('Control.Applicative.liftA2' (,))@. That is, @intersect@ the two
271+-- `Tabulation`s by 'match'ing their keys together, and combine their values
272+-- (remembering that 'Tabulation' is a 'MultiMap' so that the values are keys)
273+-- by getting their cartesian product.
274+--
275+-- You can think of it as performing a cross product of the underlying 'Query's
276+-- of the given 'Tabulation's and filtering the results for 'match'ing keys.
277+--
278+-- You can think of it as a natural join in SQL terms.
279+--
280+-- The size of the resulting 'Tabulation' will be \(\sum_{k} min(n_k, m_k) \)
281+-- in terms of the number of keys, but \(\sum_{k} n_k \times m_k\) in terms of
282+-- the number of values.
191283zip :: EqTable k
192284 => Tabulation k a -> Tabulation k b -> Tabulation k (a, b)
193285zip = zipWith (,)
194286
195287
288+-- | Analagous to
289+-- [@zipWith@](https://hackage.haskell.org/package/semialign/docs/Data-Semialign.html#v:zipWith).
290+--
291+-- See 'zip'.
196292zipWith :: EqTable k
197293 => (a -> b -> c) -> Tabulation k a -> Tabulation k b -> Tabulation k c
198294zipWith = liftA2
199295
200296
297+-- | 'similarity' returns all the entries in the left 'Tabulation' that have a
298+-- corresponding entry in the right 'Tabulation'. This corresponds to a
299+-- semijoin in relational algebra.
300+--
301+-- This differs from @'zipWith' const x y@ when the right 'Tabulation' @y@
302+-- contains an entry with multiple rows. For 'similarity', the entries in the
303+-- resulting 'Tabulation' will contain the same number of rows as their
304+-- respective entries in the left 'Tabulation' @x@. With `zipWith const x y`,
305+-- each entry would contain the /product/ of the number of rows of their
306+-- respective entries in @x@ and @y@.
307+--
308+-- See 'with'.
201309similarity :: EqTable k => Tabulation k a -> Tabulation k b -> Tabulation k a
202310similarity kas kbs = do
203311 as <- toQuery kas
@@ -205,6 +313,11 @@ similarity kas kbs = do
205313 fromQuery $ as >>= withBy (\(k, _) (l, _) -> k ==: l) bs
206314
207315
316+-- | 'difference' returns all the entries in the left 'Tabulation' that don't
317+-- exist in the right 'Tabulation'. This corresponds to an antijoin in
318+-- relational algebra.
319+--
320+-- See 'without'.
208321difference :: EqTable k => Tabulation k a -> Tabulation k b -> Tabulation k a
209322difference kas kbs = do
210323 as <- toQuery kas
src/Rel8/Type/Array1D.hs+1
@@ -66,6 +66,7 @@ import Data.Semialign ( Semialign )
6666import Data.Zip ( Repeat, Unzip, Zip )
6767
6868
69+-- | A one dimensional array.
6970newtype Array1D a = Array1D [a]
7071 deriving stock Traversable
7172 deriving newtype
src/Rel8/Type/Num.hs+3
@@ -40,6 +40,9 @@ instance {-# INCOHERENT #-} (HasNullability a, DBNum (Unnullify a)) =>
4040 Sql DBNum a
4141
4242
43+-- | The class of database types that can be coerced to from integral
44+-- expressions. This is a Rel8 concept, and allows us to provide
45+-- 'fromIntegral'.
4346type DBIntegral :: Type -> Constraint
4447class DBNum a => DBIntegral a
4548instance DBIntegral Int16