on master help sign in
Lots more docs
6e001f8Oliver Charles
src/Rel8/Expr/Aggregate.hs+3
@@ -130,6 +130,7 @@ sum = unsafeMakeAggregate toPrimExpr (castExpr . fromPrimExpr) $
130 }
130 }
131
131
132
132
133+-- | Take the sum of all expressions that satisfy a predicate.
133sumWhere :: (Sql DBNum a, Sql DBSum a)
134sumWhere :: (Sql DBNum a, Sql DBSum a)
134 => Expr Bool -> Expr a -> Aggregate (Expr a)
135 => Expr Bool -> Expr a -> Aggregate (Expr a)
135sumWhere condition a = sum (caseExpr [(condition, a)] 0)
136sumWhere condition a = sum (caseExpr [(condition, a)] 0)
@@ -152,6 +153,7 @@ groupByExpr :: Sql DBEq a => Expr a -> Aggregate (Expr a)
152groupByExpr = unsafeMakeAggregate toPrimExpr fromPrimExpr Nothing
153groupByExpr = unsafeMakeAggregate toPrimExpr fromPrimExpr Nothing
153
154
154
155
156+-- | Collect expressions values as a list.
155listAggExpr :: Expr a -> Aggregate (Expr [a])
157listAggExpr :: Expr a -> Aggregate (Expr [a])
156listAggExpr = unsafeMakeAggregate toPrimExpr from $ Just
158listAggExpr = unsafeMakeAggregate toPrimExpr from $ Just
157 Aggregator
159 Aggregator
@@ -163,6 +165,7 @@ listAggExpr = unsafeMakeAggregate toPrimExpr from $ Just
163 from = fromPrimExpr . fromPrimArray
165 from = fromPrimExpr . fromPrimArray
164
166
165
167
168+-- | Collect expressions values as a non-empty list.
166nonEmptyAggExpr :: Expr a -> Aggregate (Expr (NonEmpty a))
169nonEmptyAggExpr :: Expr a -> Aggregate (Expr (NonEmpty a))
167nonEmptyAggExpr = unsafeMakeAggregate toPrimExpr from $ Just
170nonEmptyAggExpr = unsafeMakeAggregate toPrimExpr from $ Just
168 Aggregator
171 Aggregator
src/Rel8/Expr/Bool.hs+4
@@ -78,6 +78,10 @@ caseExpr branches (Expr fallback) =
78 go (Expr condition, Expr value) = (condition, value)
78 go (Expr condition, Expr value) = (condition, value)
79
79
80
80
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.
81coalesce :: Expr (Maybe Bool) -> Expr Bool
85coalesce :: Expr (Maybe Bool) -> Expr Bool
82coalesce (Expr a) = Expr a &&. Expr (Opaleye.FunExpr "COALESCE" [a, untrue])
86coalesce (Expr a) = Expr a &&. Expr (Opaleye.FunExpr "COALESCE" [a, untrue])
83 where
87 where
src/Rel8/Expr/Num.hs+20
@@ -19,39 +19,59 @@ import Rel8.Schema.Nullability ( Homonullable, Sql )
19import Rel8.Type.Num ( DBFractional, DBIntegral, DBNum )
19import Rel8.Type.Num ( DBFractional, DBIntegral, DBNum )
20
20
21
21
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@.
22fromIntegral :: (Sql DBIntegral a, Sql DBNum b, Homonullable a b)
24fromIntegral :: (Sql DBIntegral a, Sql DBNum b, Homonullable a b)
23 => Expr a -> Expr b
25 => Expr a -> Expr b
24fromIntegral (Expr a) = castExpr (Expr a)
26fromIntegral (Expr a) = castExpr (Expr a)
25
27
26
28
29+-- | Cast 'DBNum' types to 'DBFractional' types. For example, his can be useful
30+-- to convert @Expr Float@ to @Expr Double@.
27realToFrac :: (Sql DBNum a, Sql DBFractional b, Homonullable a b)
31realToFrac :: (Sql DBNum a, Sql DBFractional b, Homonullable a b)
28 => Expr a -> Expr b
32 => Expr a -> Expr b
29realToFrac (Expr a) = castExpr (Expr a)
33realToFrac (Expr a) = castExpr (Expr a)
30
34
31
35
36+-- | Round a 'DBFractional' to a 'DBIntegral' by rounding to the nearest larger
37+-- integer.
38+--
39+-- Corresponds to the @ceiling()@ function.
32ceiling :: (Sql DBFractional a, Sql DBIntegral b, Homonullable a b)
40ceiling :: (Sql DBFractional a, Sql DBIntegral b, Homonullable a b)
33 => Expr a -> Expr b
41 => Expr a -> Expr b
34ceiling = function "CEILING"
42ceiling = function "CEILING"
35
43
36
44
45+-- | Perform integral division. Corresponds to the @div()@ function.
37div :: Sql DBIntegral a => Expr a -> Expr a -> Expr a
46div :: Sql DBIntegral a => Expr a -> Expr a -> Expr a
38div = function "DIV"
47div = function "DIV"
39
48
40
49
50+-- | Corresponds to the @mod()@ function.
41mod :: Sql DBIntegral a => Expr a -> Expr a -> Expr a
51mod :: Sql DBIntegral a => Expr a -> Expr a -> Expr a
42mod = function "MOD"
52mod = function "MOD"
43
53
44
54
55+-- | Round a 'DFractional' to a 'DBIntegral' by rounding to the nearest smaller
56+-- integer.
57+--
58+-- Corresponds to the @floor()@ function.
45floor :: (Sql DBFractional a, Sql DBIntegral b, Homonullable a b)
59floor :: (Sql DBFractional a, Sql DBIntegral b, Homonullable a b)
46 => Expr a -> Expr b
60 => Expr a -> Expr b
47floor = function "FLOOR"
61floor = function "FLOOR"
48
62
49
63
64+-- | Round a 'DBFractional' to a 'DBIntegral' by rounding to the nearest
65+-- integer.
66+--
67+-- Corresponds to the @round()@ function.
50round :: (Sql DBFractional a, Sql DBIntegral b, Homonullable a b)
68round :: (Sql DBFractional a, Sql DBIntegral b, Homonullable a b)
51 => Expr a -> Expr b
69 => Expr a -> Expr b
52round = function "ROUND"
70round = function "ROUND"
53
71
54
72
73+-- | Round a 'DBFractional' to a 'DBIntegral' by rounding to the nearest
74+-- integer towards zero.
55truncate :: (Sql DBFractional a, Sql DBIntegral b, Homonullable a b)
75truncate :: (Sql DBFractional a, Sql DBIntegral b, Homonullable a b)
56 => Expr a -> Expr b
76 => Expr a -> Expr b
57truncate = function "TRUNC"
77truncate = function "TRUNC"
src/Rel8/Expr/Opaleye.hs+2
@@ -34,6 +34,8 @@ castExpr :: Sql DBType a => Expr a -> Expr a
34castExpr = scastExpr typeInformation
34castExpr = scastExpr typeInformation
35
35
36
36
37+-- | Cast an expression to a different type. Corresponds to a @CAST()@ function
38+-- call.
37unsafeCastExpr :: Sql DBType b => Expr a -> Expr b
39unsafeCastExpr :: Sql DBType b => Expr a -> Expr b
38unsafeCastExpr = sunsafeCastExpr typeInformation
40unsafeCastExpr = sunsafeCastExpr typeInformation
39
41
src/Rel8/Expr/Serialize.hs+4
@@ -30,6 +30,10 @@ import Rel8.Type ( DBType, typeInformation )
30import Rel8.Type.Information ( TypeInformation(..) )
30import Rel8.Type.Information ( TypeInformation(..) )
31
31
32
32
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.
33litExpr :: Sql DBType a => a -> Expr a
37litExpr :: Sql DBType a => a -> Expr a
34litExpr = slitExpr nullabilization typeInformation
38litExpr = slitExpr nullabilization typeInformation
35
39
src/Rel8/Expr/Text.hs+45−10
@@ -16,7 +16,7 @@ module Rel8.Expr.Text
16 , initcap, left, length, lengthEncoding, lpad, ltrim, md5
16 , initcap, left, length, lengthEncoding, lpad, ltrim, md5
17 , pgClientEncoding, quoteIdent, quoteLiteral, quoteNullable, regexpReplace
17 , pgClientEncoding, quoteIdent, quoteLiteral, quoteNullable, regexpReplace
18 , regexpSplitToArray, repeat, replace, reverse, right, rpad, rtrim
18 , regexpSplitToArray, repeat, replace, reverse, right, rpad, rtrim
19− , splitPart, strpos, substr, toAscii, toHex, translate
19+ , splitPart, strpos, substr, translate
20 )
20 )
21where
21where
22
22
@@ -50,24 +50,32 @@ infixr 6 ++.
50
50
51
51
52-- | Matches regular expression, case sensitive
52-- | Matches regular expression, case sensitive
53+--
54+-- Corresponds to the @~.@ operator.
53(~.) :: Expr Text -> Expr Text -> Expr Bool
55(~.) :: Expr Text -> Expr Text -> Expr Bool
54(~.) = binaryOperator "~."
56(~.) = binaryOperator "~."
55infix 2 ~.
57infix 2 ~.
56
58
57
59
58-- | Matches regular expression, case insensitive
60-- | Matches regular expression, case insensitive
61+--
62+-- Corresponds to the @~*@ operator.
59(~*) :: Expr Text -> Expr Text -> Expr Bool
63(~*) :: Expr Text -> Expr Text -> Expr Bool
60(~*) = binaryOperator "~*"
64(~*) = binaryOperator "~*"
61infix 2 ~*
65infix 2 ~*
62
66
63
67
64-- | Does not match regular expression, case sensitive
68-- | Does not match regular expression, case sensitive
69+--
70+-- Corresponds to the @!~@ operator.
65(!~) :: Expr Text -> Expr Text -> Expr Bool
71(!~) :: Expr Text -> Expr Text -> Expr Bool
66(!~) = binaryOperator "!~"
72(!~) = binaryOperator "!~"
67infix 2 !~
73infix 2 !~
68
74
69
75
70-- | Does not match regular expression, case insensitive
76-- | Does not match regular expression, case insensitive
77+--
78+-- Corresponds to the @!~*@ operator.
71(!~*) :: Expr Text -> Expr Text -> Expr Bool
79(!~*) :: Expr Text -> Expr Text -> Expr Bool
72(!~*) = binaryOperator "!~*"
80(!~*) = binaryOperator "!~*"
73infix 2 !~*
81infix 2 !~*
@@ -78,164 +86,191 @@ infix 2 !~*
78-- * Standard SQL functions
86-- * Standard SQL functions
79
87
80
88
89+-- | Corresponds to the @bit_length@ function.
81bitLength :: Expr Text -> Expr Int32
90bitLength :: Expr Text -> Expr Int32
82bitLength = function "bit_length"
91bitLength = function "bit_length"
83
92
84
93
94+-- | Corresponds to the @char_length@ function.
85charLength :: Expr Text -> Expr Int32
95charLength :: Expr Text -> Expr Int32
86charLength = function "char_length"
96charLength = function "char_length"
87
97
88
98
99+-- | Corresponds to the @lower@ function.
89lower :: Expr Text -> Expr Text
100lower :: Expr Text -> Expr Text
90lower = function "lower"
101lower = function "lower"
91
102
92
103
104+-- | Corresponds to the @octet_length@ function.
93octetLength :: Expr Text -> Expr Int32
105octetLength :: Expr Text -> Expr Int32
94octetLength = function "octet_length"
106octetLength = function "octet_length"
95
107
96
108
109+-- | Corresponds to the @upper@ function.
97upper :: Expr Text -> Expr Text
110upper :: Expr Text -> Expr Text
98upper = function "upper"
111upper = function "upper"
99
112
100
113
101−-- * PostgreSQL functions
114+-- | Corresponds to the @ascii@ function.
102ascii :: Expr Text -> Expr Int32
115ascii :: Expr Text -> Expr Int32
103ascii = function "ascii"
116ascii = function "ascii"
104
117
105
118
119+-- | Corresponds to the @btrim@ function.
106btrim :: Expr Text -> Maybe (Expr Text) -> Expr Text
120btrim :: Expr Text -> Maybe (Expr Text) -> Expr Text
107btrim a (Just b) = function "btrim" a b
121btrim a (Just b) = function "btrim" a b
108btrim a Nothing = function "btrim" a
122btrim a Nothing = function "btrim" a
109
123
110
124
125+-- | Corresponds to the @chr@ function.
111chr :: Expr Int32 -> Expr Text
126chr :: Expr Int32 -> Expr Text
112chr = function "chr"
127chr = function "chr"
113
128
114
129
130+-- | Corresponds to the @convert@ function.
115convert :: Expr ByteString -> Expr Text -> Expr Text -> Expr ByteString
131convert :: Expr ByteString -> Expr Text -> Expr Text -> Expr ByteString
116convert = function "convert"
132convert = function "convert"
117
133
118
134
135+-- | Corresponds to the @convert_from@ function.
119convertFrom :: Expr ByteString -> Expr Text -> Expr Text
136convertFrom :: Expr ByteString -> Expr Text -> Expr Text
120convertFrom = function "convert_from"
137convertFrom = function "convert_from"
121
138
122
139
140+-- | Corresponds to the @convert_to@ function.
123convertTo :: Expr Text -> Expr Text -> Expr ByteString
141convertTo :: Expr Text -> Expr Text -> Expr ByteString
124convertTo = function "convert_to"
142convertTo = function "convert_to"
125
143
126
144
145+-- | Corresponds to the @decode@ function.
127decode :: Expr Text -> Expr Text -> Expr ByteString
146decode :: Expr Text -> Expr Text -> Expr ByteString
128decode = function "decode"
147decode = function "decode"
129
148
130
149
150+-- | Corresponds to the @encode@ function.
131encode :: Expr ByteString -> Expr Text -> Expr Text
151encode :: Expr ByteString -> Expr Text -> Expr Text
132encode = function "encode"
152encode = function "encode"
133
153
134
154
155+-- | Corresponds to the @initcap@ function.
135initcap :: Expr Text -> Expr Text
156initcap :: Expr Text -> Expr Text
136initcap = function "initcap"
157initcap = function "initcap"
137
158
138
159
160+-- | Corresponds to the @left@ function.
139left :: Expr Text -> Expr Int32 -> Expr Text
161left :: Expr Text -> Expr Int32 -> Expr Text
140left = function "left"
162left = function "left"
141
163
142
164
165+-- | Corresponds to the @length@ function.
143length :: Expr Text -> Expr Int32
166length :: Expr Text -> Expr Int32
144length = function "length"
167length = function "length"
145
168
146
169
170+-- | Corresponds to the @length@ function.
147lengthEncoding :: Expr ByteString -> Expr Text -> Expr Int32
171lengthEncoding :: Expr ByteString -> Expr Text -> Expr Int32
148lengthEncoding = function "length"
172lengthEncoding = function "length"
149
173
150
174
175+-- | Corresponds to the @lpad@ function.
151lpad :: Expr Text -> Expr Int32 -> Maybe (Expr Text) -> Expr Text
176lpad :: Expr Text -> Expr Int32 -> Maybe (Expr Text) -> Expr Text
152lpad a b (Just c) = function "lpad" a b c
177lpad a b (Just c) = function "lpad" a b c
153lpad a b Nothing = function "lpad" a b
178lpad a b Nothing = function "lpad" a b
154
179
155
180
181+-- | Corresponds to the @ltrim@ function.
156ltrim :: Expr Text -> Maybe (Expr Text) -> Expr Text
182ltrim :: Expr Text -> Maybe (Expr Text) -> Expr Text
157ltrim a (Just b) = function "ltrim" a b
183ltrim a (Just b) = function "ltrim" a b
158ltrim a Nothing = function "ltrim" a
184ltrim a Nothing = function "ltrim" a
159
185
160
186
187+-- | Corresponds to the @md5@ function.
161md5 :: Expr Text -> Expr Text
188md5 :: Expr Text -> Expr Text
162md5 = function "md5"
189md5 = function "md5"
163
190
164
191
192+-- | Corresponds to the @pg_client_encoding()@ expression.
165pgClientEncoding :: Expr Text
193pgClientEncoding :: Expr Text
166pgClientEncoding = nullaryFunction "pg_client_encoding"
194pgClientEncoding = nullaryFunction "pg_client_encoding"
167
195
168
196
197+-- | Corresponds to the @quote_ident@ function.
169quoteIdent :: Expr Text -> Expr Text
198quoteIdent :: Expr Text -> Expr Text
170quoteIdent = function "quote_ident"
199quoteIdent = function "quote_ident"
171
200
172
201
202+-- | Corresponds to the @quote_literal@ function.
173quoteLiteral :: Expr Text -> Expr Text
203quoteLiteral :: Expr Text -> Expr Text
174quoteLiteral = function "quote_literal"
204quoteLiteral = function "quote_literal"
175
205
176
206
207+-- | Corresponds to the @quote_nullable@ function.
177quoteNullable :: Expr Text -> Expr Text
208quoteNullable :: Expr Text -> Expr Text
178quoteNullable = function "quote_nullable"
209quoteNullable = function "quote_nullable"
179
210
180
211
212+-- | Corresponds to the @regexp_replace@ function.
181regexpReplace :: ()
213regexpReplace :: ()
182 => Expr Text -> Expr Text -> Expr Text -> Maybe (Expr Text) -> Expr Text
214 => Expr Text -> Expr Text -> Expr Text -> Maybe (Expr Text) -> Expr Text
183regexpReplace a b c (Just d) = function "regexp_replace" a b c d
215regexpReplace a b c (Just d) = function "regexp_replace" a b c d
184regexpReplace a b c Nothing = function "regexp_replace" a b c
216regexpReplace a b c Nothing = function "regexp_replace" a b c
185
217
186
218
219+-- | Corresponds to the @regexp_split_to_array@ function.
187regexpSplitToArray :: ()
220regexpSplitToArray :: ()
188 => Expr Text -> Expr Text -> Maybe (Expr Text) -> Expr (Array1D Text)
221 => Expr Text -> Expr Text -> Maybe (Expr Text) -> Expr (Array1D Text)
189regexpSplitToArray a b (Just c) = function "regexp_split_to_array" a b c
222regexpSplitToArray a b (Just c) = function "regexp_split_to_array" a b c
190regexpSplitToArray a b Nothing = function "regexp_split_to_array" a b
223regexpSplitToArray a b Nothing = function "regexp_split_to_array" a b
191
224
192
225
226+-- | Corresponds to the @repeat@ function.
193repeat :: Expr Text -> Expr Int32 -> Expr Text
227repeat :: Expr Text -> Expr Int32 -> Expr Text
194repeat = function "repeat"
228repeat = function "repeat"
195
229
196
230
231+-- | Corresponds to the @replace@ function.
197replace :: Expr Text -> Expr Text -> Expr Text -> Expr Text
232replace :: Expr Text -> Expr Text -> Expr Text -> Expr Text
198replace = function "replace"
233replace = function "replace"
199
234
200
235
236+-- | Corresponds to the @reverse@ function.
201reverse :: Expr Text -> Expr Text
237reverse :: Expr Text -> Expr Text
202reverse = function "reverse"
238reverse = function "reverse"
203
239
204
240
241+-- | Corresponds to the @right@ function.
205right :: Expr Text -> Expr Int32 -> Expr Text
242right :: Expr Text -> Expr Int32 -> Expr Text
206right = function "right"
243right = function "right"
207
244
208
245
246+-- | Corresponds to the @rpad@ function.
209rpad :: Expr Text -> Expr Int32 -> Maybe (Expr Text) -> Expr Text
247rpad :: Expr Text -> Expr Int32 -> Maybe (Expr Text) -> Expr Text
210rpad a b (Just c) = function "rpad" a b c
248rpad a b (Just c) = function "rpad" a b c
211rpad a b Nothing = function "rpad" a b
249rpad a b Nothing = function "rpad" a b
212
250
213
251
252+-- | Corresponds to the @rtrim@ function.
214rtrim :: Expr Text -> Maybe (Expr Text) -> Expr Text
253rtrim :: Expr Text -> Maybe (Expr Text) -> Expr Text
215rtrim a (Just b) = function "rtrim" a b
254rtrim a (Just b) = function "rtrim" a b
216rtrim a Nothing = function "rtrim" a
255rtrim a Nothing = function "rtrim" a
217
256
218
257
258+-- | Corresponds to the @split_part@ function.
219splitPart :: Expr Text -> Expr Text -> Expr Int32 -> Expr Text
259splitPart :: Expr Text -> Expr Text -> Expr Int32 -> Expr Text
220splitPart = function "split_part"
260splitPart = function "split_part"
221
261
222
262
263+-- | Corresponds to the @strpos@ function.
223strpos :: Expr Text -> Expr Text -> Expr Int32
264strpos :: Expr Text -> Expr Text -> Expr Int32
224strpos = function "strpos"
265strpos = function "strpos"
225
266
226
267
268+-- | Corresponds to the @substr@ function.
227substr :: Expr Text -> Expr Int32 -> Maybe (Expr Int32) -> Expr Text
269substr :: Expr Text -> Expr Int32 -> Maybe (Expr Int32) -> Expr Text
228substr a b (Just c) = function "substr" a b c
270substr a b (Just c) = function "substr" a b c
229substr a b Nothing = function "substr" a b
271substr a b Nothing = function "substr" a b
230
272
231
273
232−toAscii :: Expr Text -> Expr Text -> Expr Text
274+-- | Corresponds to the @translate@ function.
233−toAscii = function "toAscii"
234−
235−
236−toHex :: Expr Int32 -> Expr Text
237−toHex = function "toHex"
238−
239−
240translate :: Expr Text -> Expr Text -> Expr Text -> Expr Text
275translate :: Expr Text -> Expr Text -> Expr Text -> Expr Text
241translate = function "translate"
276translate = function "translate"
src/Rel8/Query/Aggregate.hs+3
@@ -29,5 +29,8 @@ aggregate :: Query (Aggregate exprs) -> Query exprs
29aggregate = mapOpaleye (Opaleye.aggregate aggregator)
29aggregate = mapOpaleye (Opaleye.aggregate aggregator)
30
30
31
31
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@.
32countRows :: Query a -> Query (Expr Int64)
35countRows :: Query a -> Query (Expr Int64)
33countRows = fmap (maybeTable 0 id) . optional . aggregate . fmap (const countStar)
36countRows = fmap (maybeTable 0 id) . optional . aggregate . fmap (const countStar)
src/Rel8/Query/Exists.hs+7−1
@@ -46,17 +46,23 @@ whereNotExists :: Query a -> Query ()
46whereNotExists = mapOpaleye Opaleye.restrictNotExists
46whereNotExists = mapOpaleye Opaleye.restrictNotExists
47
47
48
48
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'.
49with :: (a -> Query b) -> a -> Query a
52with :: (a -> Query b) -> a -> Query a
50with f a = a <$ whereExists (f a)
53with f a = a <$ whereExists (f a)
51
54
52
55
56+-- | Like @with@, but with a custom membership test.
53−withBy :: (a -> b -> Expr Bool) -> Query b -> a -> Query a
57+withBy :: (a -> b -> Expr Bool) -> Query b -> a -> Query a
54withBy predicate bs = with $ \a -> bs >>= filter (predicate a)
58withBy predicate bs = with $ \a -> bs >>= filter (predicate a)
55
59
56
60
61+-- | Filter rows where @a -> Query b@ yields no rows.
57without :: (a -> Query b) -> a -> Query a
62without :: (a -> Query b) -> a -> Query a
58without f a = a <$ whereNotExists (f a)
63without f a = a <$ whereNotExists (f a)
59
64
60
65
66+-- | Like @without@, but with a custom membership test.
61withoutBy :: (a -> b -> Expr Bool) -> Query b -> a -> Query a
67withoutBy :: (a -> b -> Expr Bool) -> Query b -> a -> Query a
62withoutBy predicate bs = without $ \a -> bs >>= filter (predicate a)
68withoutBy predicate bs = without $ \a -> bs >>= filter (predicate a)
src/Rel8/Query/Maybe.hs+5
@@ -28,6 +28,11 @@ import Rel8.Table.Opaleye ( unpackspec )
28import Rel8.Table.Tag ( Tag(..), fromExpr )
28import Rel8.Table.Tag ( Tag(..), fromExpr )
29
29
30
30
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.
31optional :: Query a -> Query (MaybeTable a)
36optional :: Query a -> Query (MaybeTable a)
32optional = mapOpaleye $ Opaleye.QueryArr . go
37optional = mapOpaleye $ Opaleye.QueryArr . go
33 where
38 where
src/Rel8/Schema/Nullability/Internal.hs+5
@@ -57,12 +57,15 @@ type Nullify :: Type -> Type
57type Nullify a = Maybe (Unnullify a)
57type Nullify a = Maybe (Unnullify a)
58
58
59
59
60+-- | @NotNull a@ means @a@ cannot take @null@ as a value.
60type NotNull :: Type -> Constraint
61type NotNull :: Type -> Constraint
61class (HasNullability a, IsMaybe a ~ 'False) => NotNull a
62class (HasNullability a, IsMaybe a ~ 'False) => NotNull a
62instance (HasNullability a, IsMaybe a ~ 'False) => NotNull a
63instance (HasNullability a, IsMaybe a ~ 'False) => NotNull a
63instance {-# OVERLAPPING #-} NotNull Opaque
64instance {-# OVERLAPPING #-} NotNull Opaque
64
65
65
66
67+-- | @Homonullable a b@ means that both @a@ and @b@ can be @null@, or neither
68+-- @a@ or @b@ can be @null@.
66type Homonullable :: Type -> Type -> Constraint
69type Homonullable :: Type -> Type -> Constraint
67class IsMaybe a ~ IsMaybe b => Homonullable a b
70class IsMaybe a ~ IsMaybe b => Homonullable a b
68instance IsMaybe a ~ IsMaybe b => Homonullable a b
71instance IsMaybe a ~ IsMaybe b => Homonullable a b
@@ -93,6 +96,8 @@ instance IsMaybe a ~ 'False => HasNullability' 'True (Maybe a) where
93 nullabilization' = Nullable
96 nullabilization' = Nullable
94
97
95
98
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.
96type HasNullability :: Type -> Constraint
101type HasNullability :: Type -> Constraint
97class HasNullability' (IsMaybe a) a => HasNullability a
102class HasNullability' (IsMaybe a) a => HasNullability a
98instance HasNullability' (IsMaybe a) a => HasNullability a
103instance HasNullability' (IsMaybe a) a => HasNullability a
src/Rel8/Statement/View.hs+3
@@ -31,6 +31,9 @@ import qualified Data.Text as Text
31import Data.Text.Encoding ( encodeUtf8 )
31import Data.Text.Encoding ( encodeUtf8 )
32
32
33
33
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.
34createView :: Selects names exprs
37createView :: Selects names exprs
35 => TableSchema names -> Query exprs -> Connection -> IO ()
38 => TableSchema names -> Query exprs -> Connection -> IO ()
36createView (TableSchema name mschema names) query =
39createView (TableSchema name mschema names) query =
src/Rel8/Table/Bool.hs+7
@@ -18,6 +18,10 @@ import Rel8.Schema.HTable ( htabulate, hfield )
18import Rel8.Table ( Table, fromColumns, toColumns )
18import Rel8.Table ( Table, fromColumns, toColumns )
19
19
20
20
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@.
21bool :: Table Expr a => a -> a -> Expr Bool -> a
25bool :: Table Expr a => a -> a -> Expr Bool -> a
22bool (toColumns -> false) (toColumns -> true) condition =
26bool (toColumns -> false) (toColumns -> true) condition =
23 fromColumns $ htabulate $ \field ->
27 fromColumns $ htabulate $ \field ->
@@ -27,6 +31,9 @@ bool (toColumns -> false) (toColumns -> true) condition =
27{-# INLINABLE bool #-}
31{-# INLINABLE bool #-}
28
32
29
33
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.
30case_ :: Table Expr a => [(Expr Bool, a)] -> a -> a
37case_ :: Table Expr a => [(Expr Bool, a)] -> a -> a
31case_ (map (fmap toColumns) -> branches) (toColumns -> fallback) =
38case_ (map (fmap toColumns) -> branches) (toColumns -> fallback) =
32 fromColumns $ htabulate $ \field -> case hfield fallback field of
39 fromColumns $ htabulate $ \field -> case hfield fallback field of
src/Rel8/Table/Eq.hs+3
@@ -48,6 +48,9 @@ import Rel8.Type.Eq ( DBEq )
48import Rel8.Schema.Nullability ( Sql )
48import Rel8.Schema.Nullability ( Sql )
49
49
50
50
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'".
51type EqTable :: Type -> Constraint
54type EqTable :: Type -> Constraint
52class Table Expr a => EqTable a where
55class Table Expr a => EqTable a where
53 eqTable :: Columns a (Dict (ConstrainDBType DBEq))
56 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
279instance {-# OVERLAPPING #-} Sql DBType a => Serializable (Expr a) a
279instance {-# OVERLAPPING #-} Sql DBType a => Serializable (Expr a) a
280
280
281
281
282+-- | Use @lit@ to turn literal Haskell values into expressions. @lit@ is
283+-- capable of lifting single @Expr@s to full tables.
282lit :: forall exprs a. Serializable exprs a => a -> exprs
284lit :: forall exprs a. Serializable exprs a => a -> exprs
283lit = fromColumns . litHTable . toIdentity' @exprs
285lit = fromColumns . litHTable . toIdentity' @exprs
284
286
src/Rel8/Tabulate.hs+113
@@ -60,6 +60,13 @@ import qualified Data.Functor.Bind
60import Data.Semigroup.Traversable.Class ( bitraverse1 )
60import Data.Semigroup.Traversable.Class ( bitraverse1 )
61
61
62
62
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.
63newtype Tabulation k a = Tabulation (k -> Query (Maybe k, a))
70newtype Tabulation k a = Tabulation (k -> Query (Maybe k, a))
64 deriving stock Functor
71 deriving stock Functor
65
72
@@ -93,6 +100,16 @@ instance EqTable k => Monad (Tabulation k) where
93 pure (mk', b)
100 pure (mk', b)
94
101
95
102
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.
96runTabulation :: EqTable k => Tabulation k a -> Query (k, a)
113runTabulation :: EqTable k => Tabulation k a -> Query (k, a)
97runTabulation (Tabulation f) = do
114runTabulation (Tabulation f) = do
98 (mk, a) <- f i
115 (mk, a) <- f i
@@ -111,14 +128,41 @@ liftQuery :: Query a -> Tabulation k a
111liftQuery query = Tabulation $ const $ fmap (Nothing,) query
128liftQuery query = Tabulation $ const $ fmap (Nothing,) query
112
129
113
130
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+-- @
114tabulate :: (a -> k) -> a -> Tabulation k a
154tabulate :: (a -> k) -> a -> Tabulation k a
115tabulate key a = fromQuery $ pure (key a, a)
155tabulate key a = fromQuery $ pure (key a, a)
116
156
117
157
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'.
118tabulateA :: (a -> Query k) -> a -> Tabulation k a
161tabulateA :: (a -> Query k) -> a -> Tabulation k a
119tabulateA key a = fromQuery $ (,a) <$> key a
162tabulateA key a = fromQuery $ (,a) <$> key a
120
163
121
164
165+-- | Analgous to 'Data.Map.Strict.fromList'.
122fromQuery :: Query (k, a) -> Tabulation k a
166fromQuery :: Query (k, a) -> Tabulation k a
123fromQuery = Tabulation . const . fmap (first Just)
167fromQuery = Tabulation . const . fmap (first Just)
124
168
@@ -133,6 +177,13 @@ ifilter f tabulation = snd <$> do
133 filter (uncurry f) `postbind` indexed tabulation
177 filter (uncurry f) `postbind` indexed tabulation
134
178
135
179
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+-- @
136prebind :: (a -> Tabulation k b) -> Query a -> Tabulation k b
187prebind :: (a -> Tabulation k b) -> Query a -> Tabulation k b
137prebind f as = Tabulation $ \k -> do
188prebind f as = Tabulation $ \k -> do
138 a <- as
189 a <- as
@@ -141,6 +192,7 @@ prebind f as = Tabulation $ \k -> do
141infixr 1 `prebind`
192infixr 1 `prebind`
142
193
143
194
195+-- | Map a 'Query' over the output side of a 'Tabulation'.
144postbind :: (a -> Query b) -> Tabulation k a -> Tabulation k b
196postbind :: (a -> Query b) -> Tabulation k a -> Tabulation k b
145postbind f (Tabulation as) = Tabulation $ \i -> do
197postbind f (Tabulation as) = Tabulation $ \i -> do
146 (k, a) <- as i
198 (k, a) <- as i
@@ -151,6 +203,8 @@ postbind f (Tabulation as) = Tabulation $ \i -> do
151infixr 1 `postbind`
203infixr 1 `postbind`
152
204
153
205
206+-- | Note that because 'Tabulation' is a @MultiMap@, the 'Query' returned by
207+-- 'lookup' can and often does contain multiple results.
154lookup :: EqTable k => k -> Tabulation k a -> Query a
208lookup :: EqTable k => k -> Tabulation k a -> Query a
155lookup key (Tabulation query) = do
209lookup key (Tabulation query) = do
156 (mk, a) <- query key
210 (mk, a) <- query key
@@ -158,11 +212,19 @@ lookup key (Tabulation query) = do
158 pure a
212 pure a
159
213
160
214
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@.
161align :: (EqTable k, Table Expr a, Table Expr b)
219align :: (EqTable k, Table Expr a, Table Expr b)
162 => Tabulation k a -> Tabulation k b -> Tabulation k (TheseTable a b)
220 => Tabulation k a -> Tabulation k b -> Tabulation k (TheseTable a b)
163align = alignWith id
221align = alignWith id
164
222
165
223
224+-- | Analagous to
225+-- [@alignWith@](https://hackage.haskell.org/package/semialign/docs/Data-Semialign.html#v:alignWith).
226+--
227+-- See 'zipWith' and 'align'.
166alignWith :: (EqTable k, Table Expr a, Table Expr b)
228alignWith :: (EqTable k, Table Expr a, Table Expr b)
167 => (TheseTable a b -> c)
229 => (TheseTable a b -> c)
168 -> Tabulation k a -> Tabulation k b -> Tabulation k c
230 -> Tabulation k a -> Tabulation k b -> Tabulation k c
@@ -177,27 +239,73 @@ alignWith f kas kbs = do
177 pure (k', f tab)
239 pure (k', f tab)
178
240
179
241
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).
180leftAlign :: EqTable k
249leftAlign :: EqTable k
181 => Tabulation k a -> Tabulation k b -> Tabulation k (a, MaybeTable b)
250 => Tabulation k a -> Tabulation k b -> Tabulation k (a, MaybeTable b)
182leftAlign = leftAlignWith (,)
251leftAlign = leftAlignWith (,)
183
252
184
253
254+-- | See 'zipWith' and 'leftAlign'.
255+--
256+-- Analagous to
257+-- [@rpadZipWith@](https://hackage.haskell.org/package/semialign/docs/Data-Semialign.html#v:rpadZipWith).
185leftAlignWith :: EqTable k
258leftAlignWith :: EqTable k
186 => (a -> MaybeTable b -> c)
259 => (a -> MaybeTable b -> c)
187 -> Tabulation k a -> Tabulation k b -> Tabulation k c
260 -> Tabulation k a -> Tabulation k b -> Tabulation k c
188leftAlignWith f left right = liftA2 f left (optionalTabulation right)
261leftAlignWith f left right = liftA2 f left (optionalTabulation right)
189
262
190
263
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.
191zip :: EqTable k
283zip :: EqTable k
192 => Tabulation k a -> Tabulation k b -> Tabulation k (a, b)
284 => Tabulation k a -> Tabulation k b -> Tabulation k (a, b)
193zip = zipWith (,)
285zip = zipWith (,)
194
286
195
287
288+-- | Analagous to
289+-- [@zipWith@](https://hackage.haskell.org/package/semialign/docs/Data-Semialign.html#v:zipWith).
290+--
291+-- See 'zip'.
196zipWith :: EqTable k
292zipWith :: EqTable k
197 => (a -> b -> c) -> Tabulation k a -> Tabulation k b -> Tabulation k c
293 => (a -> b -> c) -> Tabulation k a -> Tabulation k b -> Tabulation k c
198zipWith = liftA2
294zipWith = liftA2
199
295
200
296
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'.
201similarity :: EqTable k => Tabulation k a -> Tabulation k b -> Tabulation k a
309similarity :: EqTable k => Tabulation k a -> Tabulation k b -> Tabulation k a
202similarity kas kbs = do
310similarity kas kbs = do
203 as <- toQuery kas
311 as <- toQuery kas
@@ -205,6 +313,11 @@ similarity kas kbs = do
205 fromQuery $ as >>= withBy (\(k, _) (l, _) -> k ==: l) bs
313 fromQuery $ as >>= withBy (\(k, _) (l, _) -> k ==: l) bs
206
314
207
315
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'.
208difference :: EqTable k => Tabulation k a -> Tabulation k b -> Tabulation k a
321difference :: EqTable k => Tabulation k a -> Tabulation k b -> Tabulation k a
209difference kas kbs = do
322difference kas kbs = do
210 as <- toQuery kas
323 as <- toQuery kas
src/Rel8/Type/Array1D.hs+1
@@ -66,6 +66,7 @@ import Data.Semialign ( Semialign )
66import Data.Zip ( Repeat, Unzip, Zip )
66import Data.Zip ( Repeat, Unzip, Zip )
67
67
68
68
69+-- | A one dimensional array.
69newtype Array1D a = Array1D [a]
70newtype Array1D a = Array1D [a]
70 deriving stock Traversable
71 deriving stock Traversable
71 deriving newtype
72 deriving newtype
src/Rel8/Type/Num.hs+3
@@ -40,6 +40,9 @@ instance {-# INCOHERENT #-} (HasNullability a, DBNum (Unnullify a)) =>
40 Sql DBNum a
40 Sql DBNum a
41
41
42
42
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'.
43type DBIntegral :: Type -> Constraint
46type DBIntegral :: Type -> Constraint
44class DBNum a => DBIntegral a
47class DBNum a => DBIntegral a
45instance DBIntegral Int16
48instance DBIntegral Int16