on 29cdfdb help sign in
.github/workflows/build.yaml+3−3
since splitsnapshots
unifiedsplit
@@ -9,14 +9,14 @@ jobs:
9 persist-credentials: false
9 persist-credentials: false
10 submodules: true
10 submodules: true
11
11
12− - uses: cachix/install-nix-action@v14.1
12+ - uses: cachix/install-nix-action@v12
13 with:
13 with:
14 nix_path: nixpkgs=channel:nixos-unstable
14 nix_path: nixpkgs=channel:nixos-unstable
15 extra_nix_config: |
15 extra_nix_config: |
16 trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ=
16 trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ=
17− substituters = https://cache.nixos.org/ https://cache.iog.io
17+ substituters = https://cache.nixos.org/ https://hydra.iohk.io
18
18
19− - uses: cachix/cachix-action@v10
19+ - uses: cachix/cachix-action@v8
20 with:
20 with:
21 name: rel8
21 name: rel8
22 authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
22 authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
Changelog.md+2−90
@@ -1,90 +1,4 @@
1−# 1.4.0.0 (2002-08-17)
1+# 1.1.0.0 (??)
2−
3−## Breaking changes
4−
5−* The behavior of `greatest`/`least` has been corrected, and was previously flipped. ([#183](https://github.com/circuithub/rel8/pull/183))
6−
7−## New features
8−
9−* `NullTable`/`HNull` have been added. This is an alternative to `MaybeTable` that doesn't use a tag columns. It's less flexible (no `Functor` or `Applicative` instance) and is meaningless when used with a table that has no non-nullable columns (so nesting `NullTable` is redundant). But in situations where the underlying `Table` does have non-nullable columns, it can losslessly converted to and from `MaybeTable`. It is useful for embedding into a base table when you don't want to store the extra tag column in your schema. ([#173](https://github.com/circuithub/rel8/pull/173))
10−* Add `fromMaybeTable`. ([#179](https://github.com/circuithub/rel8/pull/179))
11−* Add `alignMaybeTable`. ([#196](https://github.com/circuithub/rel8/pull/196))
12−
13−## Improvements
14−
15−* Optimize implementation of `AltTable` for `Tabulation` ([#178](https://github.com/circuithub/rel8/pull/178))
16−
17−## Other
18−
19−* Documentation improvements for `HADT`. ([#177](https://github.com/circuithub/rel8/pull/177))
20−* Document example usage of `groupBy`. ([#184](https://github.com/circuithub/rel8/pull/184))
21−* Build with and require Opaleye >= 0.9.3.3. ([#190](https://github.com/circuithub/rel8/pull/190))
22−* Build with `hasql` 1.6. ([#195](https://github.com/circuithub/rel8/pull/195))
23−
24−# 1.3.1.0 (2022-01-20)
25−
26−## Other
27−
28−* Rel8 now requires Opaleye >= 0.9.1. ([#165](https://github.com/circuithub/rel8/pull/165))
29−
30−# 1.3.0.0 (2022-01-31)
31−
32−## Breaking changes
33−
34−* `div` and `mod` have been changed to match Haskell semantics. If you need the PostgreSQL `div()` and `mod()` functions, use `quot` and `rem`. While this is not an API change, we feel this is a breaking change in semantics and have bumped the major version number. ([#155](https://github.com/circuithub/rel8/pull/155))
35−
36−## New features
37−
38−* `divMod` and `quotRem` functions have been added, matching Haskell's `Prelude` functions. ([#155](https://github.com/circuithub/rel8/pull/155))
39−* `avg` and `mode` aggregation functions to find the mean value of an expression, or the most common row in a query, respectively. ([#152](https://github.com/circuithub/rel8/pull/152))
40−* The full `EqTable` and `OrdTable` classes have been exported, allowing for instances to be manually created. ([#157](https://github.com/circuithub/rel8/pull/157))
41−* Added `like` and `ilike` (for the `LIKE` and `ILIKE` operators). ([#146](https://github.com/circuithub/rel8/pull/146))
42−
43−## Other
44−
45−* Rel8 now requires Opaleye 0.9. ([#158](https://github.com/circuithub/rel8/pull/158))
46−* Rel8's test suite supports Hedgehog 1.1. ([#160](https://github.com/circuithub/rel8/pull/160))
47−* The documentation for binary operations has been corrected. ([#162](https://github.com/circuithub/rel8/pull/162))
48−
49−# 1.2.2.0 (2021-11-21)
50−
51−## Other
52−
53−* Support GHC 9.2 ([#145](https://github.com/circuithub/rel8/pull/145))
54−* Correct the documentation for `except` ([#147](https://github.com/circuithub/rel8/pull/147))
55−* Support `hasql` 1.5 ([#149](https://github.com/circuithub/rel8/pull/149))
56−
57−# 1.2.1.0 (2021-11-21)
58−
59−## New features
60−
61−* `castTable` has been added, which casts all columns selected from in `Query` according to the types of columns. This can occasionally be useful when using `many`/`some` on older PostgreSQL versions. ([#137](https://github.com/circuithub/rel8/pull/137))
62−
63−* Added `<:`, `<=:`, `>:`, `>=:`, `greatest` and `least`, which sort all columns in a table lexicographically. These operators are like the `<.` operators which operate on `Expr`s, but the `<:` operate on entire tables. ([#139](https://github.com/circuithub/rel8/pull/139))
64−
65−## Other
66−
67−* Support opaleye-0.8 ([#142](https://github.com/circuithub/rel8/pull/142))
68−
69−# 1.2.0.0 (2021-10-22)
70−
71−## New features
72−
73−* New `$*` and `$+` operators for projecting out of `ListTable` and `NonEmptyTable` respectively (analogous to the existing `$?` for `MaybeTable`). ([#125](https://github.com/circuithub/rel8/pull/125))
74−
75−## Bug fixes
76−
77−* Fix `UPSERT` support (and add tests), which was broken due to a typo inside Rel8 that made it impossible to construct `Projection`s which are needed by `UPSERT`. ([#134](https://github.com/circuithub/rel8/pull/134))
78−
79−* Remove `DBMin` and `DBMax` instances for `Bool`, which are invalid as far as Postgres is concerned. `and` and `or` can be used to achieve the same thing. ([#126](https://github.com/circuithub/rel8/pull/126))
80−
81−* Fix `aggregateMaybeTable`, `aggregateEitherTable` and `aggregateTheseTable`, which generated invalid SQL previously. ([#127](https://github.com/circuithub/rel8/pull/127))
82−
83−## Breaking changes
84−
85−* `rebind` now takes an additional argument for the "name" of the binding. ([#128](https://github.com/circuithub/rel8/pull/128))
86−
87−# 1.1.0.0 (2021-07-16)
88
2
89## New features
3## New features
90
4
@@ -96,7 +10,7 @@
96 , myTableY :: f Text
10 , myTableY :: f Text
97 } deriving (Generic, Rel8able)
11 } deriving (Generic, Rel8able)
98 ```
12 ```
99−
13+
100* The `Projectable` and `Biprojectable` type classes have been introduced. These classes model a type of functors and bifunctors (respectively) on tables, where the mapping function is only able to rearrange columns. For example, the following is now possible:
14* The `Projectable` and `Biprojectable` type classes have been introduced. These classes model a type of functors and bifunctors (respectively) on tables, where the mapping function is only able to rearrange columns. For example, the following is now possible:
101
15
102 ```haskell
16 ```haskell
@@ -113,8 +27,6 @@
113
27
114* The new `Rel8.Tabulate` module has been added, which gives a `Map`-esque interface to writing and composing queries. ([#70](https://github.com/circuithub/rel8/pull/70))
28* The new `Rel8.Tabulate` module has been added, which gives a `Map`-esque interface to writing and composing queries. ([#70](https://github.com/circuithub/rel8/pull/70))
115
29
116−* The new `indexed` `Query -> Query` function was added. This function augments each row in a query with it's 0-based index. ([#117](https://github.com/circuithub/rel8/pull/117))
117−
118## Breaking changes
30## Breaking changes
119
31
120* `Insert`, `Delete` and `Update` have all changed. In particular, for `Insert` users should now replace `rows = xs` with `rows = values xs`. ([#85](https://github.com/circuithub/rel8/pull/85))
32* `Insert`, `Delete` and `Update` have all changed. In particular, for `Insert` users should now replace `rows = xs` with `rows = values xs`. ([#85](https://github.com/circuithub/rel8/pull/85))
cabal.project+5
@@ -1 +1,6 @@
1packages: .
1packages: .
2+
3+allow-newer: generic-monoid:base
4+allow-newer: cryptohash-sha1:base
5+allow-newer: cryptohash-md5:base
6+allow-newer: entropy:Cabal
cabal.project.haskell-nix+6
@@ -2,3 +2,9 @@
2-- will interpret them as local packages, and try to build them when we cabal
2-- will interpret them as local packages, and try to build them when we cabal
3-- build. The only reason we have to specify these is for Haskell.nix to know to
3-- build. The only reason we have to specify these is for Haskell.nix to know to
4-- override these packages by fetching them rather than using Hackage.
4-- override these packages by fetching them rather than using Hackage.
5+
6+source-repository-package
7+ type: git
8+ location: git://github.com/jcpetruzza/barbies
9+ tag: f99b05454874192e3511bd133555dfb6cc6a6ecb
10+ --sha256: 0yy2i2jbllwavv5d2176rf8lmm4l1ws90lxkmdlfgvfzqxidx0gi
default.nix+2−2
@@ -1,11 +1,11 @@
1let
1let
2 haskellNix = import (import ./nix/sources.nix)."haskell.nix" {};
2 haskellNix = import (import ./nix/sources.nix)."haskell.nix" {};
3
3
4− nixpkgsSrc = haskellNix.sources.nixpkgs-unstable;
4+ nixpkgsSrc = haskellNix.sources.nixpkgs-2009;
5
5
6 nixpkgsArgs = haskellNix.nixpkgsArgs;
6 nixpkgsArgs = haskellNix.nixpkgsArgs;
7
7
8− compiler-nix-name = "ghc924";
8+ compiler-nix-name = "ghc901";
9
9
10 pkgs = import nixpkgsSrc nixpkgsArgs;
10 pkgs = import nixpkgsSrc nixpkgsArgs;
11
11
docs/cookbook.rst+7−9
@@ -142,25 +142,23 @@ rows, but can also return *tree*\s.
142To understand what this means, we'll consider a small example query for blog
142To understand what this means, we'll consider a small example query for blog
143posts. We want our query to return:
143posts. We want our query to return:
144
144
145−1. The latest 5 blog posts that have at least one tag each.
145+1. The latest 5 blog posts.
1462. For each blog post, all tags.
1462. For each blog post, all tags.
147−3. For each blog post, the latest 3 comments if they exist.
147+3. For each blog post, the latest 3 comments.
148
148
149In Rel8, we can write this query as::
149In Rel8, we can write this query as::
150
150
151 latestBlogPosts = do
151 latestBlogPosts = do
152 post <- each postSchema
152 post <- each postSchema
153
153
154− -- Returns a `NonEmptyTable a` which ends up as a `Data.List.NonEmpty a` after the query is run
155− tags <- some $ do
154+ tags <- aggregate do
156 tag <- each tagSchema
155 tag <- each tagSchema
157 where_ (tagPostId tag ==. postId post)
156 where_ (tagPostId tag ==. postId post)
158− return (tagName tag)
157+ return (listAgg (tagName tag))
159
158
160− -- Returns a `ListTable a` which ends up as a `[a]` after the query is run
161− latestComments <-
159+ latestComments <-
162− many $
160+ many $
163− limit 3 $
161+ limit 3 $
164 orderBy (commentCreatedAt >$< desc) do
162 orderBy (commentCreatedAt >$< desc) do
165 comment <- each commentSchema
163 comment <- each commentSchema
166 where_ (commentPostId comment ==. postId post)
164 where_ (commentPostId comment ==. postId post)
nix/sources.json+9−9
@@ -5,10 +5,10 @@
5 "homepage": "https://input-output-hk.github.io/haskell.nix",
5 "homepage": "https://input-output-hk.github.io/haskell.nix",
6 "owner": "input-output-hk",
6 "owner": "input-output-hk",
7 "repo": "haskell.nix",
7 "repo": "haskell.nix",
8− "rev": "0837156c56cb5350dd9bd84eb424187e00aa1e8a",
8+ "rev": "3856d2d24dca0ecc71fcfc314253a2a2d07a3c4f",
9− "sha256": "1djmv5a0vfj78csw2cad6nicrs8iqrp928b9b3v4h7lwwhhzliwl",
9+ "sha256": "0s69nasqhkv8n0qmdv075k83p3z584ja9vkzdnp2291mymghvx87",
10 "type": "tarball",
10 "type": "tarball",
11− "url": "https://github.com/input-output-hk/haskell.nix/archive/0837156c56cb5350dd9bd84eb424187e00aa1e8a.tar.gz",
11+ "url": "https://github.com/input-output-hk/haskell.nix/archive/3856d2d24dca0ecc71fcfc314253a2a2d07a3c4f.tar.gz",
12 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
12 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
13 },
13 },
14 "niv": {
14 "niv": {
@@ -17,10 +17,10 @@
17 "homepage": "https://github.com/nmattia/niv",
17 "homepage": "https://github.com/nmattia/niv",
18 "owner": "nmattia",
18 "owner": "nmattia",
19 "repo": "niv",
19 "repo": "niv",
20− "rev": "acdb1726783637852c01e0ad639ac9cbec28d8c8",
20+ "rev": "e0ca65c81a2d7a4d82a189f1e23a48d59ad42070",
21− "sha256": "1wszl8q70z6wfgkh2dvgmhhry6qp9nar421qq2ifd17hq2jj9ck3",
21+ "sha256": "1pq9nh1d8nn3xvbdny8fafzw87mj7gsmp6pxkdl65w2g18rmcmzx",
22 "type": "tarball",
22 "type": "tarball",
23− "url": "https://github.com/nmattia/niv/archive/acdb1726783637852c01e0ad639ac9cbec28d8c8.tar.gz",
23+ "url": "https://github.com/nmattia/niv/archive/e0ca65c81a2d7a4d82a189f1e23a48d59ad42070.tar.gz",
24 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
24 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
25 },
25 },
26 "nixpkgs": {
26 "nixpkgs": {
@@ -29,10 +29,10 @@
29 "homepage": null,
29 "homepage": null,
30 "owner": "nixos",
30 "owner": "nixos",
31 "repo": "nixpkgs",
31 "repo": "nixpkgs",
32− "rev": "1c1f5649bb9c1b0d98637c8c365228f57126f361",
32+ "rev": "59b8d9cf24e9fcf10341a0923c9bdca088dca8c8",
33− "sha256": "0f2nvdijyxfgl5kwyb4465pppd5vkhqxddx6v40k2s0z9jfhj0xl",
33+ "sha256": "08f38v4b2kcxnbapdwrb54bglka92cxj9qlnqlk5px206jyq9v4c",
34 "type": "tarball",
34 "type": "tarball",
35− "url": "https://github.com/nixos/nixpkgs/archive/1c1f5649bb9c1b0d98637c8c365228f57126f361.tar.gz",
35+ "url": "https://github.com/nixos/nixpkgs/archive/59b8d9cf24e9fcf10341a0923c9bdca088dca8c8.tar.gz",
36 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
36 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
37 }
37 }
38}
38}
nix/sources.nix+1−21
@@ -31,28 +31,8 @@ let
31 if spec ? branch then "refs/heads/${spec.branch}" else
31 if spec ? branch then "refs/heads/${spec.branch}" else
32 if spec ? tag then "refs/tags/${spec.tag}" else
32 if spec ? tag then "refs/tags/${spec.tag}" else
33 abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!";
33 abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!";
34− submodules = if spec ? submodules then spec.submodules else false;
35− submoduleArg =
36− let
37− nixSupportsSubmodules = builtins.compareVersions builtins.nixVersion "2.4" >= 0;
38− emptyArgWithWarning =
39− if submodules == true
40− then
41− builtins.trace
42− (
43− "The niv input \"${name}\" uses submodules "
44− + "but your nix's (${builtins.nixVersion}) builtins.fetchGit "
45− + "does not support them"
46− )
47− {}
48− else {};
49− in
50− if nixSupportsSubmodules
51− then { inherit submodules; }
52− else emptyArgWithWarning;
53 in
34 in
54− builtins.fetchGit
55− ({ url = spec.repo; inherit (spec) rev; inherit ref; } // submoduleArg);
35+ builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; };
56
36
57 fetch_local = spec: spec.path;
37 fetch_local = spec: spec.path;
58
38
rel8.cabal+5−16
@@ -1,6 +1,6 @@
1cabal-version: 2.0
1cabal-version: 2.0
2name: rel8
2name: rel8
3−version: 1.4.0.0
3+version: 1.0.0.1
4synopsis: Hey! Hey! Can u rel8?
4synopsis: Hey! Hey! Can u rel8?
5license: BSD3
5license: BSD3
6license-file: LICENSE
6license-file: LICENSE
@@ -20,14 +20,14 @@ source-repository head
20library
20library
21 build-depends:
21 build-depends:
22 aeson
22 aeson
23− , base ^>= 4.14 || ^>=4.15 || ^>=4.16
23+ , base ^>= 4.14 || ^>=4.15
24 , bifunctors
24 , bifunctors
25 , bytestring
25 , bytestring
26 , case-insensitive
26 , case-insensitive
27 , comonad
27 , comonad
28 , contravariant
28 , contravariant
29− , hasql ^>= 1.4.5.1 || ^>= 1.5.0.0 || ^>= 1.6.0.0
29+ , hasql ^>= 1.4.5.1
30− , opaleye ^>= 0.9.6.1
30+ , opaleye ^>= 0.7.3.0
31 , pretty
31 , pretty
32 , profunctors
32 , profunctors
33 , product-profunctors
33 , product-profunctors
@@ -46,7 +46,6 @@ library
46 -Wno-missing-import-lists -Wno-prepositive-qualified-module
46 -Wno-missing-import-lists -Wno-prepositive-qualified-module
47 -Wno-monomorphism-restriction
47 -Wno-monomorphism-restriction
48 -Wno-missing-local-signatures
48 -Wno-missing-local-signatures
49− -Wno-missing-kind-signatures
50 hs-source-dirs:
49 hs-source-dirs:
51 src
50 src
52 exposed-modules:
51 exposed-modules:
@@ -66,7 +65,6 @@ library
66 Rel8.Column.List
65 Rel8.Column.List
67 Rel8.Column.Maybe
66 Rel8.Column.Maybe
68 Rel8.Column.NonEmpty
67 Rel8.Column.NonEmpty
69− Rel8.Column.Null
70 Rel8.Column.These
68 Rel8.Column.These
71
69
72 Rel8.Expr
70 Rel8.Expr
@@ -82,7 +80,6 @@ library
82 Rel8.Expr.Order
80 Rel8.Expr.Order
83 Rel8.Expr.Sequence
81 Rel8.Expr.Sequence
84 Rel8.Expr.Serialize
82 Rel8.Expr.Serialize
85− Rel8.Expr.Window
86
83
87 Rel8.FCF
84 Rel8.FCF
88
85
@@ -109,19 +106,16 @@ library
109 Rel8.Query.Evaluate
106 Rel8.Query.Evaluate
110 Rel8.Query.Exists
107 Rel8.Query.Exists
111 Rel8.Query.Filter
108 Rel8.Query.Filter
112− Rel8.Query.Indexed
113 Rel8.Query.Limit
109 Rel8.Query.Limit
114 Rel8.Query.List
110 Rel8.Query.List
115 Rel8.Query.Maybe
111 Rel8.Query.Maybe
116 Rel8.Query.Null
112 Rel8.Query.Null
117 Rel8.Query.Opaleye
113 Rel8.Query.Opaleye
118 Rel8.Query.Order
114 Rel8.Query.Order
119− Rel8.Query.Rebind
120 Rel8.Query.Set
115 Rel8.Query.Set
121 Rel8.Query.SQL
116 Rel8.Query.SQL
122 Rel8.Query.These
117 Rel8.Query.These
123 Rel8.Query.Values
118 Rel8.Query.Values
124− Rel8.Query.Window
125
119
126 Rel8.Schema.Context.Nullify
120 Rel8.Schema.Context.Nullify
127 Rel8.Schema.Dict
121 Rel8.Schema.Dict
@@ -170,7 +164,6 @@ library
170 Rel8.Table.Maybe
164 Rel8.Table.Maybe
171 Rel8.Table.Name
165 Rel8.Table.Name
172 Rel8.Table.NonEmpty
166 Rel8.Table.NonEmpty
173− Rel8.Table.Null
174 Rel8.Table.Nullify
167 Rel8.Table.Nullify
175 Rel8.Table.Opaleye
168 Rel8.Table.Opaleye
176 Rel8.Table.Ord
169 Rel8.Table.Ord
@@ -181,7 +174,6 @@ library
181 Rel8.Table.These
174 Rel8.Table.These
182 Rel8.Table.Transpose
175 Rel8.Table.Transpose
183 Rel8.Table.Undefined
176 Rel8.Table.Undefined
184− Rel8.Table.Window
185
177
186 Rel8.Type
178 Rel8.Type
187 Rel8.Type.Array
179 Rel8.Type.Array
@@ -200,9 +192,6 @@ library
200 Rel8.Type.Sum
192 Rel8.Type.Sum
201 Rel8.Type.Tag
193 Rel8.Type.Tag
202
194
203− Rel8.Window
204−
205−
206test-suite tests
195test-suite tests
207 type: exitcode-stdio-1.0
196 type: exitcode-stdio-1.0
208 build-depends:
197 build-depends:
@@ -212,7 +201,7 @@ test-suite tests
212 , containers
201 , containers
213 , hasql
202 , hasql
214 , hasql-transaction
203 , hasql-transaction
215− , hedgehog ^>= 1.0 || ^>= 1.1
204+ , hedgehog ^>=1.0.2
216 , mmorph
205 , mmorph
217 , rel8
206 , rel8
218 , scientific
207 , scientific
shell.nix+1−1
@@ -1,6 +1,6 @@
1let
1let
2 sources = import ./nix/sources.nix;
2 sources = import ./nix/sources.nix;
3− pkgs = import sources.nixpkgs {overlays = [];};
3+ pkgs = import sources.nixpkgs {};
4 hsPkgs = import ./default.nix;
4 hsPkgs = import ./default.nix;
5in
5in
6 hsPkgs.shellFor {
6 hsPkgs.shellFor {
src/Rel8.hs+8−277
@@ -38,7 +38,6 @@ module Rel8
38 , HMaybe
38 , HMaybe
39 , HList
39 , HList
40 , HNonEmpty
40 , HNonEmpty
41− , HNull
42 , HThese
41 , HThese
43 , Lift
42 , Lift
44
43
@@ -47,18 +46,16 @@ module Rel8
47 , Transposes
46 , Transposes
48 , AltTable((<|>:))
47 , AltTable((<|>:))
49 , AlternativeTable( emptyTable )
48 , AlternativeTable( emptyTable )
50− , EqTable(..), (==:), (/=:)
49+ , EqTable, (==:), (/=:)
51− , OrdTable(..), (<:), (<=:), (>:), (>=:), ascTable, descTable, greatest, least
50+ , OrdTable, ascTable, descTable
52 , lit
51 , lit
53 , bool
52 , bool
54 , case_
53 , case_
55− , castTable
56
54
57 -- ** @MaybeTable@
55 -- ** @MaybeTable@
58 , MaybeTable
56 , MaybeTable
59 , maybeTable, ($?), nothingTable, justTable
57 , maybeTable, ($?), nothingTable, justTable
60 , isNothingTable, isJustTable
58 , isNothingTable, isJustTable
61− , fromMaybeTable
62 , optional
59 , optional
63 , catMaybeTable
60 , catMaybeTable
64 , traverseMaybeTable
61 , traverseMaybeTable
@@ -81,7 +78,6 @@ module Rel8
81 , isThisTable, isThatTable, isThoseTable
78 , isThisTable, isThatTable, isThoseTable
82 , hasHereTable, hasThereTable
79 , hasHereTable, hasThereTable
83 , justHereTable, justThereTable
80 , justHereTable, justThereTable
84− , alignMaybeTable
85 , alignBy
81 , alignBy
86 , keepHereTable, loseHereTable
82 , keepHereTable, loseHereTable
87 , keepThereTable, loseThereTable
83 , keepThereTable, loseThereTable
@@ -94,7 +90,7 @@ module Rel8
94
90
95 -- ** @ListTable@
91 -- ** @ListTable@
96 , ListTable
92 , ListTable
97− , listTable, ($*)
93+ , listTable
98 , nameListTable
94 , nameListTable
99 , many
95 , many
100 , manyExpr
96 , manyExpr
@@ -103,44 +99,21 @@ module Rel8
103
99
104 -- ** @NonEmptyTable@
100 -- ** @NonEmptyTable@
105 , NonEmptyTable
101 , NonEmptyTable
106− , nonEmptyTable, ($+)
102+ , nonEmptyTable
107 , nameNonEmptyTable
103 , nameNonEmptyTable
108 , some
104 , some
109 , someExpr
105 , someExpr
110 , catNonEmptyTable
106 , catNonEmptyTable
111 , catNonEmpty
107 , catNonEmpty
112
108
113− -- ** @NullTable@
109+ -- ** @ADT@
114− , NullTable
115− , nullableTable, nullTable, nullifyTable
116− , isNullTable, isNonNullTable
117− , catNullTable
118− , nameNullTable
119− , toNullTable, toMaybeTable
120−
121− -- ** Algebraic data types / sum types
122− -- $adts
123−
124− -- *** Naming of ADTs
125− -- $naming
126− , NameADT, nameADT
127 , ADT, ADTable
110 , ADT, ADTable
128−
129− -- *** Deconstruction of ADTs
130− -- $deconstruction
131− , DeconstructADT, deconstructADT
132−
133− -- *** Construction of ADTs
134− -- $construction
135 , BuildADT, buildADT
111 , BuildADT, buildADT
136 , ConstructADT, constructADT
112 , ConstructADT, constructADT
137−
113+ , DeconstructADT, deconstructADT
138− -- *** Other ADT operations
114+ , NameADT, nameADT
139 , AggregateADT, aggregateADT
115 , AggregateADT, aggregateADT
140
116
141− -- *** Miscellaneous notes
142− -- $misc-notes
143−
144 -- ** @HKD@
117 -- ** @HKD@
145 , HKD, HKDable
118 , HKD, HKDable
146 , BuildHKD, buildHKD
119 , BuildHKD, buildHKD
@@ -165,7 +138,6 @@ module Rel8
165 -- ** @null@
138 -- ** @null@
166 , NotNull
139 , NotNull
167 , Nullable
140 , Nullable
168− , Homonullable
169 , null
141 , null
170 , nullify
142 , nullify
171 , nullable
143 , nullable
@@ -184,7 +156,6 @@ module Rel8
184 , (==.), (/=.), (==?), (/=?)
156 , (==.), (/=.), (==?), (/=?)
185 , in_
157 , in_
186 , boolExpr, caseExpr
158 , boolExpr, caseExpr
187− , like, ilike
188
159
189 -- ** Ordering
160 -- ** Ordering
190 , DBOrd
161 , DBOrd
@@ -253,11 +224,10 @@ module Rel8
253 , countRows
224 , countRows
254 , groupBy
225 , groupBy
255 , listAgg, listAggExpr
226 , listAgg, listAggExpr
256− , mode
257 , nonEmptyAgg, nonEmptyAggExpr
227 , nonEmptyAgg, nonEmptyAggExpr
258 , DBMax, max
228 , DBMax, max
259 , DBMin, min
229 , DBMin, min
260− , DBSum, sum, sumWhere, avg
230+ , DBSum, sum, sumWhere
261 , DBString, stringAgg
231 , DBString, stringAgg
262 , count
232 , count
263 , countStar
233 , countStar
@@ -274,29 +244,6 @@ module Rel8
274 , nullsFirst
244 , nullsFirst
275 , nullsLast
245 , nullsLast
276
246
277− -- ** Window functions
278− , Window
279− , window
280− , Partition
281− , over
282− , partitionBy
283− , orderPartitionBy
284− , cumulative
285− , cumulative_
286− , currentRow
287− , rowNumber
288− , rank
289− , denseRank
290− , percentRank
291− , cumeDist
292− , ntile
293− , lag
294− , lead
295− , firstValue
296− , lastValue
297− , nthValue
298− , indexed
299−
300 -- ** Bindings
247 -- ** Bindings
301 , rebind
248 , rebind
302
249
@@ -334,7 +281,6 @@ module Rel8
334
281
335 -- ** @CREATE VIEW@
282 -- ** @CREATE VIEW@
336 , createView
283 , createView
337− , createOrReplaceView
338
284
339 -- ** Sequences
285 -- ** Sequences
340 , nextval
286 , nextval
@@ -353,7 +299,6 @@ import Rel8.Column.Lift
353import Rel8.Column.List
299import Rel8.Column.List
354import Rel8.Column.Maybe
300import Rel8.Column.Maybe
355import Rel8.Column.NonEmpty
301import Rel8.Column.NonEmpty
356−import Rel8.Column.Null
357import Rel8.Column.These
302import Rel8.Column.These
358import Rel8.Expr
303import Rel8.Expr
359import Rel8.Expr.Aggregate
304import Rel8.Expr.Aggregate
@@ -367,8 +312,6 @@ import Rel8.Expr.Ord
367import Rel8.Expr.Order
312import Rel8.Expr.Order
368import Rel8.Expr.Serialize
313import Rel8.Expr.Serialize
369import Rel8.Expr.Sequence
314import Rel8.Expr.Sequence
370−import Rel8.Expr.Text ( like, ilike )
371−import Rel8.Expr.Window hiding ( cumulative )
372import Rel8.Generic.Rel8able ( KRel8able, Rel8able )
315import Rel8.Generic.Rel8able ( KRel8able, Rel8able )
373import Rel8.Order
316import Rel8.Order
374import Rel8.Query
317import Rel8.Query
@@ -379,18 +322,15 @@ import Rel8.Query.Either
379import Rel8.Query.Evaluate
322import Rel8.Query.Evaluate
380import Rel8.Query.Exists
323import Rel8.Query.Exists
381import Rel8.Query.Filter
324import Rel8.Query.Filter
382−import Rel8.Query.Indexed
383import Rel8.Query.Limit
325import Rel8.Query.Limit
384import Rel8.Query.List
326import Rel8.Query.List
385import Rel8.Query.Maybe
327import Rel8.Query.Maybe
386import Rel8.Query.Null
328import Rel8.Query.Null
387import Rel8.Query.Order
329import Rel8.Query.Order
388−import Rel8.Query.Rebind
389import Rel8.Query.SQL (showQuery)
330import Rel8.Query.SQL (showQuery)
390import Rel8.Query.Set
331import Rel8.Query.Set
391import Rel8.Query.These
332import Rel8.Query.These
392import Rel8.Query.Values
333import Rel8.Query.Values
393−import Rel8.Query.Window
394import Rel8.Schema.Field
334import Rel8.Schema.Field
395import Rel8.Schema.HTable
335import Rel8.Schema.HTable
396import Rel8.Schema.Name
336import Rel8.Schema.Name
@@ -417,8 +357,6 @@ import Rel8.Table.List
417import Rel8.Table.Maybe
357import Rel8.Table.Maybe
418import Rel8.Table.Name
358import Rel8.Table.Name
419import Rel8.Table.NonEmpty
359import Rel8.Table.NonEmpty
420−import Rel8.Table.Null
421−import Rel8.Table.Opaleye ( castTable )
422import Rel8.Table.Ord
360import Rel8.Table.Ord
423import Rel8.Table.Order
361import Rel8.Table.Order
424import Rel8.Table.Projection
362import Rel8.Table.Projection
@@ -426,7 +364,6 @@ import Rel8.Table.Rel8able ()
426import Rel8.Table.Serialize
364import Rel8.Table.Serialize
427import Rel8.Table.These
365import Rel8.Table.These
428import Rel8.Table.Transpose
366import Rel8.Table.Transpose
429−import Rel8.Table.Window
430import Rel8.Type
367import Rel8.Type
431import Rel8.Type.Composite
368import Rel8.Type.Composite
432import Rel8.Type.Eq
369import Rel8.Type.Eq
@@ -441,7 +378,6 @@ import Rel8.Type.ReadShow
441import Rel8.Type.Semigroup
378import Rel8.Type.Semigroup
442import Rel8.Type.String
379import Rel8.Type.String
443import Rel8.Type.Sum
380import Rel8.Type.Sum
444−import Rel8.Window
445
381
446
382
447-- $running
383-- $running
@@ -449,208 +385,3 @@ import Rel8.Window
449-- provides 'select', 'insert', 'update' and 'delete' functions. Note that
385-- provides 'select', 'insert', 'update' and 'delete' functions. Note that
450-- 'insert', 'update' and 'delete' will generally need the
386-- 'insert', 'update' and 'delete' will generally need the
451-- `DuplicateRecordFields` language extension enabled.
387-- `DuplicateRecordFields` language extension enabled.
452−
453−-- $adts
454−-- Algebraic data types can be modelled between Haskell and SQL.
455−--
456−-- * Your SQL table needs a certain text field that tags which Haskell constructor is in use.
457−-- * You have to use a few combinators to specify the sum type's individual constructors.
458−-- * If you want to do case analysis at the @Expr@ (SQL) level, you can use 'maybe'/'either'-like eliminators.
459−--
460−-- The documentation in this section will assume a set of database types like this:
461−--
462−-- @
463−-- data Thing f = ThingEmployer (Employer f) | ThingPotato (Potato f) | Nullary
464−-- deriving stock Generic
465−--
466−-- data Employer f = Employer { employerId :: f Int32, employerName :: f Text}
467−-- deriving stock Generic
468−-- deriving anyclass Rel8able
469−--
470−-- data Potato f = Potato { size :: f Int32, grower :: f Text }
471−-- deriving stock Generic
472−-- deriving anyclass Rel8able
473−-- @
474−
475−-- $naming
476−--
477−-- First, in your 'TableSchema', name your type like this:
478−--
479−-- @
480−-- thingSchema :: TableSchema (ADT Thing Name)
481−-- thingSchema =
482−-- TableSchema
483−-- { schema = Nothing,
484−-- name = \"thing\",
485−-- columns =
486−-- nameADT @Thing
487−-- \"tag\"
488−-- Employer
489−-- { employerName = \"name\",
490−-- employerId = \"id\"
491−-- }
492−-- Potato {size = \"size\", grower = \"Mary\"}
493−-- }
494−-- @
495−--
496−-- Note that @nameADT \@Thing "tag"@ is variadic: it accepts one
497−-- argument per constructor, except the nullary ones (Nullary) because
498−-- there's nothing to do for them.
499−
500−-- $deconstruction
501−--
502−-- To deconstruct sum types at the SQL level, use 'deconstructADT',
503−-- which is also variadic, and has one argument for each
504−-- constructor. Similar to 'maybe'.
505−--
506−-- @
507−-- query :: Query (ADT Thing Expr)
508−-- query = do
509−-- thingExpr <- each thingSchema
510−-- where_ $
511−-- deconstructADT @Thing
512−-- (\employer -> employerName employer ==. lit \"Mary\")
513−-- (\potato -> grower potato ==. lit \"Mary\")
514−-- (lit False) -- Nullary case
515−-- thingExpr
516−-- pure thingExpr
517−-- @
518−--
519−-- SQL output:
520−--
521−-- @
522−-- SELECT
523−-- CAST("tag0_1" AS text) as "tag",
524−-- CAST("id1_1" AS int4) as "ThingEmployer/_1/employerId",
525−-- CAST("name2_1" AS text) as "ThingEmployer/_1/employerName",
526−-- CAST("size3_1" AS int4) as "ThingPotato/_1/size",
527−-- CAST("Mary4_1" AS text) as "ThingPotato/_1/grower"
528−-- FROM (SELECT
529−-- *
530−-- FROM (SELECT
531−-- "tag" as "tag0_1",
532−-- "id" as "id1_1",
533−-- "name" as "name2_1",
534−-- "size" as "size3_1",
535−-- "Mary" as "Mary4_1"
536−-- FROM "thing" as "T1") as "T1"
537−-- WHERE (CASE WHEN ("tag0_1") = (CAST(E'ThingPotato' AS text)) THEN ("Mary4_1") = (CAST(E'Mary' AS text))
538−-- WHEN ("tag0_1") = (CAST(E'Nullary' AS text)) THEN CAST(FALSE AS bool) ELSE ("name2_1") = (CAST(E'Mary' AS text)) END)) as "T1"
539−-- @
540−
541−-- $construction
542−--
543−-- To construct an ADT, you can use 'buildADT' or 'constructADT'. Consider the following type:
544−--
545−-- @
546−-- data Task f = Pending | Complete (CompletedTask f)
547−-- @
548−--
549−-- 'buildADT' is for constructing values of 'Task' in the 'Expr'
550−-- context. 'buildADT' needs two type-level arguments before its type
551−-- makes any sense. The first argument is the type of the "ADT", which
552−-- in our case is 'Task'. The second is the name of the constructor we
553−-- want to use. So that means we have the following possible
554−-- instantiations of 'buildADT' for 'Task':
555−--
556−-- @
557−-- > :t buildADT @Task @\"Pending\"
558−-- buildADT @Task @\"Pending\" :: ADT Task Expr
559−-- > :t buildADT @Task @\"Complete\"
560−-- buildADT @Task @\"Complete\" :: CompletedTask Expr -> ADT Task Expr
561−-- @
562−--
563−-- Note that as the "Pending" constructor has no fields, @buildADT
564−-- \@Task \@"Pending"@ is equivalent to @lit Pending@. But @buildADT
565−-- \@Task \@"Complete"@ is not the same as @lit . Complete@:
566−--
567−-- @
568−-- > :t lit . Complete
569−-- lit . Complete :: CompletedTask Result -> ADT Task Expr
570−-- @
571−--
572−--
573−-- Note that the former takes a @CompletedTask Expr@ while the latter
574−-- takes a @CompletedTask Result@. The former is more powerful because
575−-- you can construct @Task@s using dynamic values coming a database
576−-- query.
577−--
578−-- To show what this can look like in SQL, consider:
579−--
580−-- @
581−-- > :{
582−-- showQuery $ values
583−-- [ buildADT @Task @\"Pending\"
584−-- , buildADT @Task @\"Complete\" CompletedTask {date = Rel8.Expr.Time.now}
585−-- ]
586−-- :}
587−-- @
588−--
589−-- This produces the following SQL:
590−--
591−-- @
592−-- SELECT
593−-- CAST(\"values0_1\" AS text) as \"tag\",
594−-- CAST(\"values1_1\" AS timestamptz) as \"Complete/_1/date\"
595−-- FROM (SELECT
596−-- *
597−-- FROM (SELECT \"column1\" as \"values0_1\",
598−-- \"column2\" as \"values1_1\"
599−-- FROM
600−-- (VALUES
601−-- (CAST(E'Pending' AS text),CAST(NULL AS timestamptz)),
602−-- (CAST(E'Complete' AS text),CAST(now() AS timestamptz))) as \"V\") as \"T1\") as \"T1\"
603−-- @
604−--
605−-- This is what you get if you run it in @psql@:
606−--
607−--
608−-- @
609−-- tag | Complete/_1/date
610−-- ----------+-------------------------------
611−-- Pending |
612−-- Complete | 2022-05-19 21:28:23.969065+00
613−-- (2 rows)
614−-- @
615−--
616−-- "constructADT" is less convenient but more general alternative to
617−-- "buildADT". It requires only one type-level argument for its type
618−-- to make sense:
619−--
620−-- @
621−-- > :t constructADT @Task
622−-- constructADT @Task
623−-- :: (forall r. r -> (CompletedTask Expr -> r) -> r) -> ADT Task Expr
624−-- @
625−--
626−-- This might still seem a bit opaque, but basically it gives you a
627−-- Church-encoded constructor for arbitrary algebraic data types. You
628−-- might use it as follows:
629−--
630−-- @
631−-- let
632−-- pending :: ADT Task Expr
633−-- pending = constructADT @Task $ \pending _complete -> pending
634−--
635−-- complete :: ADT Task Expr
636−-- complete = constructADT @Task $ \_pending complete -> complete CompletedTask {date = Rel8.Expr.Time.now}
637−-- @
638−--
639−-- These values are otherwise identical to the ones we saw above with
640−-- @buildADT@, it's just a different style of constructing them.
641−--
642−
643−-- $misc-notes
644−--
645−-- 1. Note that the order of the arguments for all of these functions
646−-- is determined by the order of the constructors in the data
647−-- definition. If it were @data Task = Complete (CompletedTask f) |
648−-- Pending@ then the order of all the invocations of @constructADT@
649−-- and @deconstructADT@ would need to change.
650−--
651−-- 2. Maybe this is obvious, but just to spell it out: once you're in
652−-- the @Result@ context, you can of course construct @Task@ values
653−-- normally and use standard Haskell pattern-matching. @constructADT@
654−-- and @deconstructADT@ are specifically only needed in the @Expr@
655−-- context, and they allow you to do the equivalent of pattern
656−-- matching in PostgreSQL.
src/Rel8/Aggregate.hs+43−19
@@ -2,31 +2,29 @@
2{-# language FlexibleContexts #-}
2{-# language FlexibleContexts #-}
3{-# language FlexibleInstances #-}
3{-# language FlexibleInstances #-}
4{-# language MultiParamTypeClasses #-}
4{-# language MultiParamTypeClasses #-}
5+{-# language NamedFieldPuns #-}
5{-# language RankNTypes #-}
6{-# language RankNTypes #-}
6{-# language StandaloneKindSignatures #-}
7{-# language StandaloneKindSignatures #-}
7{-# language TypeFamilies #-}
8{-# language TypeFamilies #-}
8{-# language UndecidableInstances #-}
9{-# language UndecidableInstances #-}
9
10
10module Rel8.Aggregate
11module Rel8.Aggregate
11− ( Aggregate(..), zipOutputs
12+ ( Aggregate(..), foldInputs, mapInputs
12− , unsafeMakeAggregate
13+ , Aggregator(..), unsafeMakeAggregate
13 , Aggregates
14 , Aggregates
14 )
15 )
15where
16where
16
17
17-- base
18-- base
18−import Control.Applicative ( liftA2 )
19+import Data.Functor.Const ( Const( Const ), getConst )
19import Data.Functor.Identity ( Identity( Identity ) )
20import Data.Functor.Identity ( Identity( Identity ) )
20import Data.Kind ( Constraint, Type )
21import Data.Kind ( Constraint, Type )
21import Prelude
22import Prelude
22
23
23−-- profunctors
24−import Data.Profunctor ( dimap )
25−
26-- opaleye
24-- opaleye
27−import qualified Opaleye.Aggregate as Opaleye
25+import qualified Opaleye.Internal.Aggregate as Opaleye
28import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
26import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
29−import qualified Opaleye.Internal.Column as Opaleye
27+import qualified Opaleye.Internal.PackMap as Opaleye
30
28
31-- rel8
29-- rel8
32import Rel8.Expr ( Expr )
30import Rel8.Expr ( Expr )
@@ -42,7 +40,11 @@ import Rel8.Table.Transpose ( Transposes )
42import Rel8.Type ( DBType )
40import Rel8.Type ( DBType )
43
41
44
42
45−-- | 'Aggregate' is a special context used by 'Rel8.aggregate'.
43+-- | An @Aggregate a@ describes how to aggregate @Table@s of type @a@. You can
44+-- unpack an @Aggregate@ back to @a@ by running it with 'Rel8.aggregate'. As
45+-- @Aggregate@ is almost an 'Applicative' functor - but there is no 'pure'
46+-- operation. This means 'Aggregate' is an instance of 'Apply', and you can
47+-- combine @Aggregate@s using the @<.>@ combinator.
46type Aggregate :: K.Context
48type Aggregate :: K.Context
47newtype Aggregate a = Aggregate (Opaleye.Aggregator () (Expr a))
49newtype Aggregate a = Aggregate (Opaleye.Aggregator () (Expr a))
48
50
@@ -59,25 +61,47 @@ instance Sql DBType a => Table Aggregate (Aggregate a) where
59 fromResult (HIdentity (Identity a)) = a
61 fromResult (HIdentity (Identity a)) = a
60
62
61
63
62−-- | @Aggregates a b@ means that the columns in @a@ are all 'Aggregate's
64+-- | @Aggregates a b@ means that the columns in @a@ are all 'Aggregate' 'Expr's
63−-- for the 'Expr' columns in @b@.
65+-- for the columns in @b@.
64type Aggregates :: Type -> Type -> Constraint
66type Aggregates :: Type -> Type -> Constraint
65class Transposes Aggregate Expr aggregates exprs => Aggregates aggregates exprs
67class Transposes Aggregate Expr aggregates exprs => Aggregates aggregates exprs
66instance Transposes Aggregate Expr aggregates exprs => Aggregates aggregates exprs
68instance Transposes Aggregate Expr aggregates exprs => Aggregates aggregates exprs
67
69
68
70
69−zipOutputs :: ()
71+foldInputs :: forall (a :: Type) (b :: Type). Monoid b
70− => (Expr a -> Expr b -> Expr c) -> Aggregate a -> Aggregate b -> Aggregate c
72+ => (Maybe Aggregator -> Opaleye.PrimExpr -> b) -> Aggregate a -> b
71−zipOutputs f (Aggregate a) (Aggregate b) = Aggregate (liftA2 f a b)
73+foldInputs f (Aggregate (Opaleye.Aggregator (Opaleye.PackMap agg))) =
74+ getConst $ flip agg () $ \(aggregator, a) ->
75+ Const $ f (detuplize <$> aggregator) a
76+ where
77+ detuplize (operation, ordering, distinction) =
78+ Aggregator {operation, ordering, distinction}
79+
80+
81+mapInputs :: forall (a :: Type). ()
82+ => (Opaleye.PrimExpr -> Opaleye.PrimExpr) -> Aggregate a -> Aggregate a
83+mapInputs transform (Aggregate (Opaleye.Aggregator (Opaleye.PackMap agg))) =
84+ Aggregate $ Opaleye.Aggregator $ Opaleye.PackMap $ agg . \f input ->
85+ f (fmap transform input)
86+
87+
88+type Aggregator :: Type
89+data Aggregator = Aggregator
90+ { operation :: Opaleye.AggrOp
91+ , ordering :: [Opaleye.OrderExpr]
92+ , distinction :: Opaleye.AggrDistinct
93+ }
72
94
73
95
74−unsafeMakeAggregate :: forall (input :: Type) (output :: Type) n n' a a'. ()
96+unsafeMakeAggregate :: forall (input :: Type) (output :: Type). ()
75 => (Expr input -> Opaleye.PrimExpr)
97 => (Expr input -> Opaleye.PrimExpr)
76 -> (Opaleye.PrimExpr -> Expr output)
98 -> (Opaleye.PrimExpr -> Expr output)
77− -> Opaleye.Aggregator (Opaleye.Field_ n a) (Opaleye.Field_ n' a')
99+ -> Maybe Aggregator
78 -> Expr input
100 -> Expr input
79 -> Aggregate output
101 -> Aggregate output
80unsafeMakeAggregate input output aggregator expr =
102unsafeMakeAggregate input output aggregator expr =
81− Aggregate $ dimap in_ out aggregator
103+ Aggregate $ Opaleye.Aggregator $ Opaleye.PackMap $ \f _ ->
82− where out = output . Opaleye.unColumn
104+ output <$> f (tuplize <$> aggregator, input expr)
83− in_ = Opaleye.Column . input . const expr
105+ where
106+ tuplize Aggregator {operation, ordering, distinction} =
107+ (operation, ordering, distinction)
src/Rel8/Column/Null.hsdeleted−26
@@ -1,26 +0,0 @@
1−{-# language DataKinds #-}
2−{-# language StandaloneKindSignatures #-}
3−{-# language TypeFamilyDependencies #-}
4−
5−module Rel8.Column.Null
6− ( HNull
7− )
8−where
9−
10−-- base
11−import Data.Kind ( Type )
12−import Prelude
13−
14−-- rel8
15−import qualified Rel8.Schema.Kind as K
16−import Rel8.Schema.Result ( Result )
17−import Rel8.Table.Null ( NullTable )
18−
19−
20−-- | Nest a 'Null' value within a 'Rel8able'. @HNull f a@ will produce a
21−-- 'NullTable' @a@ in the 'Expr' context, and a @'Maybe' a@ in the 'Result'
22−-- context.
23−type HNull :: K.Context -> Type -> Type
24−type family HNull context = maybe | maybe -> context where
25− HNull Result = Maybe
26− HNull context = NullTable context
src/Rel8/Expr.hs−1
@@ -1,6 +1,5 @@
1{-# language DataKinds #-}
1{-# language DataKinds #-}
2{-# language DerivingStrategies #-}
2{-# language DerivingStrategies #-}
3−{-# language FlexibleContexts #-}
4{-# language FlexibleInstances #-}
3{-# language FlexibleInstances #-}
5{-# language MultiParamTypeClasses #-}
4{-# language MultiParamTypeClasses #-}
6{-# language ScopedTypeVariables #-}
5{-# language ScopedTypeVariables #-}
src/Rel8/Expr/Aggregate.hs+64−25
@@ -10,7 +10,6 @@ module Rel8.Expr.Aggregate
10 , and, or
10 , and, or
11 , min, max
11 , min, max
12 , sum, sumWhere
12 , sum, sumWhere
13− , avg
14 , stringAgg
13 , stringAgg
15 , groupByExpr
14 , groupByExpr
16 , listAggExpr, nonEmptyAggExpr
15 , listAggExpr, nonEmptyAggExpr
@@ -24,12 +23,10 @@ import Data.List.NonEmpty ( NonEmpty )
24import Prelude hiding ( and, max, min, null, or, sum )
23import Prelude hiding ( and, max, min, null, or, sum )
25
24
26-- opaleye
25-- opaleye
27−import qualified Opaleye.Internal.Aggregate as Opaleye
26+import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
28−import Opaleye.Internal.Column ( Field_( Column ) )
29−import qualified Opaleye.Aggregate as Opaleye
30
27
31-- rel8
28-- rel8
32−import Rel8.Aggregate ( Aggregate, unsafeMakeAggregate )
29+import Rel8.Aggregate ( Aggregate, Aggregator(..), unsafeMakeAggregate )
33import Rel8.Expr ( Expr )
30import Rel8.Expr ( Expr )
34import Rel8.Expr.Bool ( caseExpr )
31import Rel8.Expr.Bool ( caseExpr )
35import Rel8.Expr.Opaleye
32import Rel8.Expr.Opaleye
@@ -53,13 +50,23 @@ import Rel8.Type.Sum ( DBSum )
53
50
54-- | Count the occurances of a single column. Corresponds to @COUNT(a)@
51-- | Count the occurances of a single column. Corresponds to @COUNT(a)@
55count :: Expr a -> Aggregate Int64
52count :: Expr a -> Aggregate Int64
56−count = unsafeMakeAggregate toPrimExpr fromPrimExpr Opaleye.count
53+count = unsafeMakeAggregate toPrimExpr fromPrimExpr $
54+ Just Aggregator
55+ { operation = Opaleye.AggrCount
56+ , ordering = []
57+ , distinction = Opaleye.AggrAll
58+ }
59+
57
60
58-- | Count the number of distinct occurances of a single column. Corresponds to
61-- | Count the number of distinct occurances of a single column. Corresponds to
59-- @COUNT(DISTINCT a)@
62-- @COUNT(DISTINCT a)@
60countDistinct :: Sql DBEq a => Expr a -> Aggregate Int64
63countDistinct :: Sql DBEq a => Expr a -> Aggregate Int64
61countDistinct = unsafeMakeAggregate toPrimExpr fromPrimExpr $
64countDistinct = unsafeMakeAggregate toPrimExpr fromPrimExpr $
62− Opaleye.distinctAggregator Opaleye.count
65+ Just Aggregator
66+ { operation = Opaleye.AggrCount
67+ , ordering = []
68+ , distinction = Opaleye.AggrDistinct
69+ }
63
70
64
71
65-- | Corresponds to @COUNT(*)@.
72-- | Corresponds to @COUNT(*)@.
@@ -74,39 +81,56 @@ countWhere condition = count (caseExpr [(condition, litExpr (Just True))] null)
74
81
75-- | Corresponds to @bool_and@.
82-- | Corresponds to @bool_and@.
76and :: Expr Bool -> Aggregate Bool
83and :: Expr Bool -> Aggregate Bool
77−and = unsafeMakeAggregate toPrimExpr fromPrimExpr Opaleye.boolAnd
84+and = unsafeMakeAggregate toPrimExpr fromPrimExpr $
85+ Just Aggregator
86+ { operation = Opaleye.AggrBoolAnd
87+ , ordering = []
88+ , distinction = Opaleye.AggrAll
89+ }
78
90
79
91
80-- | Corresponds to @bool_or@.
92-- | Corresponds to @bool_or@.
81or :: Expr Bool -> Aggregate Bool
93or :: Expr Bool -> Aggregate Bool
82−or = unsafeMakeAggregate toPrimExpr fromPrimExpr Opaleye.boolOr
94+or = unsafeMakeAggregate toPrimExpr fromPrimExpr $
95+ Just Aggregator
96+ { operation = Opaleye.AggrBoolOr
97+ , ordering = []
98+ , distinction = Opaleye.AggrAll
99+ }
83
100
84
101
85-- | Produce an aggregation for @Expr a@ using the @max@ function.
102-- | Produce an aggregation for @Expr a@ using the @max@ function.
86max :: Sql DBMax a => Expr a -> Aggregate a
103max :: Sql DBMax a => Expr a -> Aggregate a
87−max = unsafeMakeAggregate toPrimExpr fromPrimExpr Opaleye.unsafeMax
104+max = unsafeMakeAggregate toPrimExpr fromPrimExpr $
105+ Just Aggregator
106+ { operation = Opaleye.AggrMax
107+ , ordering = []
108+ , distinction = Opaleye.AggrAll
109+ }
88
110
89
111
90-- | Produce an aggregation for @Expr a@ using the @max@ function.
112-- | Produce an aggregation for @Expr a@ using the @max@ function.
91min :: Sql DBMin a => Expr a -> Aggregate a
113min :: Sql DBMin a => Expr a -> Aggregate a
92−min = unsafeMakeAggregate toPrimExpr fromPrimExpr Opaleye.unsafeMin
114+min = unsafeMakeAggregate toPrimExpr fromPrimExpr $
115+ Just Aggregator
116+ { operation = Opaleye.AggrMin
117+ , ordering = []
118+ , distinction = Opaleye.AggrAll
119+ }
93
120
94-- | Corresponds to @sum@. Note that in SQL, @sum@ is type changing - for
121-- | Corresponds to @sum@. Note that in SQL, @sum@ is type changing - for
95-- example the @sum@ of @integer@ returns a @bigint@. Rel8 doesn't support
122-- example the @sum@ of @integer@ returns a @bigint@. Rel8 doesn't support
96−-- this, and will add explicit casts back to the original input type. This can
123+-- this, and will add explicit cast back to the original input type. This can
97-- lead to overflows, and if you anticipate very large sums, you should upcast
124-- lead to overflows, and if you anticipate very large sums, you should upcast
98-- your input.
125-- your input.
99sum :: Sql DBSum a => Expr a -> Aggregate a
126sum :: Sql DBSum a => Expr a -> Aggregate a
100−sum = unsafeMakeAggregate toPrimExpr (castExpr . fromPrimExpr) Opaleye.unsafeSum
127+sum = unsafeMakeAggregate toPrimExpr (castExpr . fromPrimExpr) $
101−
128+ Just Aggregator
129+ { operation = Opaleye.AggrSum
130+ , ordering = []
131+ , distinction = Opaleye.AggrAll
132+ }
102
133
103−-- | Corresponds to @avg@. Note that in SQL, @avg@ is type changing - for
104−-- example, the @avg@ of @integer@ returns a @numeric@. Rel8 doesn't support
105−-- this, and will add explicit casts back to the original input type. If you
106−-- need a fractional result on an integral column, you should cast your input
107−-- to 'Double' or 'Data.Scientific.Scientific' before calling 'avg'.
108−avg :: Sql DBSum a => Expr a -> Aggregate a
109−avg = unsafeMakeAggregate toPrimExpr (castExpr . fromPrimExpr) Opaleye.unsafeAvg
110
134
111-- | Take the sum of all expressions that satisfy a predicate.
135-- | Take the sum of all expressions that satisfy a predicate.
112sumWhere :: (Sql DBNum a, Sql DBSum a)
136sumWhere :: (Sql DBNum a, Sql DBSum a)
@@ -118,12 +142,17 @@ sumWhere condition a = sum (caseExpr [(condition, a)] 0)
118stringAgg :: Sql DBString a
142stringAgg :: Sql DBString a
119 => Expr db -> Expr a -> Aggregate a
143 => Expr db -> Expr a -> Aggregate a
120stringAgg delimiter =
144stringAgg delimiter =
121− unsafeMakeAggregate toPrimExpr (castExpr . fromPrimExpr) (Opaleye.stringAgg (Column (toPrimExpr delimiter)))
145+ unsafeMakeAggregate toPrimExpr (castExpr . fromPrimExpr) $
146+ Just Aggregator
147+ { operation = Opaleye.AggrStringAggr (toPrimExpr delimiter)
148+ , ordering = []
149+ , distinction = Opaleye.AggrAll
150+ }
122
151
123
152
124-- | Aggregate a value by grouping by it.
153-- | Aggregate a value by grouping by it.
125groupByExpr :: Sql DBEq a => Expr a -> Aggregate a
154groupByExpr :: Sql DBEq a => Expr a -> Aggregate a
126−groupByExpr = unsafeMakeAggregate toPrimExpr fromPrimExpr Opaleye.groupBy
155+groupByExpr = unsafeMakeAggregate toPrimExpr fromPrimExpr Nothing
127
156
128
157
129-- | Collect expressions values as a list.
158-- | Collect expressions values as a list.
@@ -138,13 +167,23 @@ nonEmptyAggExpr = snonEmptyAggExpr typeInformation
138
167
139slistAggExpr :: ()
168slistAggExpr :: ()
140 => TypeInformation (Unnullify a) -> Expr a -> Aggregate [a]
169 => TypeInformation (Unnullify a) -> Expr a -> Aggregate [a]
141−slistAggExpr info = unsafeMakeAggregate to fromPrimExpr Opaleye.arrayAgg
170+slistAggExpr info = unsafeMakeAggregate to fromPrimExpr $ Just
171+ Aggregator
172+ { operation = Opaleye.AggrArr
173+ , ordering = []
174+ , distinction = Opaleye.AggrAll
175+ }
142 where
176 where
143 to = encodeArrayElement info . toPrimExpr
177 to = encodeArrayElement info . toPrimExpr
144
178
145
179
146snonEmptyAggExpr :: ()
180snonEmptyAggExpr :: ()
147 => TypeInformation (Unnullify a) -> Expr a -> Aggregate (NonEmpty a)
181 => TypeInformation (Unnullify a) -> Expr a -> Aggregate (NonEmpty a)
148−snonEmptyAggExpr info = unsafeMakeAggregate to fromPrimExpr Opaleye.arrayAgg
182+snonEmptyAggExpr info = unsafeMakeAggregate to fromPrimExpr $ Just
183+ Aggregator
184+ { operation = Opaleye.AggrArr
185+ , ordering = []
186+ , distinction = Opaleye.AggrAll
187+ }
149 where
188 where
150 to = encodeArrayElement info . toPrimExpr
189 to = encodeArrayElement info . toPrimExpr
src/Rel8/Expr/Num.hs+6−39
@@ -4,24 +4,18 @@
4{-# options_ghc -fno-warn-redundant-constraints #-}
4{-# options_ghc -fno-warn-redundant-constraints #-}
5
5
6module Rel8.Expr.Num
6module Rel8.Expr.Num
7− ( fromIntegral
8− , realToFrac
9− , div, mod, divMod
10− , quot, rem, quotRem
11− , ceiling, floor, round, truncate
7+ ( fromIntegral, realToFrac, div, mod, ceiling, floor, round, truncate
12 )
8 )
13where
9where
14
10
15-- base
11-- base
16−import Prelude ( (+), (-), fst, negate, signum, snd )
12+import Prelude ()
17
13
18-- rel
14-- rel
19import Rel8.Expr ( Expr( Expr ) )
15import Rel8.Expr ( Expr( Expr ) )
20−import Rel8.Expr.Eq ( (==.) )
21import Rel8.Expr.Function ( function )
16import Rel8.Expr.Function ( function )
22import Rel8.Expr.Opaleye ( castExpr )
17import Rel8.Expr.Opaleye ( castExpr )
23import Rel8.Schema.Null ( Homonullable, Sql )
18import Rel8.Schema.Null ( Homonullable, Sql )
24−import Rel8.Table.Bool ( bool )
25import Rel8.Type.Num ( DBFractional, DBIntegral, DBNum )
19import Rel8.Type.Num ( DBFractional, DBIntegral, DBNum )
26
20
27
21
@@ -48,41 +42,14 @@ ceiling :: (Sql DBFractional a, Sql DBIntegral b, Homonullable a b)
48ceiling = function "ceiling"
42ceiling = function "ceiling"
49
43
50
44
51−-- | Emulates the behaviour of the Haskell function 'Prelude.div' in
45+-- | Perform integral division. Corresponds to the @div()@ function.
52−-- PostgreSQL.
53div :: Sql DBIntegral a => Expr a -> Expr a -> Expr a
46div :: Sql DBIntegral a => Expr a -> Expr a -> Expr a
54−div n d = fst (divMod n d)
47+div = function "div"
55
48
56
49
57−-- | Emulates the behaviour of the Haskell function 'Prelude.mod' in
50+-- | Corresponds to the @mod()@ function.
58−-- PostgreSQL.
59mod :: Sql DBIntegral a => Expr a -> Expr a -> Expr a
51mod :: Sql DBIntegral a => Expr a -> Expr a -> Expr a
60−mod n d = snd (divMod n d)
52+mod = function "mod"
61−
62−
63−-- | Simultaneous 'div' and 'mod'.
64−divMod :: Sql DBIntegral a => Expr a -> Expr a -> (Expr a, Expr a)
65−divMod n d = bool qr (q - 1, r + d) (signum r ==. negate (signum d))
66− where
67− qr@(q, r) = quotRem n d
68−
69−
70−-- | Perform integral division. Corresponds to the @div()@ function in
71−-- PostgreSQL, which behaves like Haskell's 'Prelude.quot' rather than
72−-- 'Prelude.div'.
73−quot :: Sql DBIntegral a => Expr a -> Expr a -> Expr a
74−quot = function "div"
75−
76−
77−-- | Corresponds to the @mod()@ function in PostgreSQL, which behaves like
78−-- Haskell's 'Prelude.rem' rather than 'Prelude.mod'.
79−rem :: Sql DBIntegral a => Expr a -> Expr a -> Expr a
80−rem = function "mod"
81−
82−
83−-- | Simultaneous 'quot' and 'rem'.
84−quotRem :: Sql DBIntegral a => Expr a -> Expr a -> (Expr a, Expr a)
85−quotRem n d = (quot n d, rem n d)
86
53
87
54
88-- | Round a 'DFractional' to a 'DBIntegral' by rounding to the nearest smaller
55-- | Round a 'DFractional' to a 'DBIntegral' by rounding to the nearest smaller
src/Rel8/Expr/Opaleye.hs+3−12
@@ -10,7 +10,7 @@ module Rel8.Expr.Opaleye
10 , scastExpr, sunsafeCastExpr
10 , scastExpr, sunsafeCastExpr
11 , unsafeLiteral
11 , unsafeLiteral
12 , fromPrimExpr, toPrimExpr, mapPrimExpr, zipPrimExprsWith, traversePrimExpr
12 , fromPrimExpr, toPrimExpr, mapPrimExpr, zipPrimExprsWith, traversePrimExpr
13− , toColumn, fromColumn, traverseFieldP
13+ , toColumn, fromColumn
14 )
14 )
15where
15where
16
16
@@ -27,9 +27,6 @@ import Rel8.Schema.Null ( Unnullify, Sql )
27import Rel8.Type ( DBType, typeInformation )
27import Rel8.Type ( DBType, typeInformation )
28import Rel8.Type.Information ( TypeInformation(..) )
28import Rel8.Type.Information ( TypeInformation(..) )
29
29
30−-- profunctors
31−import Data.Profunctor ( Profunctor, dimap )
32−
33
30
34castExpr :: Sql DBType a => Expr a -> Expr a
31castExpr :: Sql DBType a => Expr a -> Expr a
35castExpr = scastExpr typeInformation
32castExpr = scastExpr typeInformation
@@ -83,15 +80,9 @@ traversePrimExpr :: Functor f
83traversePrimExpr f = fmap fromPrimExpr . f . toPrimExpr
80traversePrimExpr f = fmap fromPrimExpr . f . toPrimExpr
84
81
85
82
86−traverseFieldP :: Profunctor p
87− => p (Opaleye.Field_ n x) (Opaleye.Field_ m y)
88− -> p (Expr a) (Expr b)
89−traverseFieldP = dimap (toColumn . toPrimExpr) (fromPrimExpr . fromColumn)
90−
91−
92−toColumn :: Opaleye.PrimExpr -> Opaleye.Field_ n b
83+toColumn :: Opaleye.PrimExpr -> Opaleye.Column b
93toColumn = Opaleye.Column
84toColumn = Opaleye.Column
94
85
95
86
96−fromColumn :: Opaleye.Field_ n b -> Opaleye.PrimExpr
87+fromColumn :: Opaleye.Column b -> Opaleye.PrimExpr
97fromColumn (Opaleye.Column a) = a
88fromColumn (Opaleye.Column a) = a
src/Rel8/Expr/Sequence.hs+1−2
@@ -11,7 +11,6 @@ import Prelude
11import Rel8.Expr ( Expr )
11import Rel8.Expr ( Expr )
12import Rel8.Expr.Function ( function )
12import Rel8.Expr.Function ( function )
13import Rel8.Expr.Serialize ( litExpr )
13import Rel8.Expr.Serialize ( litExpr )
14−import Rel8.Expr.Text ( quoteIdent )
15
14
16-- text
15-- text
17import Data.Text ( pack )
16import Data.Text ( pack )
@@ -19,4 +18,4 @@ import Data.Text ( pack )
19
18
20-- | See https://www.postgresql.org/docs/current/functions-sequence.html
19-- | See https://www.postgresql.org/docs/current/functions-sequence.html
21nextval :: String -> Expr Int64
20nextval :: String -> Expr Int64
22−nextval = function "nextval" . quoteIdent . litExpr . pack
21+nextval = function "nextval" . litExpr . pack
src/Rel8/Expr/Text.hs+2−23
@@ -17,9 +17,6 @@ module Rel8.Expr.Text
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, translate
19 , splitPart, strpos, substr, translate
20−
21− -- * @LIKE@ and @ILIKE@
22− , like, ilike
23 )
20 )
24where
21where
25
22
@@ -27,7 +24,7 @@ where
27import Data.Bool ( Bool )
24import Data.Bool ( Bool )
28import Data.Int ( Int32 )
25import Data.Int ( Int32 )
29import Data.Maybe ( Maybe( Nothing, Just ) )
26import Data.Maybe ( Maybe( Nothing, Just ) )
30−import Prelude ( flip )
27+import Prelude ()
31
28
32-- bytestring
29-- bytestring
33import Data.ByteString ( ByteString )
30import Data.ByteString ( ByteString )
@@ -52,7 +49,7 @@ infixr 6 ++.
52
49
53
50
54-- | Matches regular expression, case sensitive
51-- | Matches regular expression, case sensitive
55−--
52+--
56-- Corresponds to the @~.@ operator.
53-- Corresponds to the @~.@ operator.
57(~.) :: Expr Text -> Expr Text -> Expr Bool
54(~.) :: Expr Text -> Expr Text -> Expr Bool
58(~.) = binaryOperator "~."
55(~.) = binaryOperator "~."
@@ -276,21 +273,3 @@ substr a b Nothing = function "substr" a b
276-- | Corresponds to the @translate@ function.
273-- | Corresponds to the @translate@ function.
277translate :: Expr Text -> Expr Text -> Expr Text -> Expr Text
274translate :: Expr Text -> Expr Text -> Expr Text -> Expr Text
278translate = function "translate"
275translate = function "translate"
279−
280−
281−-- | @like x y@ corresponds to the expression @y LIKE x@.
282−--
283−-- Note that the arguments to @like@ are swapped. This is to aid currying, so
284−-- you can write expressions like
285−-- @filter (like "Rel%" . packageName) =<< each haskellPackages@
286−like :: Expr Text -> Expr Text -> Expr Bool
287−like = flip (binaryOperator "LIKE")
288−
289−
290−-- | @ilike x y@ corresponds to the expression @y ILIKE x@.
291−--
292−-- Note that the arguments to @ilike@ are swapped. This is to aid currying, so
293−-- you can write expressions like
294−-- @filter (ilike "Rel%" . packageName) =<< each haskellPackages@
295−ilike :: Expr Text -> Expr Text -> Expr Bool
296−ilike = flip (binaryOperator "ILIKE")
src/Rel8/Expr/Window.hsdeleted−121
@@ -1,121 +0,0 @@
1−module Rel8.Expr.Window
2− ( cumulative
3− , rowNumber
4− , rank
5− , denseRank
6− , percentRank
7− , cumeDist
8− , ntile
9− , lag
10− , lead
11− , firstValue
12− , lastValue
13− , nthValue
14− )
15−where
16−
17−-- base
18−import Data.Int ( Int32, Int64 )
19−import Prelude
20−
21−-- opaleye
22−import qualified Opaleye.Internal.Aggregate as Opaleye
23−import qualified Opaleye.Internal.PackMap as Opaleye
24−import qualified Opaleye.Internal.Window as Opaleye
25−import qualified Opaleye.Window as Opaleye
26−
27−-- profunctors
28−import Data.Profunctor (dimap)
29−
30−-- rel8
31−import Rel8.Aggregate ( Aggregate( Aggregate ) )
32−import Rel8.Expr ( Expr )
33−import Rel8.Expr.Opaleye ( fromColumn, fromPrimExpr, toColumn, toPrimExpr )
34−import Rel8.Schema.Null ( Nullify )
35−import Rel8.Window ( Window( Window ) )
36−
37−
38−cumulative :: (a -> Aggregate b) -> Window a (Expr b)
39−cumulative f =
40− fromWindowFunction $ Opaleye.aggregatorWindowFunction (fromAggregate f) id
41−
42−
43−-- | [@row_number()@](https://www.postgresql.org/docs/current/functions-window.html)
44−rowNumber :: Window a (Expr Int64)
45−rowNumber = fromWindowFunction $ fromPrimExpr . fromColumn <$> Opaleye.rowNumber
46−
47−
48−-- | [@rank()@](https://www.postgresql.org/docs/current/functions-window.html)
49−rank :: Window a (Expr Int64)
50−rank = fromWindowFunction $ fromPrimExpr . fromColumn <$> Opaleye.rank
51−
52−
53−-- | [@dense_rank()@](https://www.postgresql.org/docs/current/functions-window.html)
54−denseRank :: Window a (Expr Int64)
55−denseRank = fromWindowFunction $ fromPrimExpr . fromColumn <$> Opaleye.denseRank
56−
57−
58−-- | [@percent_rank()@](https://www.postgresql.org/docs/current/functions-window.html)
59−percentRank :: Window a (Expr Double)
60−percentRank = fromWindowFunction $ fromPrimExpr . fromColumn <$> Opaleye.percentRank
61−
62−
63−-- | [@cume_dist()@](https://www.postgresql.org/docs/current/functions-window.html)
64−cumeDist :: Window a (Expr Double)
65−cumeDist = fromWindowFunction $ fromPrimExpr . fromColumn <$> Opaleye.cumeDist
66−
67−
68−-- | [@ntile(num_buckets)@](https://www.postgresql.org/docs/current/functions-window.html)
69−ntile :: Expr Int32 -> Window a (Expr Int32)
70−ntile buckets = fromWindowFunction $ fromPrimExpr . fromColumn <$>
71− Opaleye.ntile (toColumn (toPrimExpr buckets))
72−
73−
74−-- | [@lag(value, offset, default)@](https://www.postgresql.org/docs/current/functions-window.html)
75−lag :: Expr Int32 -> Expr a -> Window (Expr a) (Expr a)
76−lag offset def =
77− fromWindowFunction $
78− dimap (toColumn . toPrimExpr) (fromPrimExpr . fromColumn) $
79− Opaleye.lag (toColumn (toPrimExpr offset)) (toColumn (toPrimExpr def))
80−
81−
82−-- | [@lead(value, offset, default)@](https://www.postgresql.org/docs/current/functions-window.html)
83−lead :: Expr Int32 -> Expr a -> Window (Expr a) (Expr a)
84−lead offset def =
85− fromWindowFunction $
86− dimap (toColumn . toPrimExpr) (fromPrimExpr . fromColumn) $
87− Opaleye.lead (toColumn (toPrimExpr offset)) (toColumn (toPrimExpr def))
88−
89−
90−-- | [@first_value(value)@](https://www.postgresql.org/docs/current/functions-window.html)
91−firstValue :: Window (Expr a) (Expr a)
92−firstValue =
93− fromWindowFunction $
94− dimap (toColumn . toPrimExpr) (fromPrimExpr . fromColumn)
95− Opaleye.firstValue
96−
97−
98−-- | [@last_value(value)@](https://www.postgresql.org/docs/current/functions-window.html)
99−lastValue :: Window (Expr a) (Expr a)
100−lastValue =
101− fromWindowFunction $
102− dimap (toColumn . toPrimExpr) (fromPrimExpr . fromColumn)
103− Opaleye.lastValue
104−
105−
106−-- | [@nth_value(value, n)@](https://www.postgresql.org/docs/current/functions-window.html)
107−nthValue :: Expr Int32 -> Window (Expr a) (Expr (Nullify a))
108−nthValue n =
109− fromWindowFunction $
110− dimap (toColumn . toPrimExpr) (fromPrimExpr . fromColumn) $
111− Opaleye.nthValue (toColumn (toPrimExpr n))
112−
113−
114−fromAggregate :: (a -> Aggregate b) -> Opaleye.Aggregator a (Expr b)
115−fromAggregate f = Opaleye.Aggregator $ Opaleye.PackMap $ \w a -> case f a of
116− Aggregate (Opaleye.Aggregator (Opaleye.PackMap x)) -> x w ()
117−
118−
119−fromWindowFunction :: Opaleye.WindowFunction a b -> Window a b
120−fromWindowFunction (Opaleye.WindowFunction (Opaleye.PackMap w)) =
121− Window $ Opaleye.Windows $ Opaleye.PackMap $ \f -> w $ \o -> f (o, mempty)
src/Rel8/Generic/Construction/ADT.hs−1
@@ -1,7 +1,6 @@
1{-# language AllowAmbiguousTypes #-}
1{-# language AllowAmbiguousTypes #-}
2{-# language BlockArguments #-}
2{-# language BlockArguments #-}
3{-# language DataKinds #-}
3{-# language DataKinds #-}
4−{-# language FlexibleContexts #-}
5{-# language FlexibleInstances #-}
4{-# language FlexibleInstances #-}
6{-# language MultiParamTypeClasses #-}
5{-# language MultiParamTypeClasses #-}
7{-# language RankNTypes #-}
6{-# language RankNTypes #-}
src/Rel8/Generic/Record.hs−2
@@ -1,6 +1,5 @@
1{-# language AllowAmbiguousTypes #-}
1{-# language AllowAmbiguousTypes #-}
2{-# language DataKinds #-}
2{-# language DataKinds #-}
3−{-# language FlexibleContexts #-}
4{-# language FlexibleInstances #-}
3{-# language FlexibleInstances #-}
5{-# language MultiParamTypeClasses #-}
4{-# language MultiParamTypeClasses #-}
6{-# language PolyKinds #-}
5{-# language PolyKinds #-}
@@ -142,7 +141,6 @@ instance {-# OVERLAPPABLE #-} Snd (Count n rep) ~ rep => Countable n rep where
142 uncount = id
141 uncount = id
143
142
144
143
145−type Record :: Type -> Type
146newtype Record a = Record
144newtype Record a = Record
147 { unrecord :: a
145 { unrecord :: a
148 }
146 }
src/Rel8/Generic/Rel8able.hs+1−6
@@ -63,11 +63,6 @@ type KRel8able = K.Rel8able
63-- This is almost 'Data.Type.Equality.==', but we add an extra case.
63-- This is almost 'Data.Type.Equality.==', but we add an extra case.
64type (==) :: k -> k -> Bool
64type (==) :: k -> k -> Bool
65type family a == b where
65type family a == b where
66− -- This extra case is needed to solve the equation "a == a" (where a is a
67− -- KRel8able), which occurs when we have polymorphic Rel8ables
68− -- (e.g., newtype T t f = T { x :: t a })
69− (a :: KRel8able) == (a :: KRel8able) = 'True
70−
71 -- This extra case is needed to solve the equation "a == Identity a",
66 -- This extra case is needed to solve the equation "a == Identity a",
72 -- which occurs when we have polymorphic Rel8ables
67 -- which occurs when we have polymorphic Rel8ables
73 -- (e.g., newtype T a f = T { x :: Column f a })
68 -- (e.g., newtype T a f = T { x :: Column f a })
@@ -146,7 +141,7 @@ type instance Eval (TSerialize expr a) =
146-- [Supplying @Rel8able@ instances]
141-- [Supplying @Rel8able@ instances]
147--
142--
148-- This type class should be derived generically for all table types in your
143-- This type class should be derived generically for all table types in your
149−-- project. To do this, enable the @DeriveAnyClass@ and @DeriveGeneric@ language
144+-- project. To do this, enable the @DeriveAnyType@ and @DeriveGeneric@ language
150-- extensions:
145-- extensions:
151--
146--
152-- @
147-- @
src/Rel8/Generic/Table/ADT.hs−1
@@ -1,6 +1,5 @@
1{-# language AllowAmbiguousTypes #-}
1{-# language AllowAmbiguousTypes #-}
2{-# language DataKinds #-}
2{-# language DataKinds #-}
3−{-# language FlexibleContexts #-}
4{-# language FlexibleInstances #-}
3{-# language FlexibleInstances #-}
5{-# language LambdaCase #-}
4{-# language LambdaCase #-}
6{-# language MultiParamTypeClasses #-}
5{-# language MultiParamTypeClasses #-}
src/Rel8/Order.hs+8−1
@@ -4,6 +4,7 @@
4
4
5module Rel8.Order
5module Rel8.Order
6 ( Order(..)
6 ( Order(..)
7+ , toOrderExprs
7 )
8 )
8where
9where
9
10
@@ -16,7 +17,8 @@ import Prelude
16import Data.Functor.Contravariant.Divisible ( Decidable, Divisible )
17import Data.Functor.Contravariant.Divisible ( Decidable, Divisible )
17
18
18-- opaleye
19-- opaleye
19−import qualified Opaleye.Order as Opaleye
20+import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
21+import qualified Opaleye.Internal.Order as Opaleye
20
22
21
23
22-- | An ordering expression for @a@. Primitive orderings are defined with
24-- | An ordering expression for @a@. Primitive orderings are defined with
@@ -28,3 +30,8 @@ import qualified Opaleye.Order as Opaleye
28type Order :: Type -> Type
30type Order :: Type -> Type
29newtype Order a = Order (Opaleye.Order a)
31newtype Order a = Order (Opaleye.Order a)
30 deriving newtype (Contravariant, Divisible, Decidable, Semigroup, Monoid)
32 deriving newtype (Contravariant, Divisible, Decidable, Semigroup, Monoid)
33+
34+
35+toOrderExprs :: Order a -> a -> [Opaleye.OrderExpr]
36+toOrderExprs (Order (Opaleye.Order order)) a =
37+ uncurry Opaleye.OrderExpr <$> order a
src/Rel8/Query.hs+10−23
@@ -1,6 +1,4 @@
1−{-# language FlexibleContexts #-}
2{-# language StandaloneKindSignatures #-}
1{-# language StandaloneKindSignatures #-}
3−{-# language UndecidableInstances #-}
4
2
5module Rel8.Query
3module Rel8.Query
6 ( Query( Query )
4 ( Query( Query )
@@ -17,16 +15,15 @@ import Prelude
17-- opaleye
15-- opaleye
18import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
16import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
19import qualified Opaleye.Internal.PackMap as Opaleye
17import qualified Opaleye.Internal.PackMap as Opaleye
20−import qualified Opaleye.Internal.PrimQuery as Opaleye hiding (lateral)
18+import qualified Opaleye.Internal.PrimQuery as Opaleye
21import qualified Opaleye.Internal.QueryArr as Opaleye
19import qualified Opaleye.Internal.QueryArr as Opaleye
22import qualified Opaleye.Internal.Tag as Opaleye
20import qualified Opaleye.Internal.Tag as Opaleye
23
21
24-- rel8
22-- rel8
25−import Rel8.Expr ( Expr )
26import Rel8.Query.Set ( unionAll )
23import Rel8.Query.Set ( unionAll )
27import Rel8.Query.Opaleye ( fromOpaleye )
24import Rel8.Query.Opaleye ( fromOpaleye )
28import Rel8.Query.Values ( values )
25import Rel8.Query.Values ( values )
29−import Rel8.Table ( Table, fromColumns, toColumns )
26+import Rel8.Table ( fromColumns, toColumns )
30import Rel8.Table.Alternative
27import Rel8.Table.Alternative
31 ( AltTable, (<|>:)
28 ( AltTable, (<|>:)
32 , AlternativeTable, emptyTable
29 , AlternativeTable, emptyTable
@@ -178,14 +175,14 @@ instance Bind Query where
178
175
179
176
180instance Monad Query where
177instance Monad Query where
181− Query q >>= f = Query $ \dummies -> Opaleye.stateQueryArr $ \_ tag ->
178+ Query q >>= f = Query $ \dummies -> Opaleye.QueryArr $ \(_, tag) ->
182 let
179 let
183− qa = q dummies
180+ Opaleye.QueryArr qa = q dummies
184− ((m, a), query, tag') = Opaleye.runStateQueryArr qa () tag
181+ ((m, a), query, tag') = qa ((), tag)
185 Query q' = f a
182 Query q' = f a
186 (dummies', query', tag'') =
183 (dummies', query', tag'') =
187 ( dummy : dummies
184 ( dummy : dummies
188− , query <> Opaleye.aRebind bindings
185+ , \lateral -> Opaleye.Rebind True bindings . query lateral
189 , Opaleye.next tag'
186 , Opaleye.next tag'
190 )
187 )
191 where
188 where
@@ -193,11 +190,11 @@ instance Monad Query where
193 where
190 where
194 random = Opaleye.FunExpr "random" []
191 random = Opaleye.FunExpr "random" []
195 name = Opaleye.extractAttr "dummy" tag'
192 name = Opaleye.extractAttr "dummy" tag'
196− qa' = Opaleye.lateral $ \_ -> q' dummies'
193+ Opaleye.QueryArr qa' = Opaleye.lateral $ \_ -> q' dummies'
197− ((m'@(Any needsDummies), b), query'', tag''') = Opaleye.runStateQueryArr qa' () tag''
194+ ((m'@(Any needsDummies), b), query'', tag''') = qa' ((), tag'')
198 query'''
195 query'''
199− | needsDummies = query' <> query''
196+ | needsDummies = \lateral -> query'' lateral . query' lateral
200− | otherwise = query <> query''
197+ | otherwise = \lateral -> query'' lateral . query lateral
201 m'' = m <> m'
198 m'' = m <> m'
202 in
199 in
203 ((m'', b), query''', tag''')
200 ((m'', b), query''', tag''')
@@ -211,13 +208,3 @@ instance AltTable Query where
211-- | 'emptyTable' = 'values' @[]@.
208-- | 'emptyTable' = 'values' @[]@.
212instance AlternativeTable Query where
209instance AlternativeTable Query where
213 emptyTable = values []
210 emptyTable = values []
214−
215−
216−-- | '<>' = 'unionAll'.
217−instance Table Expr a => Semigroup (Query a) where
218− (<>) = (<|>:)
219−
220−
221−-- | 'mempty' = @'values' []@.
222−instance Table Expr a => Monoid (Query a) where
223− mempty = emptyTable
src/Rel8/Query/Aggregate.hs−19
@@ -1,17 +1,13 @@
1{-# language FlexibleContexts #-}
1{-# language FlexibleContexts #-}
2{-# language MonoLocalBinds #-}
2{-# language MonoLocalBinds #-}
3−{-# language ScopedTypeVariables #-}
4−{-# language TypeApplications #-}
5
3
6module Rel8.Query.Aggregate
4module Rel8.Query.Aggregate
7 ( aggregate
5 ( aggregate
8 , countRows
6 , countRows
9− , mode
10 )
7 )
11where
8where
12
9
13-- base
10-- base
14−import Data.Functor.Contravariant ( (>$<) )
15import Data.Int ( Int64 )
11import Data.Int ( Int64 )
16import Prelude
12import Prelude
17
13
@@ -22,16 +18,9 @@ import qualified Opaleye.Aggregate as Opaleye
22import Rel8.Aggregate ( Aggregates )
18import Rel8.Aggregate ( Aggregates )
23import Rel8.Expr ( Expr )
19import Rel8.Expr ( Expr )
24import Rel8.Expr.Aggregate ( countStar )
20import Rel8.Expr.Aggregate ( countStar )
25−import Rel8.Expr.Order ( desc )
26import Rel8.Query ( Query )
21import Rel8.Query ( Query )
27−import Rel8.Query.Limit ( limit )
28import Rel8.Query.Maybe ( optional )
22import Rel8.Query.Maybe ( optional )
29import Rel8.Query.Opaleye ( mapOpaleye )
23import Rel8.Query.Opaleye ( mapOpaleye )
30−import Rel8.Query.Order ( orderBy )
31−import Rel8.Table ( toColumns )
32−import Rel8.Table.Aggregate ( hgroupBy )
33−import Rel8.Table.Cols ( Cols( Cols ), fromCols )
34−import Rel8.Table.Eq ( EqTable, eqTable )
35import Rel8.Table.Opaleye ( aggregator )
24import Rel8.Table.Opaleye ( aggregator )
36import Rel8.Table.Maybe ( maybeTable )
25import Rel8.Table.Maybe ( maybeTable )
37
26
@@ -46,11 +35,3 @@ aggregate = mapOpaleye (Opaleye.aggregate aggregator)
46-- will return @0@.
35-- will return @0@.
47countRows :: Query a -> Query (Expr Int64)
36countRows :: Query a -> Query (Expr Int64)
48countRows = fmap (maybeTable 0 id) . optional . aggregate . fmap (const countStar)
37countRows = fmap (maybeTable 0 id) . optional . aggregate . fmap (const countStar)
49−
50−
51−-- | Return the most common row in a query.
52−mode :: forall a. EqTable a => Query a -> Query a
53−mode rows = limit 1 $ fmap (fromCols . snd) $ orderBy (fst >$< desc) $ do
54− aggregate $ do
55− row <- toColumns <$> rows
56− pure (countStar, Cols $ hgroupBy (eqTable @a) row)
src/Rel8/Query/Distinct.hs+9−4
@@ -1,3 +1,5 @@
1+{-# options_ghc -fno-warn-redundant-constraints #-}
2+
1module Rel8.Query.Distinct
3module Rel8.Query.Distinct
2 ( distinct
4 ( distinct
3 , distinctOn
5 , distinctOn
@@ -6,11 +8,12 @@ module Rel8.Query.Distinct
6where
8where
7
9
8-- base
10-- base
9−import Prelude ()
11+import Prelude
10
12
11-- opaleye
13-- opaleye
12import qualified Opaleye.Distinct as Opaleye
14import qualified Opaleye.Distinct as Opaleye
13−import qualified Opaleye.Order as Opaleye
15+import qualified Opaleye.Internal.Order as Opaleye
16+import qualified Opaleye.Internal.QueryArr as Opaleye
14
17
15-- rel8
18-- rel8
16import Rel8.Order ( Order( Order ) )
19import Rel8.Order ( Order( Order ) )
@@ -30,11 +33,13 @@ distinct = mapOpaleye (Opaleye.distinctExplicit distinctspec)
30-- to a projection. If multiple rows have the same projection, it is
33-- to a projection. If multiple rows have the same projection, it is
31-- unspecified which row will be returned. If this matters, use 'distinctOnBy'.
34-- unspecified which row will be returned. If this matters, use 'distinctOnBy'.
32distinctOn :: EqTable b => (a -> b) -> Query a -> Query a
35distinctOn :: EqTable b => (a -> b) -> Query a -> Query a
36+distinctOn proj =
33−distinctOn proj = mapOpaleye (Opaleye.distinctOnExplicit unpackspec proj)
37+ mapOpaleye (\q -> Opaleye.productQueryArr (Opaleye.distinctOn unpackspec proj . Opaleye.runSimpleQueryArr q))
34
38
35
39
36-- | Select all distinct rows from a query, where rows are equivalent according
40-- | Select all distinct rows from a query, where rows are equivalent according
37-- to a projection. If there are multiple rows with the same projection, the
41-- to a projection. If there are multiple rows with the same projection, the
38-- first row according to the specified 'Order' will be returned.
42-- first row according to the specified 'Order' will be returned.
39distinctOnBy :: EqTable b => (a -> b) -> Order a -> Query a -> Query a
43distinctOnBy :: EqTable b => (a -> b) -> Order a -> Query a -> Query a
40−distinctOnBy proj (Order order) = mapOpaleye (Opaleye.distinctOnByExplicit unpackspec proj order)
44+distinctOnBy proj (Order order) =
45+ mapOpaleye (\q -> Opaleye.productQueryArr (Opaleye.distinctOnBy unpackspec proj order . Opaleye.runSimpleQueryArr q))
src/Rel8/Query/Evaluate.hs+22−3
@@ -3,6 +3,7 @@
3
3
4module Rel8.Query.Evaluate
4module Rel8.Query.Evaluate
5 ( evaluate
5 ( evaluate
6+ , rebind
6 )
7 )
7where
8where
8
9
@@ -15,23 +16,28 @@ import Prelude hiding ( undefined )
15
16
16-- opaleye
17-- opaleye
17import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
18import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
19+import qualified Opaleye.Internal.PackMap as Opaleye
20+import qualified Opaleye.Internal.PrimQuery as Opaleye
21+import qualified Opaleye.Internal.QueryArr as Opaleye
22+import qualified Opaleye.Internal.Tag as Opaleye
23+import qualified Opaleye.Internal.Unpackspec as Opaleye
18
24
19-- rel8
25-- rel8
20import Rel8.Expr ( Expr )
26import Rel8.Expr ( Expr )
21import Rel8.Expr.Bool ( (&&.) )
27import Rel8.Expr.Bool ( (&&.) )
22import Rel8.Expr.Opaleye ( fromPrimExpr )
28import Rel8.Expr.Opaleye ( fromPrimExpr )
23import Rel8.Query ( Query( Query ) )
29import Rel8.Query ( Query( Query ) )
24−import Rel8.Query.Rebind ( rebind )
25import Rel8.Table ( Table )
30import Rel8.Table ( Table )
26import Rel8.Table.Bool ( case_ )
31import Rel8.Table.Bool ( case_ )
27−import Rel8.Table.Undefined ( undefined )
32+import Rel8.Table.Opaleye ( unpackspec )
33+import Rel8.Table.Undefined
28
34
29
35
30-- | 'evaluate' takes expressions that could potentially have side effects and
36-- | 'evaluate' takes expressions that could potentially have side effects and
31-- \"runs\" them in the 'Query' monad. The returned expressions have no side
37-- \"runs\" them in the 'Query' monad. The returned expressions have no side
32-- effects and can safely be reused.
38-- effects and can safely be reused.
33evaluate :: Table Expr a => a -> Query a
39evaluate :: Table Expr a => a -> Query a
34−evaluate = laterally >=> rebind "eval"
40+evaluate = laterally >=> rebind
35
41
36
42
37laterally :: Table Expr a => a -> Query a
43laterally :: Table Expr a => a -> Query a
@@ -45,5 +51,18 @@ laterally a = Query $ \bindings -> pure $ (Any True,) $
45 go = fromPrimExpr . Opaleye.UnExpr Opaleye.OpIsNotNull
51 go = fromPrimExpr . Opaleye.UnExpr Opaleye.OpIsNotNull
46
52
47
53
54+-- | 'rebind' takes some expressions, and binds each of them to a new
55+-- variable in the SQL. The @a@ returned consists only of these
56+-- variables. It's essentially a @let@ binding for Postgres expressions.
57+rebind :: Table Expr a => a -> Query a
58+rebind a = Query $ \_ -> Opaleye.QueryArr $ \(_, tag) ->
59+ let
60+ tag' = Opaleye.next tag
61+ (a', bindings) = Opaleye.run $
62+ Opaleye.runUnpackspec unpackspec (Opaleye.extractAttr "eval" tag') a
63+ in
64+ ((mempty, a'), \_ -> Opaleye.Rebind True bindings, tag')
65+
66+
48foldl1' :: (a -> a -> a) -> NonEmpty a -> a
67foldl1' :: (a -> a -> a) -> NonEmpty a -> a
49foldl1' f (a :| as) = foldl' f a as
68foldl1' f (a :| as) = foldl' f a as
src/Rel8/Query/Exists.hs+1−1
@@ -12,7 +12,7 @@ import Prelude hiding ( filter )
12
12
13-- opaleye
13-- opaleye
14import qualified Opaleye.Exists as Opaleye
14import qualified Opaleye.Exists as Opaleye
15−import qualified Opaleye.Operators as Opaleye
15+import qualified Opaleye.Operators as Opaleye hiding ( exists )
16
16
17-- rel8
17-- rel8
18import Rel8.Expr ( Expr )
18import Rel8.Expr ( Expr )
src/Rel8/Query/Indexed.hsdeleted−21
@@ -1,21 +0,0 @@
1−module Rel8.Query.Indexed
2− ( indexed
3− )
4−where
5−
6−-- base
7−import Control.Applicative ( liftA2 )
8−import Data.Int ( Int64 )
9−import Prelude
10−
11−-- rel8
12−import Rel8.Expr ( Expr )
13−import Rel8.Expr.Window ( rowNumber )
14−import Rel8.Query ( Query )
15−import Rel8.Query.Window ( window )
16−import Rel8.Table.Window ( currentRow )
17−
18−
19−-- | Pair each row of a query with its index within the query.
20−indexed :: Query a -> Query (Expr Int64, a)
21−indexed = window (liftA2 (,) (subtract 1 <$> rowNumber) currentRow)
src/Rel8/Query/List.hs+7−9
@@ -24,8 +24,8 @@ import Rel8.Expr.Aggregate ( listAggExpr, nonEmptyAggExpr )
24import Rel8.Expr.Opaleye ( mapPrimExpr )
24import Rel8.Expr.Opaleye ( mapPrimExpr )
25import Rel8.Query ( Query )
25import Rel8.Query ( Query )
26import Rel8.Query.Aggregate ( aggregate )
26import Rel8.Query.Aggregate ( aggregate )
27+import Rel8.Query.Evaluate ( rebind )
27import Rel8.Query.Maybe ( optional )
28import Rel8.Query.Maybe ( optional )
28−import Rel8.Query.Rebind ( rebind )
29import Rel8.Schema.HTable.Vectorize ( hunvectorize )
29import Rel8.Schema.HTable.Vectorize ( hunvectorize )
30import Rel8.Schema.Null ( Sql, Unnullify )
30import Rel8.Schema.Null ( Sql, Unnullify )
31import Rel8.Schema.Spec ( Spec( Spec, info ) )
31import Rel8.Schema.Spec ( Spec( Spec, info ) )
@@ -85,9 +85,8 @@ someExpr = aggregate . fmap nonEmptyAggExpr
85--
85--
86-- @catListTable@ is an inverse to 'many'.
86-- @catListTable@ is an inverse to 'many'.
87catListTable :: Table Expr a => ListTable Expr a -> Query a
87catListTable :: Table Expr a => ListTable Expr a -> Query a
88−catListTable (ListTable as) =
89− rebind "unnest" $ fromColumns $ runIdentity $
88+catListTable (ListTable as) = rebind $ fromColumns $ runIdentity $
90− hunvectorize (\Spec {info} -> pure . sunnest info) as
89+ hunvectorize (\Spec {info} -> pure . sunnest info) as
91
90
92
91
93-- | Expand a 'NonEmptyTable' into a 'Query', where each row in the query is an
92-- | Expand a 'NonEmptyTable' into a 'Query', where each row in the query is an
@@ -95,9 +94,8 @@ catListTable (ListTable as) =
95--
94--
96-- @catNonEmptyTable@ is an inverse to 'some'.
95-- @catNonEmptyTable@ is an inverse to 'some'.
97catNonEmptyTable :: Table Expr a => NonEmptyTable Expr a -> Query a
96catNonEmptyTable :: Table Expr a => NonEmptyTable Expr a -> Query a
98−catNonEmptyTable (NonEmptyTable as) =
97+catNonEmptyTable (NonEmptyTable as) = rebind $ fromColumns $ runIdentity $
99− rebind "unnest" $ fromColumns $ runIdentity $
100− hunvectorize (\Spec {info} -> pure . sunnest info) as
98+ hunvectorize (\Spec {info} -> pure . sunnest info) as
101
99
102
100
103-- | Expand an expression that contains a list into a 'Query', where each row
101-- | Expand an expression that contains a list into a 'Query', where each row
@@ -105,7 +103,7 @@ catNonEmptyTable (NonEmptyTable as) =
105--
103--
106-- @catList@ is an inverse to 'manyExpr'.
104-- @catList@ is an inverse to 'manyExpr'.
107catList :: Sql DBType a => Expr [a] -> Query (Expr a)
105catList :: Sql DBType a => Expr [a] -> Query (Expr a)
108−catList = rebind "unnest" . sunnest typeInformation
106+catList = rebind . sunnest typeInformation
109
107
110
108
111-- | Expand an expression that contains a non-empty list into a 'Query', where
109-- | Expand an expression that contains a non-empty list into a 'Query', where
@@ -113,7 +111,7 @@ catList = rebind "unnest" . sunnest typeInformation
113--
111--
114-- @catNonEmpty@ is an inverse to 'someExpr'.
112-- @catNonEmpty@ is an inverse to 'someExpr'.
115catNonEmpty :: Sql DBType a => Expr (NonEmpty a) -> Query (Expr a)
113catNonEmpty :: Sql DBType a => Expr (NonEmpty a) -> Query (Expr a)
116−catNonEmpty = rebind "unnest" . sunnest typeInformation
114+catNonEmpty = rebind . sunnest typeInformation
117
115
118
116
119sunnest :: TypeInformation (Unnullify a) -> Expr (list a) -> Expr a
117sunnest :: TypeInformation (Unnullify a) -> Expr (list a) -> Expr a
src/Rel8/Query/Null.hs−15
@@ -1,8 +1,5 @@
1−{-# language FlexibleContexts #-}
2−
3module Rel8.Query.Null
1module Rel8.Query.Null
4 ( catNull
2 ( catNull
5− , catNullTable
6 )
3 )
7where
4where
8
5
@@ -12,8 +9,6 @@ import Prelude
12-- rel8
9-- rel8
13import Rel8.Expr ( Expr )
10import Rel8.Expr ( Expr )
14import Rel8.Expr.Null ( isNonNull, unsafeUnnullify )
11import Rel8.Expr.Null ( isNonNull, unsafeUnnullify )
15−import Rel8.Table ( Table )
16−import Rel8.Table.Null ( NullTable, isNonNullTable, unsafeUnnullifyTable )
17import Rel8.Query ( Query )
12import Rel8.Query ( Query )
18import Rel8.Query.Filter ( where_ )
13import Rel8.Query.Filter ( where_ )
19
14
@@ -26,13 +21,3 @@ catNull :: Expr (Maybe a) -> Query (Expr a)
26catNull a = do
21catNull a = do
27 where_ $ isNonNull a
22 where_ $ isNonNull a
28 pure $ unsafeUnnullify a
23 pure $ unsafeUnnullify a
29−
30−
31−-- | Filter a 'Query' that might return @nullTable@ to a 'Query' without any
32−-- @nullTable@s.
33−--
34−-- Corresponds to 'Data.Maybe.catMaybes'.
35−catNullTable :: Table Expr a => NullTable Expr a -> Query a
36−catNullTable a = do
37− where_ $ isNonNullTable a
38− pure $ unsafeUnnullifyTable a
src/Rel8/Query/Opaleye.hs+11−11
@@ -41,30 +41,30 @@ zipOpaleyeWith f (Query a) (Query b) = Query $ liftA2 (zipping f) a b
41
41
42unsafePeekQuery :: Query a -> a
42unsafePeekQuery :: Query a -> a
43unsafePeekQuery (Query q) = case q mempty of
43unsafePeekQuery (Query q) = case q mempty of
44− f -> case Opaleye.runStateQueryArr f () Opaleye.start of
44+ Opaleye.QueryArr f -> case f ((), Opaleye.start) of
45 ((_, a), _, _) -> a
45 ((_, a), _, _) -> a
46
46
47
47
48mapping :: ()
48mapping :: ()
49 => (Opaleye.Select a -> Opaleye.Select b)
49 => (Opaleye.Select a -> Opaleye.Select b)
50 -> Opaleye.Select (m, a) -> Opaleye.Select (m, b)
50 -> Opaleye.Select (m, a) -> Opaleye.Select (m, b)
51−mapping f q = Opaleye.stateQueryArr $ \_ tag ->
51+mapping f q@(Opaleye.QueryArr qa) = Opaleye.QueryArr $ \(_, tag) ->
52 let
52 let
53− ((m, _), _, _) = Opaleye.runStateQueryArr q () tag
53+ ((m, _), _, _) = qa ((), tag)
54− q' = (m,) <$> f (snd <$> q)
54+ Opaleye.QueryArr qa' = (m,) <$> f (snd <$> q)
55 in
55 in
56− Opaleye.runStateQueryArr q' () tag
56+ qa' ((), tag)
57
57
58
58
59zipping :: Semigroup m
59zipping :: Semigroup m
60 => (Opaleye.Select a -> Opaleye.Select b -> Opaleye.Select c)
60 => (Opaleye.Select a -> Opaleye.Select b -> Opaleye.Select c)
61 -> Opaleye.Select (m, a) -> Opaleye.Select (m, b) -> Opaleye.Select (m, c)
61 -> Opaleye.Select (m, a) -> Opaleye.Select (m, b) -> Opaleye.Select (m, c)
62−zipping f q q' =
62+zipping f q@(Opaleye.QueryArr qa) q'@(Opaleye.QueryArr qa') =
63− Opaleye.stateQueryArr $ \_ tag ->
63+ Opaleye.QueryArr $ \(_, tag) ->
64 let
64 let
65− ((m, _), _, _) = Opaleye.runStateQueryArr q () tag
65+ ((m, _), _, _) = qa ((), tag)
66− ((m', _), _, _) = Opaleye.runStateQueryArr q' () tag
66+ ((m', _), _, _) = qa' ((), tag)
67 m'' = m <> m'
67 m'' = m <> m'
68− q'' = (m'',) <$> f (snd <$> q) (snd <$> q')
68+ Opaleye.QueryArr qa'' = (m'',) <$> f (snd <$> q) (snd <$> q')
69 in
69 in
70− Opaleye.runStateQueryArr q'' () tag
70+ qa'' ((), tag)
src/Rel8/Query/Rebind.hsdeleted−27
@@ -1,27 +0,0 @@
1−{-# language FlexibleContexts #-}
2−
3−module Rel8.Query.Rebind
4− ( rebind
5− )
6−where
7−
8−-- base
9−import Prelude
10−import Control.Arrow ((<<<))
11−
12−-- opaleye
13−import qualified Opaleye.Internal.Rebind as Opaleye
14−
15−-- rel8
16−import Rel8.Expr ( Expr )
17−import Rel8.Query ( Query )
18−import Rel8.Table ( Table )
19−import Rel8.Table.Opaleye ( unpackspec )
20−import Rel8.Query.Opaleye (fromOpaleye)
21−
22−
23−-- | 'rebind' takes a variable name, some expressions, and binds each of them
24−-- to a new variable in the SQL. The @a@ returned consists only of these
25−-- variables. It's essentially a @let@ binding for Postgres expressions.
26−rebind :: Table Expr a => String -> a -> Query a
27−rebind prefix a = fromOpaleye (Opaleye.rebindExplicitPrefix prefix unpackspec <<< pure a)
src/Rel8/Query/Set.hs+6−6
@@ -23,36 +23,36 @@ import Rel8.Table.Opaleye ( binaryspec )
23
23
24
24
25-- | Combine the results of two queries of the same type, collapsing
25-- | Combine the results of two queries of the same type, collapsing
26−-- duplicates. @union a b@ is the same as the SQL statement @a UNION b@.
26+-- duplicates. @union a b@ is the same as the SQL statement @x UNION b@.
27union :: EqTable a => Query a -> Query a -> Query a
27union :: EqTable a => Query a -> Query a -> Query a
28union = zipOpaleyeWith (Opaleye.unionExplicit binaryspec)
28union = zipOpaleyeWith (Opaleye.unionExplicit binaryspec)
29
29
30
30
31-- | Combine the results of two queries of the same type, retaining duplicates.
31-- | Combine the results of two queries of the same type, retaining duplicates.
32−-- @unionAll a b@ is the same as the SQL statement @a UNION ALL b@.
32+-- @unionAll a b@ is the same as the SQL statement @x UNION ALL b@.
33unionAll :: Table Expr a => Query a -> Query a -> Query a
33unionAll :: Table Expr a => Query a -> Query a -> Query a
34unionAll = zipOpaleyeWith (Opaleye.unionAllExplicit binaryspec)
34unionAll = zipOpaleyeWith (Opaleye.unionAllExplicit binaryspec)
35
35
36
36
37-- | Find the intersection of two queries, collapsing duplicates. @intersect a
37-- | Find the intersection of two queries, collapsing duplicates. @intersect a
38−-- b@ is the same as the SQL statement @a INTERSECT b@.
38+-- b@ is the same as the SQL statement @x INTERSECT b@.
39intersect :: EqTable a => Query a -> Query a -> Query a
39intersect :: EqTable a => Query a -> Query a -> Query a
40intersect = zipOpaleyeWith (Opaleye.intersectExplicit binaryspec)
40intersect = zipOpaleyeWith (Opaleye.intersectExplicit binaryspec)
41
41
42
42
43-- | Find the intersection of two queries, retaining duplicates. @intersectAll
43-- | Find the intersection of two queries, retaining duplicates. @intersectAll
44−-- a b@ is the same as the SQL statement @a INTERSECT ALL b@.
44+-- a b@ is the same as the SQL statement @x INTERSECT ALL b@.
45intersectAll :: EqTable a => Query a -> Query a -> Query a
45intersectAll :: EqTable a => Query a -> Query a -> Query a
46intersectAll = zipOpaleyeWith (Opaleye.intersectAllExplicit binaryspec)
46intersectAll = zipOpaleyeWith (Opaleye.intersectAllExplicit binaryspec)
47
47
48
48
49-- | Find the difference of two queries, collapsing duplicates @except a b@ is
49-- | Find the difference of two queries, collapsing duplicates @except a b@ is
50−-- the same as the SQL statement @a EXCEPT b@.
50+-- the same as the SQL statement @x INTERSECT b@.
51except :: EqTable a => Query a -> Query a -> Query a
51except :: EqTable a => Query a -> Query a -> Query a
52except = zipOpaleyeWith (Opaleye.exceptExplicit binaryspec)
52except = zipOpaleyeWith (Opaleye.exceptExplicit binaryspec)
53
53
54
54
55-- | Find the difference of two queries, retaining duplicates. @exceptAll a b@
55-- | Find the difference of two queries, retaining duplicates. @exceptAll a b@
56−-- is the same as the SQL statement @a EXCEPT ALL b@.
56+-- is the same as the SQL statement @x EXCEPT ALL b@.
57exceptAll :: EqTable a => Query a -> Query a -> Query a
57exceptAll :: EqTable a => Query a -> Query a -> Query a
58exceptAll = zipOpaleyeWith (Opaleye.exceptAllExplicit binaryspec)
58exceptAll = zipOpaleyeWith (Opaleye.exceptAllExplicit binaryspec)
src/Rel8/Query/These.hs+8−8
@@ -48,11 +48,11 @@ import Rel8.Type.Tag ( EitherTag( IsLeft, IsRight ) )
48alignBy :: ()
48alignBy :: ()
49 => (a -> b -> Expr Bool)
49 => (a -> b -> Expr Bool)
50 -> Query a -> Query b -> Query (TheseTable Expr a b)
50 -> Query a -> Query b -> Query (TheseTable Expr a b)
51−alignBy condition = zipOpaleyeWith $ \left right -> Opaleye.stateQueryArr $ \_ t -> case t of
51+alignBy condition = zipOpaleyeWith $ \left right -> Opaleye.QueryArr $ \i -> case i of
52− tag -> (tab, join', tag''')
52+ (_, tag) -> (tab, join', tag''')
53 where
53 where
54− (ma, left', tag') = Opaleye.runStateQueryArr (pure <$> left) () tag
54+ (ma, left', tag') = Opaleye.runSimpleQueryArr (pure <$> left) ((), tag)
55− (mb, right', tag'') = Opaleye.runStateQueryArr (pure <$> right) () tag'
55+ (mb, right', tag'') = Opaleye.runSimpleQueryArr (pure <$> right) ((), tag')
56 MaybeTable hasHere a = ma
56 MaybeTable hasHere a = ma
57 MaybeTable hasThere b = mb
57 MaybeTable hasThere b = mb
58 (hasHere', lbindings) = Opaleye.run $ do
58 (hasHere', lbindings) = Opaleye.run $ do
@@ -60,15 +60,15 @@ alignBy condition = zipOpaleyeWith $ \left right -> Opaleye.stateQueryArr $ \_ t
60 (hasThere', rbindings) = Opaleye.run $ do
60 (hasThere', rbindings) = Opaleye.run $ do
61 traversePrimExpr (Opaleye.extractAttr "hasThere" tag'') hasThere
61 traversePrimExpr (Opaleye.extractAttr "hasThere" tag'') hasThere
62 tag''' = Opaleye.next tag''
62 tag''' = Opaleye.next tag''
63− join = Opaleye.Join Opaleye.FullJoin on left'' right''
63+ join lateral = Opaleye.Join Opaleye.FullJoin on left'' right''
64 where
64 where
65 on = toPrimExpr $ condition (extract a) (extract b)
65 on = toPrimExpr $ condition (extract a) (extract b)
66− left'' = (Opaleye.NonLateral, Opaleye.toPrimQuery (left' <> Opaleye.aRebind lbindings))
66+ left'' = (lateral, Opaleye.Rebind True lbindings left')
67− right'' = (Opaleye.NonLateral, Opaleye.toPrimQuery (right' <> Opaleye.aRebind rbindings))
67+ right'' = (lateral, Opaleye.Rebind True rbindings right')
68 ma' = MaybeTable hasHere' a
68 ma' = MaybeTable hasHere' a
69 mb' = MaybeTable hasThere' b
69 mb' = MaybeTable hasThere' b
70 tab = TheseTable {here = ma', there = mb'}
70 tab = TheseTable {here = ma', there = mb'}
71− join' = Opaleye.aProduct join
71+ join' lateral input = Opaleye.times lateral input (join lateral)
72
72
73
73
74keepHereTable :: TheseTable Expr a b -> Query (a, MaybeTable Expr b)
74keepHereTable :: TheseTable Expr a b -> Query (a, MaybeTable Expr b)
src/Rel8/Query/Window.hsdeleted−26
@@ -1,26 +0,0 @@
1−module Rel8.Query.Window
2− ( window
3− )
4−where
5−
6−-- base
7−import Prelude ()
8−
9−-- opaleye
10−import qualified Opaleye.Window as Opaleye
11−
12−-- rel8
13−import Rel8.Query ( Query )
14−import Rel8.Query.Opaleye ( mapOpaleye )
15−import Rel8.Window ( Window( Window ) )
16−
17−
18−-- | 'window' runs a query composed of expressions containing
19−-- [window functions](https://www.postgresql.org/docs/current/tutorial-window.html).
20−-- 'window' is similar to 'Rel8.aggregate', with the main difference being
21−-- that in a window query, each input row corresponds to one output row,
22−-- whereas aggregation queries fold the entire input query down into a single
23−-- row. To put this into a Haskell context, 'Rel8.aggregate' is to 'foldl' as
24−-- 'window' is to 'scanl'.
25−window :: Window a b -> Query a -> Query b
26−window (Window a) = mapOpaleye (Opaleye.runWindows a)
src/Rel8/Schema/Context/Nullify.hs+14−6
@@ -18,17 +18,18 @@ where
18import Data.Bool ( bool )
18import Data.Bool ( bool )
19import Data.Functor.Identity ( Identity( Identity ) )
19import Data.Functor.Identity ( Identity( Identity ) )
20import Data.Kind ( Constraint, Type )
20import Data.Kind ( Constraint, Type )
21+import Data.Monoid ( getFirst )
21import Prelude hiding ( null )
22import Prelude hiding ( null )
22
23
23-- opaleye
24-- opaleye
24import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
25import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
25
26
26-- rel8
27-- rel8
27−import Rel8.Aggregate ( Aggregate(..), zipOutputs )
28+import Rel8.Aggregate ( Aggregate( Aggregate ), foldInputs, mapInputs )
28import Rel8.Expr ( Expr )
29import Rel8.Expr ( Expr )
29import Rel8.Expr.Bool ( boolExpr )
30import Rel8.Expr.Bool ( boolExpr )
30import Rel8.Expr.Null ( nullify, unsafeUnnullify )
31import Rel8.Expr.Null ( nullify, unsafeUnnullify )
31−import Rel8.Expr.Opaleye ( fromPrimExpr )
32+import Rel8.Expr.Opaleye ( fromPrimExpr, toPrimExpr )
32import Rel8.Kind.Context ( SContext(..) )
33import Rel8.Kind.Context ( SContext(..) )
33import Rel8.Schema.Field ( Field )
34import Rel8.Schema.Field ( Field )
34import qualified Rel8.Schema.Kind as K
35import qualified Rel8.Schema.Kind as K
@@ -94,10 +95,17 @@ guarder :: ()
94 -> context (Maybe a)
95 -> context (Maybe a)
95 -> context (Maybe a)
96 -> context (Maybe a)
96guarder = \case
97guarder = \case
97− SAggregate -> \tag _ isNonNull -> zipOutputs (sguard . isNonNull) tag
98+ SAggregate -> \tag _ isNonNull (Aggregate a) ->
99+ let
100+ mtag = foldInputs (\_ -> pure . fromPrimExpr) tag
101+ run = maybe id (sguard . isNonNull) (getFirst mtag)
102+ in
103+ mapInputs (toPrimExpr . run . fromPrimExpr) $
104+ Aggregate $
105+ run <$> a
98− SExpr -> \tag _ isNonNull -> sguard (isNonNull tag)
106+ SExpr -> \tag _ isNonNull a -> sguard (isNonNull tag) a
99− SField -> \_ _ _ -> id
107+ SField -> \_ _ _ field -> field
100− SName -> \_ _ _ -> id
108+ SName -> \_ _ _ name -> name
101 SResult -> \(Identity tag) isNonNull _ (Identity a) ->
109 SResult -> \(Identity tag) isNonNull _ (Identity a) ->
102 Identity (bool Nothing a (isNonNull tag))
110 Identity (bool Nothing a (isNonNull tag))
103
111
src/Rel8/Schema/HTable.hs+2−29
@@ -16,12 +16,11 @@
16module Rel8.Schema.HTable
16module Rel8.Schema.HTable
17 ( HTable (HField, HConstrainTable)
17 ( HTable (HField, HConstrainTable)
18 , hfield, htabulate, htraverse, hdicts, hspecs
18 , hfield, htabulate, htraverse, hdicts, hspecs
19− , hfoldMap, hmap, htabulateA, htraverseP, htraversePWithField
19+ , hmap, htabulateA
20 )
20 )
21where
21where
22
22
23-- base
23-- base
24−import Data.Functor.Const ( Const( Const ), getConst )
25import Data.Kind ( Constraint, Type )
24import Data.Kind ( Constraint, Type )
26import Data.Functor.Compose ( Compose( Compose ), getCompose )
25import Data.Functor.Compose ( Compose( Compose ), getCompose )
27import Data.Proxy ( Proxy )
26import Data.Proxy ( Proxy )
@@ -33,12 +32,6 @@ import GHC.Generics
33 )
32 )
34import Prelude
33import Prelude
35
34
36−-- profunctors
37−import Data.Profunctor ( rmap, Profunctor (lmap) )
38−
39−-- product-profunctors
40−import Data.Profunctor.Product ( ProductProfunctor ((****)) )
41−
42-- rel8
35-- rel8
43import Rel8.Schema.Dict ( Dict )
36import Rel8.Schema.Dict ( Dict )
44import Rel8.Schema.Spec ( Spec )
37import Rel8.Schema.Spec ( Spec )
@@ -48,6 +41,7 @@ import qualified Rel8.Schema.Kind as K
48-- semigroupoids
41-- semigroupoids
49import Data.Functor.Apply ( Apply, (<.>) )
42import Data.Functor.Apply ( Apply, (<.>) )
50
43
44+
51-- | A @HTable@ is a functor-indexed/higher-kinded data type that is
45-- | A @HTable@ is a functor-indexed/higher-kinded data type that is
52-- representable ('htabulate'/'hfield'), constrainable ('hdicts'), and
46-- representable ('htabulate'/'hfield'), constrainable ('hdicts'), and
53-- specified ('hspecs').
47-- specified ('hspecs').
@@ -120,11 +114,6 @@ class HTable t where
120 {-# INLINABLE hspecs #-}
114 {-# INLINABLE hspecs #-}
121
115
122
116
123−hfoldMap :: (HTable t, Semigroup s)
124− => (forall a. context a -> s) -> t context -> s
125−hfoldMap f a = getConst $ htraverse (Const . f) a
126−
127−
128hmap :: HTable t
117hmap :: HTable t
129 => (forall a. context a -> context' a) -> t context -> t context'
118 => (forall a. context a -> context' a) -> t context -> t context'
130hmap f a = htabulate $ \field -> f (hfield a field)
119hmap f a = htabulate $ \field -> f (hfield a field)
@@ -135,22 +124,6 @@ htabulateA :: (HTable t, Apply m)
135htabulateA f = htraverse getCompose $ htabulate $ Compose . f
124htabulateA f = htraverse getCompose $ htabulate $ Compose . f
136{-# INLINABLE htabulateA #-}
125{-# INLINABLE htabulateA #-}
137
126
138−newtype ApplyP p a b = ApplyP { unApplyP :: p a b }
139−
140−instance Profunctor p => Functor (ApplyP p a) where
141− fmap f = ApplyP . rmap f . unApplyP
142−
143−instance ProductProfunctor p => Apply (ApplyP p a) where
144− ApplyP f <.> ApplyP x = ApplyP (rmap id f **** x)
145−
146−htraverseP :: (HTable t, ProductProfunctor p)
147− => (forall a. p (f a) (g a)) -> p (t f) (t g)
148−htraverseP f = htraversePWithField (const f)
149−
150−htraversePWithField :: (HTable t, ProductProfunctor p)
151− => (forall a. HField t a -> p (f a) (g a)) -> p (t f) (t g)
152−htraversePWithField f = unApplyP $ htabulateA $ \field -> ApplyP $
153− lmap (flip hfield field) (f field)
154
127
155type GHField :: K.HTable -> Type -> Type
128type GHField :: K.HTable -> Type -> Type
156newtype GHField t a = GHField (HField (GHColumns (Rep (t Proxy))) a)
129newtype GHField t a = GHField (HField (GHColumns (Rep (t Proxy))) a)
src/Rel8/Schema/HTable/Vectorize.hs+2−10
@@ -22,12 +22,11 @@ module Rel8.Schema.HTable.Vectorize
22 , hvectorize, hunvectorize
22 , hvectorize, hunvectorize
23 , happend, hempty
23 , happend, hempty
24 , hproject
24 , hproject
25− , hcolumn
26 )
25 )
27where
26where
28
27
29-- base
28-- base
30−import Data.Kind ( Constraint, Type )
29+import Data.Kind ( Type )
31import Data.List.NonEmpty ( NonEmpty )
30import Data.List.NonEmpty ( NonEmpty )
32import GHC.Generics (Generic)
31import GHC.Generics (Generic)
33import Prelude
32import Prelude
@@ -37,11 +36,9 @@ import Rel8.FCF ( Eval, Exp )
37import Rel8.Schema.Dict ( Dict( Dict ) )
36import Rel8.Schema.Dict ( Dict( Dict ) )
38import qualified Rel8.Schema.Kind as K
37import qualified Rel8.Schema.Kind as K
39import Rel8.Schema.HTable ( HTable, hfield, htabulate, htabulateA, hspecs )
38import Rel8.Schema.HTable ( HTable, hfield, htabulate, htabulateA, hspecs )
40−import Rel8.Schema.HTable.Identity ( HIdentity( HIdentity ) )
41import Rel8.Schema.HTable.MapTable
39import Rel8.Schema.HTable.MapTable
42− ( HMapTable( HMapTable ), HMapTableField( HMapTableField )
40+ ( HMapTable, HMapTableField( HMapTableField )
43 , MapSpec, mapInfo
41 , MapSpec, mapInfo
44− , Precompose( Precompose )
45 )
42 )
46import qualified Rel8.Schema.HTable.MapTable as HMapTable ( hproject )
43import qualified Rel8.Schema.HTable.MapTable as HMapTable ( hproject )
47import Rel8.Schema.Null ( Unnullify, NotNull, Nullity( NotNull ) )
44import Rel8.Schema.Null ( Unnullify, NotNull, Nullity( NotNull ) )
@@ -53,7 +50,6 @@ import Rel8.Type.Information ( TypeInformation )
53import Data.Zip ( Unzip, Zip, Zippy(..) )
50import Data.Zip ( Unzip, Zip, Zippy(..) )
54
51
55
52
56−type Vector :: (Type -> Type) -> Constraint
57class Vector list where
53class Vector list where
58 listNotNull :: proxy a -> Dict NotNull (list a)
54 listNotNull :: proxy a -> Dict NotNull (list a)
59 vectorTypeInformation :: ()
55 vectorTypeInformation :: ()
@@ -137,7 +133,3 @@ hproject :: ()
137 => (forall ctx. t ctx -> t' ctx)
133 => (forall ctx. t ctx -> t' ctx)
138 -> HVectorize list t context -> HVectorize list t' context
134 -> HVectorize list t context -> HVectorize list t' context
139hproject f (HVectorize a) = HVectorize (HMapTable.hproject f a)
135hproject f (HVectorize a) = HVectorize (HMapTable.hproject f a)
140−
141−
142−hcolumn :: HVectorize list (HIdentity a) context -> context (list a)
143−hcolumn (HVectorize (HMapTable (HIdentity (Precompose a)))) = a
src/Rel8/Schema/Table.hs−3
@@ -2,7 +2,6 @@
2{-# language DerivingStrategies #-}
2{-# language DerivingStrategies #-}
3{-# language DisambiguateRecordFields #-}
3{-# language DisambiguateRecordFields #-}
4{-# language NamedFieldPuns #-}
4{-# language NamedFieldPuns #-}
5−{-# language StandaloneKindSignatures #-}
6
5
7module Rel8.Schema.Table
6module Rel8.Schema.Table
8 ( TableSchema(..)
7 ( TableSchema(..)
@@ -11,7 +10,6 @@ module Rel8.Schema.Table
11where
10where
12
11
13-- base
12-- base
14−import Data.Kind ( Type )
15import Prelude
13import Prelude
16
14
17-- opaleye
15-- opaleye
@@ -28,7 +26,6 @@ import Text.PrettyPrint ( Doc )
28--
26--
29-- For each selectable table in your database, you should provide a
27-- For each selectable table in your database, you should provide a
30-- @TableSchema@ in order to interact with the table via Rel8.
28-- @TableSchema@ in order to interact with the table via Rel8.
31−type TableSchema :: Type -> Type
32data TableSchema names = TableSchema
29data TableSchema names = TableSchema
33 { name :: String
30 { name :: String
34 -- ^ The name of the table.
31 -- ^ The name of the table.
src/Rel8/Statement/Insert.hs+2−2
@@ -35,7 +35,7 @@ import Rel8.Schema.Name ( Name, Selects, ppColumn )
35import Rel8.Schema.Table ( TableSchema(..), ppTable )
35import Rel8.Schema.Table ( TableSchema(..), ppTable )
36import Rel8.Statement.OnConflict ( OnConflict, ppOnConflict )
36import Rel8.Statement.OnConflict ( OnConflict, ppOnConflict )
37import Rel8.Statement.Returning ( Returning, decodeReturning, ppReturning )
37import Rel8.Statement.Returning ( Returning, decodeReturning, ppReturning )
38−import Rel8.Statement.Select ( ppRows )
38+import Rel8.Statement.Select ( ppSelect )
39import Rel8.Table ( Table )
39import Rel8.Table ( Table )
40import Rel8.Table.Name ( showNames )
40import Rel8.Table.Name ( showNames )
41
41
@@ -66,7 +66,7 @@ ppInsert :: Insert a -> Doc
66ppInsert Insert {..} =
66ppInsert Insert {..} =
67 text "INSERT INTO" <+>
67 text "INSERT INTO" <+>
68 ppInto into $$
68 ppInto into $$
69− ppRows rows $$
69+ ppSelect rows $$
70 ppOnConflict into onConflict $$
70 ppOnConflict into onConflict $$
71 ppReturning into returning
71 ppReturning into returning
72
72
src/Rel8/Statement/Select.hs+4−34
@@ -3,7 +3,6 @@
3{-# language FlexibleContexts #-}
3{-# language FlexibleContexts #-}
4{-# language MonoLocalBinds #-}
4{-# language MonoLocalBinds #-}
5{-# language ScopedTypeVariables #-}
5{-# language ScopedTypeVariables #-}
6−{-# language StandaloneKindSignatures #-}
7{-# language TypeApplications #-}
6{-# language TypeApplications #-}
8
7
9module Rel8.Statement.Select
8module Rel8.Statement.Select
@@ -12,13 +11,10 @@ module Rel8.Statement.Select
12
11
13 , Optimized(..)
12 , Optimized(..)
14 , ppPrimSelect
13 , ppPrimSelect
15− , ppRows
16 )
14 )
17where
15where
18
16
19-- base
17-- base
20−import Data.Foldable ( toList )
21−import Data.Kind ( Type )
22import Data.Void ( Void )
18import Data.Void ( Void )
23import Prelude hiding ( undefined )
19import Prelude hiding ( undefined )
24
20
@@ -28,15 +24,12 @@ import qualified Hasql.Encoders as Hasql
28import qualified Hasql.Statement as Hasql
24import qualified Hasql.Statement as Hasql
29
25
30-- opaleye
26-- opaleye
31−import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
32import qualified Opaleye.Internal.HaskellDB.Sql as Opaleye
27import qualified Opaleye.Internal.HaskellDB.Sql as Opaleye
33−import qualified Opaleye.Internal.HaskellDB.Sql.Print as Opaleye
34import qualified Opaleye.Internal.PrimQuery as Opaleye
28import qualified Opaleye.Internal.PrimQuery as Opaleye
35import qualified Opaleye.Internal.Print as Opaleye
29import qualified Opaleye.Internal.Print as Opaleye
36import qualified Opaleye.Internal.Optimize as Opaleye
30import qualified Opaleye.Internal.Optimize as Opaleye
37import qualified Opaleye.Internal.QueryArr as Opaleye hiding ( Select )
31import qualified Opaleye.Internal.QueryArr as Opaleye hiding ( Select )
38−import qualified Opaleye.Internal.Sql as Opaleye hiding ( Values )
32+import qualified Opaleye.Internal.Sql as Opaleye
39−import qualified Opaleye.Internal.Tag as Opaleye
40
33
41-- pretty
34-- pretty
42import Text.PrettyPrint ( Doc )
35import Text.PrettyPrint ( Doc )
@@ -52,7 +45,6 @@ import Rel8.Table ( Table )
52import Rel8.Table.Cols ( toCols )
45import Rel8.Table.Cols ( toCols )
53import Rel8.Table.Name ( namesFromLabels )
46import Rel8.Table.Name ( namesFromLabels )
54import Rel8.Table.Opaleye ( castTable, exprsWithNames )
47import Rel8.Table.Opaleye ( castTable, exprsWithNames )
55−import qualified Rel8.Table.Opaleye as T
56import Rel8.Table.Serialize ( Serializable, parse )
48import Rel8.Table.Serialize ( Serializable, parse )
57import Rel8.Table.Undefined ( undefined )
49import Rel8.Table.Undefined ( undefined )
58
50
@@ -88,38 +80,16 @@ ppSelect query =
88 never = pure (toPrimExpr false)
80 never = pure (toPrimExpr false)
89
81
90
82
91−ppRows :: Table Expr a => Query a -> Doc
92−ppRows query = case optimize primQuery of
93− -- Special case VALUES because we can't use DEFAULT inside a SELECT
94− Optimized (Opaleye.Values symbols rows)
95− | eqSymbols symbols (toList (T.exprs a)) ->
96− Opaleye.ppValues_ (map Opaleye.sqlExpr <$> toList rows)
97− _ -> ppSelect query
98− where
99− (a, primQuery, _) = Opaleye.runSimpleQueryArrStart (toOpaleye query) ()
100−
101− eqSymbols (symbol : symbols) (Opaleye.AttrExpr symbol' : exprs)
102− | eqSymbol symbol symbol' = eqSymbols symbols exprs
103− | otherwise = False
104− eqSymbols [] [] = True
105− eqSymbols _ _ = False
106−
107− eqSymbol
108− (Opaleye.Symbol name (Opaleye.UnsafeTag tag))
109− (Opaleye.Symbol name' (Opaleye.UnsafeTag tag'))
110− = name == name' && tag == tag'
111−
112−
113ppPrimSelect :: Query a -> (Optimized Doc, a)
83ppPrimSelect :: Query a -> (Optimized Doc, a)
114ppPrimSelect query =
84ppPrimSelect query =
115 (Opaleye.ppSql . primSelect <$> optimize primQuery, a)
85 (Opaleye.ppSql . primSelect <$> optimize primQuery, a)
116 where
86 where
87+ (a, primQuery, _) =
117− (a, primQuery, _) = Opaleye.runSimpleQueryArrStart (toOpaleye query) ()
88+ Opaleye.runSimpleQueryArrStart (toOpaleye query) ()
118
89
119
90
120−type Optimized :: Type -> Type
121data Optimized a = Empty | Unit | Optimized a
91data Optimized a = Empty | Unit | Optimized a
122− deriving stock (Functor, Foldable, Traversable, Show)
92+ deriving stock (Functor, Foldable, Traversable)
123
93
124
94
125optimize :: Opaleye.PrimQuery' a -> Optimized (Opaleye.PrimQuery' Void)
95optimize :: Opaleye.PrimQuery' a -> Optimized (Opaleye.PrimQuery' Void)
src/Rel8/Statement/View.hs+12−31
@@ -3,16 +3,18 @@
3
3
4module Rel8.Statement.View
4module Rel8.Statement.View
5 ( createView
5 ( createView
6− , createOrReplaceView
7 )
6 )
8where
7where
9
8
10-- base
9-- base
10+import Control.Exception ( throwIO )
11import Prelude
11import Prelude
12
12
13-- hasql
13-- hasql
14+import Hasql.Connection ( Connection )
14import qualified Hasql.Decoders as Hasql
15import qualified Hasql.Decoders as Hasql
15import qualified Hasql.Encoders as Hasql
16import qualified Hasql.Encoders as Hasql
17+import qualified Hasql.Session as Hasql
16import qualified Hasql.Statement as Hasql
18import qualified Hasql.Statement as Hasql
17
19
18-- rel8
20-- rel8
@@ -30,49 +32,28 @@ import qualified Data.Text as Text
30import Data.Text.Encoding ( encodeUtf8 )
32import Data.Text.Encoding ( encodeUtf8 )
31
33
32
34
33−data CreateView = Create | CreateOrReplace
34−
35−
36-- | Given a 'TableSchema' and 'Query', @createView@ runs a @CREATE VIEW@
35-- | Given a 'TableSchema' and 'Query', @createView@ runs a @CREATE VIEW@
37-- statement that will save the given query as a view. This can be useful if
36-- statement that will save the given query as a view. This can be useful if
38-- you want to share Rel8 queries with other applications.
37-- you want to share Rel8 queries with other applications.
39createView :: Selects names exprs
38createView :: Selects names exprs
40− => TableSchema names -> Query exprs -> Hasql.Statement () ()
39+ => Connection -> TableSchema names -> Query exprs -> IO ()
41−createView =
40+createView connection schema query =
42− createViewGeneric Create
41+ Hasql.run session connection >>= either throwIO pure
43−
44−
45−-- | Given a 'TableSchema' and 'Query', @createOrReplaceView@ runs a
46−-- @CREATE OR REPLACE VIEW@ statement that will save the given query
47−-- as a view, replacing the current view definition if it exists and
48−-- adheres to the restrictions in place for replacing a view in
49−-- PostgreSQL.
50−createOrReplaceView :: Selects names exprs
51− => TableSchema names -> Query exprs -> Hasql.Statement () ()
52−createOrReplaceView =
53− createViewGeneric CreateOrReplace
54−
55−
56−createViewGeneric :: Selects names exprs
57− => CreateView -> TableSchema names -> Query exprs -> Hasql.Statement () ()
58−createViewGeneric replace schema query =
59− Hasql.Statement bytes params decode prepare
60 where
42 where
43+ session = Hasql.statement () statement
44+ statement = Hasql.Statement bytes params decode prepare
61 bytes = encodeUtf8 (Text.pack sql)
45 bytes = encodeUtf8 (Text.pack sql)
62 params = Hasql.noParams
46 params = Hasql.noParams
63 decode = Hasql.noResult
47 decode = Hasql.noResult
64 prepare = False
48 prepare = False
65 sql = show doc
49 sql = show doc
66− doc = ppCreateView schema query replace
50+ doc = ppCreateView schema query
67
51
68
52
69ppCreateView :: Selects names exprs
53ppCreateView :: Selects names exprs
70− => TableSchema names -> Query exprs -> CreateView -> Doc
54+ => TableSchema names -> Query exprs -> Doc
71−ppCreateView schema query replace =
55+ppCreateView schema query =
72− createOrReplace replace <+>
56+ text "CREATE VIEW" <+>
73 ppInto schema $$
57 ppInto schema $$
74 text "AS" <+>
58 text "AS" <+>
75 ppSelect query
59 ppSelect query
76− where
77− createOrReplace Create = text "CREATE VIEW"
78− createOrReplace CreateOrReplace = text "CREATE OR REPLACE VIEW"
src/Rel8/Table/Aggregate.hs−12
@@ -37,18 +37,6 @@ import Rel8.Type.Eq ( DBEq )
37
37
38-- | Group equal tables together. This works by aggregating each column in the
38-- | Group equal tables together. This works by aggregating each column in the
39-- given table with 'groupByExpr'.
39-- given table with 'groupByExpr'.
40−--
41−-- For example, if we have a table of items, we could group the items by the
42−-- order they belong to:
43−--
44−-- @
45−-- itemsByOrder :: Query (OrderId Expr, ListTable Expr (Item Expr))
46−-- itemsByOrder = aggregate $ do
47−-- item <- each itemSchema
48−-- let orderId = groupBy (itemOrderId item)
49−-- let orderItems = listAgg item
50−-- pure (orderId, orderItems)
51−-- @
52groupBy :: forall exprs aggregates. (EqTable exprs, Aggregates aggregates exprs)
40groupBy :: forall exprs aggregates. (EqTable exprs, Aggregates aggregates exprs)
53 => exprs -> aggregates
41 => exprs -> aggregates
54groupBy = fromColumns . hgroupBy (eqTable @exprs) . toColumns
42groupBy = fromColumns . hgroupBy (eqTable @exprs) . toColumns
src/Rel8/Table/HKD.hs−1
@@ -157,7 +157,6 @@ instance
157 => HKDable a
157 => HKDable a
158
158
159
159
160−type Top_ :: Constraint
161class Top_
160class Top_
162instance Top_
161instance Top_
163
162
src/Rel8/Table/List.hs+3−14
@@ -11,9 +11,7 @@
11
11
12module Rel8.Table.List
12module Rel8.Table.List
13 ( ListTable(..)
13 ( ListTable(..)
14− , ($*)
15− , listTable
16− , nameListTable
14+ , listTable, nameListTable
17 )
15 )
18where
16where
19
17
@@ -30,7 +28,7 @@ import Rel8.Schema.HTable.List ( HListTable )
30import Rel8.Schema.HTable.Vectorize
28import Rel8.Schema.HTable.Vectorize
31 ( hvectorize, hunvectorize
29 ( hvectorize, hunvectorize
32 , happend, hempty
30 , happend, hempty
33− , hproject, hcolumn
31+ , hproject
34 )
32 )
35import qualified Rel8.Schema.Kind as K
33import qualified Rel8.Schema.Kind as K
36import Rel8.Schema.Name ( Name( Name ) )
34import Rel8.Schema.Name ( Name( Name ) )
@@ -48,9 +46,7 @@ import Rel8.Table.Alternative
48 )
46 )
49import Rel8.Table.Eq ( EqTable, eqTable )
47import Rel8.Table.Eq ( EqTable, eqTable )
50import Rel8.Table.Ord ( OrdTable, ordTable )
48import Rel8.Table.Ord ( OrdTable, ordTable )
51−import Rel8.Table.Projection
49+import Rel8.Table.Projection ( Projectable, project, apply )
52− ( Projectable, Projecting, Projection, project, apply
53− )
54import Rel8.Table.Serialize ( ToExprs )
50import Rel8.Table.Serialize ( ToExprs )
55
51
56
52
@@ -120,13 +116,6 @@ instance (context ~ Expr, Table Expr a) =>
120 mempty = ListTable $ hempty $ \Spec {info} -> sempty info
116 mempty = ListTable $ hempty $ \Spec {info} -> sempty info
121
117
122
118
123−-- | Project a single expression out of a 'ListTable'.
124−($*) :: Projecting a (Expr b)
125− => Projection a (Expr b) -> ListTable Expr a -> Expr [b]
126−f $* ListTable a = hcolumn $ hproject (apply f) a
127−infixl 4 $*
128−
129−
130-- | Construct a @ListTable@ from a list of expressions.
119-- | Construct a @ListTable@ from a list of expressions.
131listTable :: Table Expr a => [a] -> ListTable Expr a
120listTable :: Table Expr a => [a] -> ListTable Expr a
132listTable =
121listTable =
src/Rel8/Table/Maybe.hs−6
@@ -15,7 +15,6 @@ module Rel8.Table.Maybe
15 ( MaybeTable(..)
15 ( MaybeTable(..)
16 , maybeTable, nothingTable, justTable
16 , maybeTable, nothingTable, justTable
17 , isNothingTable, isJustTable
17 , isNothingTable, isJustTable
18− , fromMaybeTable
19 , ($?)
18 , ($?)
20 , aggregateMaybeTable
19 , aggregateMaybeTable
21 , nameMaybeTable
20 , nameMaybeTable
@@ -207,11 +206,6 @@ justTable :: a -> MaybeTable Expr a
207justTable = MaybeTable mempty . pure
206justTable = MaybeTable mempty . pure
208
207
209
208
210−-- | 'Data.Maybe.fromMaybe' for 'MaybeTable's.
211−fromMaybeTable :: Table Expr a => a -> MaybeTable Expr a -> a
212−fromMaybeTable fallback = maybeTable fallback id
213−
214−
215-- | Project a single expression out of a 'MaybeTable'. You can think of this
209-- | Project a single expression out of a 'MaybeTable'. You can think of this
216-- operator like the '$' operator, but it also has the ability to return
210-- operator like the '$' operator, but it also has the ability to return
217-- @null@.
211-- @null@.
src/Rel8/Table/NonEmpty.hs+3−14
@@ -11,9 +11,7 @@
11
11
12module Rel8.Table.NonEmpty
12module Rel8.Table.NonEmpty
13 ( NonEmptyTable(..)
13 ( NonEmptyTable(..)
14− , ($+)
15− , nonEmptyTable
16− , nameNonEmptyTable
14+ , nonEmptyTable, nameNonEmptyTable
17 )
15 )
18where
16where
19
17
@@ -31,7 +29,7 @@ import Rel8.Schema.HTable.NonEmpty ( HNonEmptyTable )
31import Rel8.Schema.HTable.Vectorize
29import Rel8.Schema.HTable.Vectorize
32 ( hvectorize, hunvectorize
30 ( hvectorize, hunvectorize
33 , happend
31 , happend
34− , hproject, hcolumn
32+ , hproject
35 )
33 )
36import qualified Rel8.Schema.Kind as K
34import qualified Rel8.Schema.Kind as K
37import Rel8.Schema.Name ( Name( Name ) )
35import Rel8.Schema.Name ( Name( Name ) )
@@ -46,9 +44,7 @@ import Rel8.Table
46import Rel8.Table.Alternative ( AltTable, (<|>:) )
44import Rel8.Table.Alternative ( AltTable, (<|>:) )
47import Rel8.Table.Eq ( EqTable, eqTable )
45import Rel8.Table.Eq ( EqTable, eqTable )
48import Rel8.Table.Ord ( OrdTable, ordTable )
46import Rel8.Table.Ord ( OrdTable, ordTable )
49−import Rel8.Table.Projection
47+import Rel8.Table.Projection ( Projectable, project, apply )
50− ( Projectable, Projecting, Projection, project, apply
51− )
52import Rel8.Table.Serialize ( ToExprs )
48import Rel8.Table.Serialize ( ToExprs )
53
49
54
50
@@ -114,13 +110,6 @@ instance (Table Expr a, context ~ Expr) => Semigroup (NonEmptyTable context a)
114 happend (const sappend1) as bs
110 happend (const sappend1) as bs
115
111
116
112
117−-- | Project a single expression out of a 'NonEmptyTable'.
118−($+) :: Projecting a (Expr b)
119− => Projection a (Expr b) -> NonEmptyTable Expr a -> Expr (NonEmpty b)
120−f $+ NonEmptyTable a = hcolumn $ hproject (apply f) a
121−infixl 4 $+
122−
123−
124-- | Construct a @NonEmptyTable@ from a non-empty list of expressions.
113-- | Construct a @NonEmptyTable@ from a non-empty list of expressions.
125nonEmptyTable :: Table Expr a => NonEmpty a -> NonEmptyTable Expr a
114nonEmptyTable :: Table Expr a => NonEmpty a -> NonEmptyTable Expr a
126nonEmptyTable =
115nonEmptyTable =
src/Rel8/Table/Null.hsdeleted−147
@@ -1,147 +0,0 @@
1−{-# language DataKinds #-}
2−{-# language FlexibleContexts #-}
3−{-# language FlexibleInstances #-}
4−{-# language MultiParamTypeClasses #-}
5−{-# language ScopedTypeVariables #-}
6−{-# language StandaloneKindSignatures #-}
7−{-# language TypeApplications #-}
8−{-# language TypeFamilies #-}
9−{-# language UndecidableInstances #-}
10−
11−module Rel8.Table.Null
12− ( NullTable(..)
13− , nullableTable, nullTable, nullifyTable, unsafeUnnullifyTable
14− , isNullTable, isNonNullTable
15− , nameNullTable
16− , toMaybeTable, toNullTable
17− )
18−where
19−
20−-- base
21−import Data.Kind ( Type )
22−import Prelude hiding ( null, undefined )
23−
24−-- comonad
25−import Control.Comonad ( extract )
26−
27−-- rel8
28−import Rel8.Expr ( Expr )
29−import Rel8.Expr.Bool ( not_ )
30−import Rel8.Kind.Context ( Reifiable )
31−import qualified Rel8.Schema.Kind as K
32−import Rel8.Schema.Name ( Name )
33−import Rel8.Table
34− ( Table, Columns, Context, fromColumns, toColumns
35− , FromExprs, fromResult, toResult
36− , Transpose
37− )
38−import Rel8.Table.Alternative
39− ( AltTable, (<|>:)
40− , AlternativeTable, emptyTable
41− )
42−import Rel8.Table.Bool ( bool )
43−import Rel8.Table.Eq ( EqTable, eqTable )
44−import Rel8.Table.Maybe ( MaybeTable, justTable, maybeTable, nothingTable )
45−import Rel8.Table.Nullify ( Nullify, isNull )
46−import Rel8.Table.Ord ( OrdTable, ordTable )
47−import Rel8.Table.Projection ( Projectable, project )
48−import Rel8.Table.Serialize ( ToExprs )
49−import Rel8.Table.Undefined ( undefined )
50−
51−
52−-- | @NullTable t@ is the table @t@, but where all the columns in @t@ have the
53−-- possibility of being 'Rel8.null'. This is very similar to
54−-- 'Rel8.MaybeTable', except that it does not use an extra tag field, so it
55−-- cannot distinguish between @Nothing@ and @Just Nothing@ if nested. In other
56−-- words, if all of the columns of the @t@ passed to @NullTable@ are already
57−-- nullable, then @NullTable@ has no effect.
58−type NullTable :: K.Context -> Type -> Type
59−newtype NullTable context a = NullTable (Nullify context a)
60−
61−
62−instance Projectable (NullTable context) where
63− project f (NullTable a) = NullTable (project f a)
64−
65−
66−instance context ~ Expr => AltTable (NullTable context) where
67− ma <|>: mb = bool ma mb (isNullTable ma)
68−
69−
70−instance context ~ Expr => AlternativeTable (NullTable context) where
71− emptyTable = nullTable
72−
73−
74−instance (Table context a, Reifiable context, context ~ context') =>
75− Table context' (NullTable context a)
76− where
77− type Columns (NullTable context a) = Columns (Nullify context a)
78− type Context (NullTable context a) = Context (Nullify context a)
79− type FromExprs (NullTable context a) = FromExprs (Nullify context a)
80− type Transpose to (NullTable context a) = NullTable to (Transpose to a)
81−
82− toColumns (NullTable a) = toColumns a
83− fromColumns = NullTable . fromColumns
84−
85− toResult = toResult @_ @(Nullify context a)
86− fromResult = fromResult @_ @(Nullify context a)
87−
88−
89−instance (EqTable a, context ~ Expr) => EqTable (NullTable context a) where
90− eqTable = eqTable @(Nullify context a)
91−
92−
93−instance (OrdTable a, context ~ Expr) => OrdTable (NullTable context a) where
94− ordTable = ordTable @(Nullify context a)
95−
96−
97−instance (ToExprs exprs a, context ~ Expr) =>
98− ToExprs (NullTable context exprs) (Maybe a)
99−
100−
101−-- | Check if any of the non-nullable fields of @a@ are 'Rel8.null' under the
102−-- 'NullTable'. Returns 'Rel8.false' if @a@ has no non-nullable fields.
103−isNullTable :: Table Expr a => NullTable Expr a -> Expr Bool
104−isNullTable (NullTable a) = isNull a
105−
106−
107−-- | The inverse of 'isNullTable'.
108−isNonNullTable :: Table Expr a => NullTable Expr a -> Expr Bool
109−isNonNullTable = not_ . isNullTable
110−
111−
112−-- | Like 'Rel8.nullable'.
113−nullableTable :: (Table Expr a, Table Expr b)
114− => b -> (a -> b) -> NullTable Expr a -> b
115−nullableTable b f ma@(NullTable a) = bool (f (extract a)) b (isNullTable ma)
116−
117−
118−-- | The null table. Like 'Rel8.null'.
119−nullTable :: Table Expr a => NullTable Expr a
120−nullTable = NullTable (pure undefined)
121−
122−
123−-- | Lift any table into 'NullTable'. Like 'Rel8.nullify'.
124−nullifyTable :: a -> NullTable Expr a
125−nullifyTable = NullTable . pure
126−
127−
128−unsafeUnnullifyTable :: NullTable Expr a -> a
129−unsafeUnnullifyTable (NullTable a) = extract a
130−
131−
132−-- | Construct a 'NullTable' in the 'Name' context. This can be useful if you
133−-- have a 'NullTable' that you are storing in a table and need to construct a
134−-- 'TableSchema'.
135−nameNullTable :: a -> NullTable Name a
136−nameNullTable = NullTable . pure
137−
138−
139−-- | Convert a 'NullTable' to a 'MaybeTable'.
140−toMaybeTable :: Table Expr a => NullTable Expr a -> MaybeTable Expr a
141−toMaybeTable = nullableTable nothingTable justTable
142−
143−
144−-- | Convert a 'MaybeTable' to a 'NullTable'. Note that if the underlying @a@
145−-- has no non-nullable fields, this is a lossy conversion.
146−toNullTable :: Table Expr a => MaybeTable Expr a -> NullTable Expr a
147−toNullTable = maybeTable nullTable nullifyTable
src/Rel8/Table/Nullify.hs−27
@@ -1,9 +1,7 @@
1{-# language DataKinds #-}
1{-# language DataKinds #-}
2−{-# language FlexibleContexts #-}
3{-# language FlexibleInstances #-}
2{-# language FlexibleInstances #-}
4{-# language LambdaCase #-}
3{-# language LambdaCase #-}
5{-# language MultiParamTypeClasses #-}
4{-# language MultiParamTypeClasses #-}
6−{-# language NamedFieldPuns #-}
7{-# language ScopedTypeVariables #-}
5{-# language ScopedTypeVariables #-}
8{-# language StandaloneKindSignatures #-}
6{-# language StandaloneKindSignatures #-}
9{-# language TypeApplications #-}
7{-# language TypeApplications #-}
@@ -14,13 +12,11 @@ module Rel8.Table.Nullify
14 ( Nullify
12 ( Nullify
15 , aggregateNullify
13 , aggregateNullify
16 , guard
14 , guard
17− , isNull
18 )
15 )
19where
16where
20
17
21-- base
18-- base
22import Control.Applicative ( liftA2 )
19import Control.Applicative ( liftA2 )
23−import Data.Functor.Const ( Const( Const ), getConst )
24import Data.Functor.Identity ( runIdentity )
20import Data.Functor.Identity ( runIdentity )
25import Data.Kind ( Type )
21import Data.Kind ( Type )
26import Prelude
22import Prelude
@@ -31,8 +27,6 @@ import Control.Comonad ( Comonad, duplicate, extract, ComonadApply, (<@>) )
31-- rel8
27-- rel8
32import Rel8.Aggregate ( Aggregate )
28import Rel8.Aggregate ( Aggregate )
33import Rel8.Expr ( Expr )
29import Rel8.Expr ( Expr )
34−import Rel8.Expr.Bool ( (||.), false )
35−import qualified Rel8.Expr.Null as Expr
36import Rel8.Kind.Context ( Reifiable, contextSing )
30import Rel8.Kind.Context ( Reifiable, contextSing )
37import Rel8.Schema.Context.Nullify
31import Rel8.Schema.Context.Nullify
38 ( Nullifiability( NAggregate, NExpr )
32 ( Nullifiability( NAggregate, NExpr )
@@ -51,14 +45,12 @@ import Rel8.Schema.HTable.Nullify
51 , hproject
45 , hproject
52 )
46 )
53import qualified Rel8.Schema.Kind as K
47import qualified Rel8.Schema.Kind as K
54−import Rel8.Schema.Null ( Nullity( NotNull, Null ) )
55import qualified Rel8.Schema.Result as R
48import qualified Rel8.Schema.Result as R
56import Rel8.Table
49import Rel8.Table
57 ( Table, Columns, Context, toColumns, fromColumns
50 ( Table, Columns, Context, toColumns, fromColumns
58 , FromExprs, fromResult, toResult
51 , FromExprs, fromResult, toResult
59 , Transpose
52 , Transpose
60 )
53 )
61−import Rel8.Schema.Spec ( Spec( Spec, nullity ) )
62import Rel8.Table.Eq ( EqTable, eqTable )
54import Rel8.Table.Eq ( EqTable, eqTable )
63import Rel8.Table.Ord ( OrdTable, ordTable )
55import Rel8.Table.Ord ( OrdTable, ordTable )
64import Rel8.Table.Projection ( Projectable, apply, project )
56import Rel8.Table.Projection ( Projectable, apply, project )
@@ -190,22 +182,3 @@ guard :: (Reifiable context, HTable t)
190 -> HNullify t context
182 -> HNullify t context
191guard tag isNonNull isNonNullExpr =
183guard tag isNonNull isNonNullExpr =
192 hguard (guarder contextSing tag isNonNull isNonNullExpr)
184 hguard (guarder contextSing tag isNonNull isNonNullExpr)
193−
194−
195−isNull :: forall a. Table Expr a => Nullify Expr a -> Expr Bool
196−isNull =
197− maybe false getAny .
198− getConst .
199− hunnullify (\Spec {nullity} a -> Const $ case nullity of
200− NotNull -> Just $ Any $ Expr.isNull a
201− Null -> Nothing) .
202− toColumns
203−
204−
205−newtype Any = Any
206− { getAny :: Expr Bool
207− }
208−
209−
210−instance Semigroup Any where
211− Any a <> Any b = Any (a ||. b)
src/Rel8/Table/Opaleye.hs+47−31
@@ -7,8 +7,6 @@
7{-# language TypeFamilies #-}
7{-# language TypeFamilies #-}
8{-# language ViewPatterns #-}
8{-# language ViewPatterns #-}
9
9
10−{-# options_ghc -Wno-deprecations #-}
11−
12module Rel8.Table.Opaleye
10module Rel8.Table.Opaleye
13 ( aggregator
11 ( aggregator
14 , attributes
12 , attributes
@@ -28,15 +26,17 @@ where
28-- base
26-- base
29import Data.Functor.Const ( Const( Const ), getConst )
27import Data.Functor.Const ( Const( Const ), getConst )
30import Data.List.NonEmpty ( NonEmpty )
28import Data.List.NonEmpty ( NonEmpty )
31−import Prelude
29+import Prelude hiding ( undefined )
32
30
33-- opaleye
31-- opaleye
34−import qualified Opaleye.Adaptors as Opaleye
35−import qualified Opaleye.Field as Opaleye ( Field_ )
36import qualified Opaleye.Internal.Aggregate as Opaleye
32import qualified Opaleye.Internal.Aggregate as Opaleye
33+import qualified Opaleye.Internal.Binary as Opaleye
34+import qualified Opaleye.Internal.Distinct as Opaleye
37import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
35import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
36+import qualified Opaleye.Internal.PackMap as Opaleye
37+import qualified Opaleye.Internal.Unpackspec as Opaleye
38import qualified Opaleye.Internal.Values as Opaleye
38import qualified Opaleye.Internal.Values as Opaleye
39−import qualified Opaleye.Table as Opaleye
39+import qualified Opaleye.Internal.Table as Opaleye
40
40
41-- profunctors
41-- profunctors
42import Data.Profunctor ( dimap, lmap )
42import Data.Profunctor ( dimap, lmap )
@@ -46,25 +46,27 @@ import Rel8.Aggregate ( Aggregate( Aggregate ), Aggregates )
46import Rel8.Expr ( Expr )
46import Rel8.Expr ( Expr )
47import Rel8.Expr.Opaleye
47import Rel8.Expr.Opaleye
48 ( fromPrimExpr, toPrimExpr
48 ( fromPrimExpr, toPrimExpr
49+ , traversePrimExpr
50+ , fromColumn, toColumn
49− , scastExpr, traverseFieldP
51+ , scastExpr
50 )
52 )
51−import Rel8.Schema.HTable ( htabulateA, hfield, hspecs, htabulate,
53+import Rel8.Schema.HTable ( htabulateA, hfield, htraverse, hspecs, htabulate )
52− htraverseP, htraversePWithField )
53import Rel8.Schema.Name ( Name( Name ), Selects, ppColumn )
54import Rel8.Schema.Name ( Name( Name ), Selects, ppColumn )
54import Rel8.Schema.Spec ( Spec(..) )
55import Rel8.Schema.Spec ( Spec(..) )
55import Rel8.Schema.Table ( TableSchema(..), ppTable )
56import Rel8.Schema.Table ( TableSchema(..), ppTable )
56import Rel8.Table ( Table, fromColumns, toColumns )
57import Rel8.Table ( Table, fromColumns, toColumns )
57−import Rel8.Type.Information ( typeName )
58+import Rel8.Table.Undefined ( undefined )
58
59
59-- semigroupoids
60-- semigroupoids
60import Data.Functor.Apply ( WrappedApplicative(..) )
61import Data.Functor.Apply ( WrappedApplicative(..) )
61−import Data.Profunctor.Product ( ProductProfunctor )
62
62
63
63
64aggregator :: Aggregates aggregates exprs => Opaleye.Aggregator aggregates exprs
64aggregator :: Aggregates aggregates exprs => Opaleye.Aggregator aggregates exprs
65−aggregator = dimap toColumns fromColumns $
65+aggregator = Opaleye.Aggregator $ Opaleye.PackMap $ \f aggregates ->
66− htraverseP $
66+ fmap fromColumns $ unwrapApplicative $ htabulateA $ \field ->
67− lmap (\(Aggregate a) -> (a, ())) Opaleye.aggregatorApply
67+ WrapApplicative $ case hfield (toColumns aggregates) field of
68+ Aggregate (Opaleye.Aggregator (Opaleye.PackMap inner)) ->
69+ inner f ()
68
70
69
71
70attributes :: Selects names exprs => TableSchema names -> exprs
72attributes :: Selects names exprs => TableSchema names -> exprs
@@ -75,19 +77,22 @@ attributes schema@TableSchema {columns} = fromColumns $ htabulate $ \field ->
75 show (ppTable schema) <> "." <> show (ppColumn column)
77 show (ppTable schema) <> "." <> show (ppColumn column)
76
78
77
79
78−fromOpaleyespec :: (ProductProfunctor p, Table Expr a)
79− => p (Opaleye.Field_ n x) (Opaleye.Field_ n x)
80− -> p a a
81−fromOpaleyespec x =
82− dimap toColumns fromColumns (htraverseP (traverseFieldP x))
83−
84−
85binaryspec :: Table Expr a => Opaleye.Binaryspec a a
80binaryspec :: Table Expr a => Opaleye.Binaryspec a a
86−binaryspec = fromOpaleyespec Opaleye.binaryspecField
81+binaryspec = Opaleye.Binaryspec $ Opaleye.PackMap $ \f (as, bs) ->
82+ fmap fromColumns $ unwrapApplicative $ htabulateA $ \field ->
83+ WrapApplicative $
84+ case (hfield (toColumns as) field, hfield (toColumns bs) field) of
85+ (a, b) -> fromPrimExpr <$> f (toPrimExpr a, toPrimExpr b)
87
86
88
87
89distinctspec :: Table Expr a => Opaleye.Distinctspec a a
88distinctspec :: Table Expr a => Opaleye.Distinctspec a a
90−distinctspec = fromOpaleyespec Opaleye.distinctspecField
89+distinctspec =
90+ Opaleye.Distinctspec $ Opaleye.Aggregator $ Opaleye.PackMap $ \f ->
91+ fmap fromColumns .
92+ unwrapApplicative .
93+ htraverse
94+ (\a -> WrapApplicative $ fromPrimExpr <$> f (Nothing, toPrimExpr a)) .
95+ toColumns
91
96
92
97
93exprs :: Table Expr a => a -> NonEmpty Opaleye.PrimExpr
98exprs :: Table Expr a => a -> NonEmpty Opaleye.PrimExpr
@@ -106,8 +111,8 @@ exprsWithNames names as = getConst $ htabulateA $ \field ->
106table :: Selects names exprs => TableSchema names -> Opaleye.Table exprs exprs
111table :: Selects names exprs => TableSchema names -> Opaleye.Table exprs exprs
107table (TableSchema name schema columns) =
112table (TableSchema name schema columns) =
108 case schema of
113 case schema of
109− Nothing -> Opaleye.table name (tableFields columns)
114+ Nothing -> Opaleye.Table name (tableFields columns)
110− Just schemaName -> Opaleye.tableWithSchema schemaName name (tableFields columns)
115+ Just schemaName -> Opaleye.TableWithSchema schemaName name (tableFields columns)
111
116
112
117
113tableFields :: Selects names exprs
118tableFields :: Selects names exprs
@@ -119,19 +124,21 @@ tableFields (toColumns -> names) = dimap toColumns fromColumns $
119 where
124 where
120 go :: Name a -> Opaleye.TableFields (Expr a) (Expr a)
125 go :: Name a -> Opaleye.TableFields (Expr a) (Expr a)
121 go (Name name) =
126 go (Name name) =
122− traverseFieldP $
127+ dimap (toColumn . toPrimExpr) (fromPrimExpr . fromColumn) $
123 Opaleye.requiredTableField name
128 Opaleye.requiredTableField name
124
129
125
130
126unpackspec :: Table Expr a => Opaleye.Unpackspec a a
131unpackspec :: Table Expr a => Opaleye.Unpackspec a a
127−unpackspec = fromOpaleyespec Opaleye.unpackspecField
132+unpackspec = Opaleye.Unpackspec $ Opaleye.PackMap $ \f ->
133+ fmap fromColumns .
134+ unwrapApplicative .
135+ htraverse (WrapApplicative . traversePrimExpr f) .
136+ toColumns
128{-# INLINABLE unpackspec #-}
137{-# INLINABLE unpackspec #-}
129
138
130
139
131−valuesspec :: Table Expr a => Opaleye.Valuesspec a a
140+valuesspec :: Table Expr a => Opaleye.ValuesspecSafe a a
132−valuesspec = dimap toColumns fromColumns $
141+valuesspec = Opaleye.ValuesspecSafe (toPackMap undefined) unpackspec
133− htraversePWithField (traverseFieldP . Opaleye.valuesspecFieldType . typeName)
134− where typeName = Rel8.Type.Information.typeName . info . hfield hspecs
135
142
136
143
137view :: Selects names exprs => names -> exprs
144view :: Selects names exprs => names -> exprs
@@ -140,6 +147,15 @@ view columns = fromColumns $ htabulate $ \field ->
140 Name column -> fromPrimExpr $ Opaleye.BaseTableAttrExpr column
147 Name column -> fromPrimExpr $ Opaleye.BaseTableAttrExpr column
141
148
142
149
150+toPackMap :: Table Expr a
151+ => a -> Opaleye.PackMap Opaleye.PrimExpr Opaleye.PrimExpr () a
152+toPackMap as = Opaleye.PackMap $ \f () ->
153+ fmap fromColumns $
154+ unwrapApplicative .
155+ htraverse (WrapApplicative . traversePrimExpr f) $
156+ toColumns as
157+
158+
143-- | Transform a table by adding 'CAST' to all columns. This is most useful for
159-- | Transform a table by adding 'CAST' to all columns. This is most useful for
144-- finalising a SELECT or RETURNING statement, guaranteed that the output
160-- finalising a SELECT or RETURNING statement, guaranteed that the output
145-- matches what is encoded in each columns TypeInformation.
161-- matches what is encoded in each columns TypeInformation.
src/Rel8/Table/Ord.hs+2−2
@@ -146,9 +146,9 @@ infix 4 >=:
146
146
147-- | Given two 'Table's, return the table that sorts before the other.
147-- | Given two 'Table's, return the table that sorts before the other.
148least :: OrdTable a => a -> a -> a
148least :: OrdTable a => a -> a -> a
149−least a b = bool b a (a <: b)
149+least a b = bool a b (a <: b)
150
150
151
151
152-- | Given two 'Table's, return the table that sorts after the other.
152-- | Given two 'Table's, return the table that sorts after the other.
153greatest :: OrdTable a => a -> a -> a
153greatest :: OrdTable a => a -> a -> a
154−greatest a b = bool b a (a >: b)
154+greatest a b = bool a b (a >: b)
src/Rel8/Table/Projection.hs+1−1
@@ -35,7 +35,7 @@ class
35 => Projecting a b
35 => Projecting a b
36instance
36instance
37 ( Transposes (Context a) (Field a) a (Transpose (Field a) a)
37 ( Transposes (Context a) (Field a) a (Transpose (Field a) a)
38− , Transposes (Context a) (Field a) b (Transpose (Field a) b)
38+ , Transposes (Context a) (Field a) b (Transpose (Field b) b)
39 )
39 )
40 => Projecting a b
40 => Projecting a b
41
41
src/Rel8/Table/These.hs+3−14
@@ -20,7 +20,6 @@ module Rel8.Table.These
20 , isThisTable, isThatTable, isThoseTable
20 , isThisTable, isThatTable, isThoseTable
21 , hasHereTable, hasThereTable
21 , hasHereTable, hasThereTable
22 , justHereTable, justThereTable
22 , justHereTable, justThereTable
23− , alignMaybeTable
24 , aggregateTheseTable
23 , aggregateTheseTable
25 , nameTheseTable
24 , nameTheseTable
26 )
25 )
@@ -30,13 +29,13 @@ where
30import Data.Bifunctor ( Bifunctor, bimap )
29import Data.Bifunctor ( Bifunctor, bimap )
31import Data.Kind ( Type )
30import Data.Kind ( Type )
32import Data.Maybe ( isJust )
31import Data.Maybe ( isJust )
33−import Prelude hiding ( null, undefined )
32+import Prelude hiding ( undefined )
34
33
35-- rel8
34-- rel8
36import Rel8.Aggregate ( Aggregate )
35import Rel8.Aggregate ( Aggregate )
37import Rel8.Expr ( Expr )
36import Rel8.Expr ( Expr )
38−import Rel8.Expr.Bool ( (&&.), (||.), boolExpr, not_ )
37+import Rel8.Expr.Bool ( (&&.), not_ )
39−import Rel8.Expr.Null ( null, isNonNull )
38+import Rel8.Expr.Null ( isNonNull )
40import Rel8.Kind.Context ( Reifiable )
39import Rel8.Kind.Context ( Reifiable )
41import Rel8.Schema.Context.Nullify ( Nullifiable )
40import Rel8.Schema.Context.Nullify ( Nullifiable )
42import Rel8.Schema.Dict ( Dict( Dict ) )
41import Rel8.Schema.Dict ( Dict( Dict ) )
@@ -289,16 +288,6 @@ justThereTable :: TheseTable context a b -> MaybeTable context b
289justThereTable = there
288justThereTable = there
290
289
291
290
292−-- | Construct a @TheseTable@ from two 'MaybeTable's.
293−alignMaybeTable :: ()
294− => MaybeTable Expr a
295− -> MaybeTable Expr b
296− -> MaybeTable Expr (TheseTable Expr a b)
297−alignMaybeTable a b = MaybeTable tag (pure (TheseTable a b))
298− where
299− tag = boolExpr null mempty (isJustTable a ||. isJustTable b)
300−
301−
302-- | Construct a @TheseTable@. Corresponds to 'This'.
291-- | Construct a @TheseTable@. Corresponds to 'This'.
303thisTable :: Table Expr b => a -> TheseTable Expr a b
292thisTable :: Table Expr b => a -> TheseTable Expr a b
304thisTable a = TheseTable (justTable a) nothingTable
293thisTable a = TheseTable (justTable a) nothingTable
src/Rel8/Table/Window.hsdeleted−43
@@ -1,43 +0,0 @@
1−{-# language MonoLocalBinds #-}
2−
3−module Rel8.Table.Window
4− ( cumulative
5− , cumulative_
6− , currentRow
7− )
8−where
9−
10−-- base
11−import Prelude
12−
13−-- opaleye
14−import qualified Opaleye.Window as Opaleye
15−
16−-- rel8
17−import Rel8.Aggregate ( Aggregates )
18−import qualified Rel8.Expr.Window as Expr
19−import Rel8.Schema.HTable ( hfield, htabulateA )
20−import Rel8.Table ( fromColumns, toColumns )
21−import Rel8.Window ( Window( Window ) )
22−
23−
24−-- | 'cumulative' allows the use of aggregation functions in 'Window'
25−-- expressions. In particular, @'cumulative' 'Rel8.sum'@
26−-- (when combined with 'Rel8.Window.orderPartitionBy') gives a running total,
27−-- also known as a \"cumulative sum\", hence the name @cumulative@.
28−cumulative :: Aggregates aggregates exprs => (a -> aggregates) -> Window a exprs
29−cumulative f =
30− fmap fromColumns $
31− htabulateA $ \field ->
32− Expr.cumulative ((`hfield` field) . toColumns . f)
33−
34−
35−-- | A version of 'cumulative' for use with nullary aggregators like
36−-- 'Rel8.Expr.Aggregate.countStar'.
37−cumulative_ :: Aggregates aggregates exprs => aggregates -> Window a exprs
38−cumulative_ = cumulative . const
39−
40−
41−-- | Return every column of the current row of a window query.
42−currentRow :: Window a a
43−currentRow = Window $ Opaleye.over (Opaleye.noWindowFunction id) mempty mempty
src/Rel8/Tabulate.hs+21−24
@@ -58,7 +58,7 @@ import Data.Function ( on )
58import Data.Functor.Contravariant ( Contravariant, (>$<), contramap )
58import Data.Functor.Contravariant ( Contravariant, (>$<), contramap )
59import Data.Int ( Int64 )
59import Data.Int ( Int64 )
60import Data.Kind ( Type )
60import Data.Kind ( Type )
61−import Data.Maybe ( fromJust, fromMaybe )
61+import Data.Maybe ( fromMaybe )
62import Prelude hiding ( lookup, zip, zipWith )
62import Prelude hiding ( lookup, zip, zipWith )
63
63
64-- bifunctors
64-- bifunctors
@@ -69,7 +69,9 @@ import Control.Comonad ( extract )
69
69
70-- opaleye
70-- opaleye
71import qualified Opaleye.Aggregate as Opaleye
71import qualified Opaleye.Aggregate as Opaleye
72−import qualified Opaleye.Order as Opaleye ( orderBy, distinctOnExplicit )
72+import qualified Opaleye.Internal.Order as Opaleye
73+import qualified Opaleye.Internal.QueryArr as Opaleye
74+import qualified Opaleye.Order as Opaleye ( orderBy )
73
75
74-- profunctors
76-- profunctors
75import Data.Profunctor ( dimap, lmap )
77import Data.Profunctor ( dimap, lmap )
@@ -90,10 +92,10 @@ import Rel8.Order ( Order( Order ) )
90import Rel8.Query ( Query )
92import Rel8.Query ( Query )
91import qualified Rel8.Query.Exists as Q ( exists, present, absent )
93import qualified Rel8.Query.Exists as Q ( exists, present, absent )
92import Rel8.Query.Filter ( where_ )
94import Rel8.Query.Filter ( where_ )
95+import Rel8.Query.Limit ( limit )
93import Rel8.Query.List ( catNonEmptyTable )
96import Rel8.Query.List ( catNonEmptyTable )
94import qualified Rel8.Query.Maybe as Q ( optional )
97import qualified Rel8.Query.Maybe as Q ( optional )
95import Rel8.Query.Opaleye ( mapOpaleye, unsafePeekQuery )
98import Rel8.Query.Opaleye ( mapOpaleye, unsafePeekQuery )
96−import Rel8.Query.Rebind ( rebind )
97import Rel8.Query.These ( alignBy )
99import Rel8.Query.These ( alignBy )
98import Rel8.Table ( Table, fromColumns, toColumns )
100import Rel8.Table ( Table, fromColumns, toColumns )
99import Rel8.Table.Aggregate ( hgroupBy, listAgg, nonEmptyAgg )
101import Rel8.Table.Aggregate ( hgroupBy, listAgg, nonEmptyAgg )
@@ -177,7 +179,7 @@ ensure (Predicate mp) = traverse_ (\k -> traverse_ (\p -> where_ (p k)) mp)
177-- stopping a 'Tabulation' from containing multiple rows with the same key, so
179-- stopping a 'Tabulation' from containing multiple rows with the same key, so
178-- technically @Map k (NonEmpty a)@ is more accurate.
180-- technically @Map k (NonEmpty a)@ is more accurate.
179--
181--
180−-- 'Tabulation's can be created from 'Query's with 'fromQuery' and 'liftQuery'
182+-- 'Tabulation's can be created from 'Query's with 'fromQuery' and 'littQuery'
181-- and converted back to 'Query's with 'lookup' and 'toQuery' (though note the
183-- and converted back to 'Query's with 'lookup' and 'toQuery' (though note the
182-- caveats that come with the latter).
184-- caveats that come with the latter).
183type Tabulation :: Type -> Type -> Type
185type Tabulation :: Type -> Type -> Type
@@ -234,13 +236,7 @@ instance EqTable k => Monad (Tabulation k) where
234-- | If @'Tabulation' k a@ is @Map k (NonEmpty a)@, then @(<|>:)@ is
236-- | If @'Tabulation' k a@ is @Map k (NonEmpty a)@, then @(<|>:)@ is
235-- @unionWith (<>)@.
237-- @unionWith (<>)@.
236instance EqTable k => AltTable (Tabulation k) where
238instance EqTable k => AltTable (Tabulation k) where
237− tas <|>: tbs = do
238− eas <- peek tas
239− ebs <- peek tbs
240− case (eas, ebs) of
241− (Left as, Left bs) -> liftQuery $ as <|>: bs
242− (Right as, Right bs) -> fromQuery $ as <|>: bs
243− _ -> catNonEmptyTable `through` ((<>) `on` some) tas tbs
239+ as <|>: bs = catNonEmptyTable `through` ((<>) `on` some) as bs
244
240
245
241
246instance EqTable k => AlternativeTable (Tabulation k) where
242instance EqTable k => AlternativeTable (Tabulation k) where
@@ -349,8 +345,19 @@ aggregate (Tabulation f) = Tabulation $
349-- \"first\" value it encounters for each key, but note that \"first\" is
345-- \"first\" value it encounters for each key, but note that \"first\" is
350-- undefined unless you first call 'order'.
346-- undefined unless you first call 'order'.
351distinct :: EqTable k => Tabulation k a -> Tabulation k a
347distinct :: EqTable k => Tabulation k a -> Tabulation k a
352−distinct (Tabulation f) = Tabulation $
348+distinct (Tabulation f) = Tabulation $ \p ->
349+ -- workaround for https://github.com/tomjaguarpaw/haskell-opaleye/pull/518
350+ case fst (unsafePeekQuery (f p)) of
351+ Nothing -> limit 1 (f p)
352+ Just _ ->
353+ mapOpaleye
354+ (\q ->
355+ Opaleye.productQueryArr
353− mapOpaleye (Opaleye.distinctOnExplicit (key unpackspec) fst) . f
356+ ( Opaleye.distinctOn (key unpackspec) fst
357+ . Opaleye.runSimpleQueryArr q
358+ )
359+ )
360+ (f p)
354
361
355
362
356-- | 'order' orders the /values/ of a 'Tabulation' within their
363-- | 'order' orders the /values/ of a 'Tabulation' within their
@@ -509,8 +516,8 @@ alignWith :: EqTable k
509 -> Tabulation k a -> Tabulation k b -> Tabulation k c
516 -> Tabulation k a -> Tabulation k b -> Tabulation k c
510alignWith f (Tabulation as) (Tabulation bs) = Tabulation $ \p -> do
517alignWith f (Tabulation as) (Tabulation bs) = Tabulation $ \p -> do
511 tkab <- liftF2 (alignBy condition) as bs p
518 tkab <- liftF2 (alignBy condition) as bs p
512− k <- traverse (rebind "key") $ recover $ bimap fst fst tkab
513 let
519 let
520+ k = recover $ bimap fst fst tkab
514 tab = bimap snd snd tkab
521 tab = bimap snd snd tkab
515 pure (k, f tab)
522 pure (k, f tab)
516 where
523 where
@@ -628,13 +635,3 @@ similarity a b = a <* present b
628-- @do@-notation.
635-- @do@-notation.
629difference :: EqTable k => Tabulation k a -> Tabulation k b -> Tabulation k a
636difference :: EqTable k => Tabulation k a -> Tabulation k b -> Tabulation k a
630difference a b = a <* absent b
637difference a b = a <* absent b
631−
632−
633−-- | 'Tabulation's can be produced with either 'fromQuery' or 'liftQuery', and
634−-- in some cases we might want to treat these differently. 'peek' uses
635−-- 'unsafePeekQuery' to determine which type of 'Tabulation' we have.
636−peek :: Tabulation k a -> Tabulation k (Either (Query a) (Query (k, a)))
637−peek (Tabulation f) = Tabulation $ \p ->
638− pure $ (empty,) $ case unsafePeekQuery (f p) of
639− (Nothing, _) -> Left $ fmap snd (f p)
640− (Just _, _) -> Right $ fmap (first fromJust) (f p)
src/Rel8/Type.hs+8−9
@@ -1,4 +1,3 @@
1−{-# language FlexibleContexts #-}
2{-# language FlexibleInstances #-}
1{-# language FlexibleInstances #-}
3{-# language MonoLocalBinds #-}
2{-# language MonoLocalBinds #-}
4{-# language MultiWayIf #-}
3{-# language MultiWayIf #-}
@@ -128,10 +127,10 @@ instance DBType Int64 where
128instance DBType Float where
127instance DBType Float where
129 typeInformation = TypeInformation
128 typeInformation = TypeInformation
130 { encode = \x -> Opaleye.ConstExpr
129 { encode = \x -> Opaleye.ConstExpr
131− if | x == (1 / 0) -> Opaleye.OtherLit "'Infinity'"
130+ if | x == (1 /0) -> Opaleye.OtherLit "'Infinity'"
132− | isNaN x -> Opaleye.OtherLit "'NaN'"
131+ | isNaN x -> Opaleye.OtherLit "'NaN'"
133− | x == (-1 / 0) -> Opaleye.OtherLit "'-Infinity'"
132+ | x == (-1/0) -> Opaleye.OtherLit "'-Infinity'"
134− | otherwise -> Opaleye.NumericLit $ realToFrac x
133+ | otherwise -> Opaleye.NumericLit $ realToFrac x
135 , decode = Hasql.float4
134 , decode = Hasql.float4
136 , typeName = "float4"
135 , typeName = "float4"
137 }
136 }
@@ -141,10 +140,10 @@ instance DBType Float where
141instance DBType Double where
140instance DBType Double where
142 typeInformation = TypeInformation
141 typeInformation = TypeInformation
143 { encode = \x -> Opaleye.ConstExpr
142 { encode = \x -> Opaleye.ConstExpr
144− if | x == (1 / 0) -> Opaleye.OtherLit "'Infinity'"
143+ if | x == (1 /0) -> Opaleye.OtherLit "'Infinity'"
145− | isNaN x -> Opaleye.OtherLit "'NaN'"
144+ | isNaN x -> Opaleye.OtherLit "'NaN'"
146− | x == (-1 / 0) -> Opaleye.OtherLit "'-Infinity'"
145+ | x == (-1/0) -> Opaleye.OtherLit "'-Infinity'"
147− | otherwise -> Opaleye.NumericLit $ realToFrac x
146+ | otherwise -> Opaleye.NumericLit $ realToFrac x
148 , decode = Hasql.float8
147 , decode = Hasql.float8
149 , typeName = "float8"
148 , typeName = "float8"
150 }
149 }
src/Rel8/Type/Enum.hs−1
@@ -106,7 +106,6 @@ class (DBType a, Enumable a) => DBEnum a where
106
106
107-- | Types that are sum types, where each constructor is unary (that is, has no
107-- | Types that are sum types, where each constructor is unary (that is, has no
108-- fields).
108-- fields).
109−type Enumable :: Type -> Constraint
110class (Generic a, GEnumable (Rep a)) => Enumable a
109class (Generic a, GEnumable (Rep a)) => Enumable a
111instance (Generic a, GEnumable (Rep a)) => Enumable a
110instance (Generic a, GEnumable (Rep a)) => Enumable a
112
111
src/Rel8/Type/JSONBEncoded.hs−4
@@ -1,5 +1,3 @@
1−{-# language StandaloneKindSignatures #-}
2−
3module Rel8.Type.JSONBEncoded ( JSONBEncoded(..) ) where
1module Rel8.Type.JSONBEncoded ( JSONBEncoded(..) ) where
4
2
5-- aeson
3-- aeson
@@ -8,7 +6,6 @@ import Data.Aeson.Types ( parseEither )
8
6
9-- base
7-- base
10import Data.Bifunctor ( first )
8import Data.Bifunctor ( first )
11−import Data.Kind ( Type )
12import Prelude
9import Prelude
13
10
14-- hasql
11-- hasql
@@ -23,7 +20,6 @@ import Data.Text ( pack )
23
20
24
21
25-- | Like 'Rel8.JSONEncoded', but works for @jsonb@ columns.
22-- | Like 'Rel8.JSONEncoded', but works for @jsonb@ columns.
26−type JSONBEncoded :: Type -> Type
27newtype JSONBEncoded a = JSONBEncoded { fromJSONBEncoded :: a }
23newtype JSONBEncoded a = JSONBEncoded { fromJSONBEncoded :: a }
28
24
29
25
src/Rel8/Type/JSONEncoded.hs−4
@@ -1,5 +1,3 @@
1−{-# language StandaloneKindSignatures #-}
2−
3module Rel8.Type.JSONEncoded ( JSONEncoded(..) ) where
1module Rel8.Type.JSONEncoded ( JSONEncoded(..) ) where
4
2
5-- aeson
3-- aeson
@@ -7,7 +5,6 @@ import Data.Aeson ( FromJSON, ToJSON, parseJSON, toJSON )
7import Data.Aeson.Types ( parseEither )
5import Data.Aeson.Types ( parseEither )
8
6
9-- base
7-- base
10−import Data.Kind ( Type )
11import Prelude
8import Prelude
12
9
13-- rel8
10-- rel8
@@ -18,7 +15,6 @@ import Rel8.Type.Information ( parseTypeInformation )
18-- | A deriving-via helper type for column types that store a Haskell value
15-- | A deriving-via helper type for column types that store a Haskell value
19-- using a JSON encoding described by @aeson@'s 'ToJSON' and 'FromJSON' type
16-- using a JSON encoding described by @aeson@'s 'ToJSON' and 'FromJSON' type
20-- classes.
17-- classes.
21−type JSONEncoded :: Type -> Type
22newtype JSONEncoded a = JSONEncoded { fromJSONEncoded :: a }
18newtype JSONEncoded a = JSONEncoded { fromJSONEncoded :: a }
23
19
24
20
src/Rel8/Type/Num.hs+1−4
@@ -18,7 +18,6 @@ import Prelude
18
18
19-- rel8
19-- rel8
20import Rel8.Type ( DBType )
20import Rel8.Type ( DBType )
21−import Rel8.Type.Ord ( DBOrd )
22
21
23-- scientific
22-- scientific
24import Data.Scientific ( Scientific )
23import Data.Scientific ( Scientific )
@@ -40,14 +39,13 @@ instance DBNum Scientific
40-- expressions. This is a Rel8 concept, and allows us to provide
39-- expressions. This is a Rel8 concept, and allows us to provide
41-- 'fromIntegral'.
40-- 'fromIntegral'.
42type DBIntegral :: Type -> Constraint
41type DBIntegral :: Type -> Constraint
43−class (DBNum a, DBOrd a) => DBIntegral a
42+class DBNum a => DBIntegral a
44instance DBIntegral Int16
43instance DBIntegral Int16
45instance DBIntegral Int32
44instance DBIntegral Int32
46instance DBIntegral Int64
45instance DBIntegral Int64
47
46
48
47
49-- | The class of database types that support the @/@ operator.
48-- | The class of database types that support the @/@ operator.
50−type DBFractional :: Type -> Constraint
51class DBNum a => DBFractional a
49class DBNum a => DBFractional a
52instance DBFractional Float
50instance DBFractional Float
53instance DBFractional Double
51instance DBFractional Double
@@ -55,7 +53,6 @@ instance DBFractional Scientific
55
53
56
54
57-- | The class of database types that support the @/@ operator.
55-- | The class of database types that support the @/@ operator.
58−type DBFloating :: Type -> Constraint
59class DBFractional a => DBFloating a
56class DBFractional a => DBFloating a
60instance DBFloating Float
57instance DBFloating Float
61instance DBFloating Double
58instance DBFloating Double
src/Rel8/Type/Ord.hs+2
@@ -75,6 +75,7 @@ instance Sql DBOrd a => DBOrd (NonEmpty a)
75-- | The class of database types that support the @max@ aggregation function.
75-- | The class of database types that support the @max@ aggregation function.
76type DBMax :: Type -> Constraint
76type DBMax :: Type -> Constraint
77class DBOrd a => DBMax a
77class DBOrd a => DBMax a
78+instance DBMax Bool
78instance DBMax Char
79instance DBMax Char
79instance DBMax Int16
80instance DBMax Int16
80instance DBMax Int32
81instance DBMax Int32
@@ -100,6 +101,7 @@ instance Sql DBMax a => DBMax (NonEmpty a)
100-- | The class of database types that support the @min@ aggregation function.
101-- | The class of database types that support the @min@ aggregation function.
101type DBMin :: Type -> Constraint
102type DBMin :: Type -> Constraint
102class DBOrd a => DBMin a
103class DBOrd a => DBMin a
104+instance DBMin Bool
103instance DBMin Char
105instance DBMin Char
104instance DBMin Int16
106instance DBMin Int16
105instance DBMin Int32
107instance DBMin Int32
src/Rel8/Type/ReadShow.hs−3
@@ -1,12 +1,10 @@
1{-# language ScopedTypeVariables #-}
1{-# language ScopedTypeVariables #-}
2−{-# language StandaloneKindSignatures #-}
3{-# language TypeApplications #-}
2{-# language TypeApplications #-}
4{-# language ViewPatterns #-}
3{-# language ViewPatterns #-}
5
4
6module Rel8.Type.ReadShow ( ReadShow(..) ) where
5module Rel8.Type.ReadShow ( ReadShow(..) ) where
7
6
8-- base
7-- base
9−import Data.Kind ( Type )
10import Data.Proxy ( Proxy( Proxy ) )
8import Data.Proxy ( Proxy( Proxy ) )
11import Data.Typeable ( Typeable, typeRep )
9import Data.Typeable ( Typeable, typeRep )
12import Prelude
10import Prelude
@@ -22,7 +20,6 @@ import qualified Data.Text as Text
22
20
23-- | A deriving-via helper type for column types that store a Haskell value
21-- | A deriving-via helper type for column types that store a Haskell value
24-- using a Haskell's 'Read' and 'Show' type classes.
22-- using a Haskell's 'Read' and 'Show' type classes.
25−type ReadShow :: Type -> Type
26newtype ReadShow a = ReadShow { fromReadShow :: a }
23newtype ReadShow a = ReadShow { fromReadShow :: a }
27
24
28
25
src/Rel8/Type/Tag.hs−1
@@ -90,7 +90,6 @@ instance DBMonoid MaybeTag where
90 memptyExpr = litExpr mempty
90 memptyExpr = litExpr mempty
91
91
92
92
93−type Tag :: Type
94newtype Tag = Tag Text
93newtype Tag = Tag Text
95 deriving newtype
94 deriving newtype
96 ( Eq, Ord, Read, Show
95 ( Eq, Ord, Read, Show
src/Rel8/Window.hsdeleted−97
@@ -1,97 +0,0 @@
1−{-# language DerivingVia #-}
2−{-# language FlexibleContexts #-}
3−{-# language GeneralizedNewtypeDeriving #-}
4−{-# language ScopedTypeVariables #-}
5−{-# language StandaloneKindSignatures #-}
6−{-# language TypeApplications #-}
7−
8−module Rel8.Window
9− ( Window(..)
10− , Partition
11− , over
12− , partitionBy
13− , orderPartitionBy
14− )
15−where
16−
17−-- base
18−import Data.Functor.Const ( Const( Const ), getConst )
19−import Data.Functor.Contravariant ( Contravariant, contramap )
20−import Data.Kind ( Type )
21−import Prelude
22−
23−-- opaleye
24−import qualified Opaleye.Internal.Window as Opaleye
25−import qualified Opaleye.Internal.PackMap as Opaleye
26−
27−-- profunctors
28−import Data.Profunctor ( Profunctor )
29−
30−-- product-profunctors
31−import Data.Profunctor.Product ( ProductProfunctor, (****), purePP )
32−
33−-- rel8
34−import Rel8.Expr.Opaleye ( toColumn, toPrimExpr )
35−import Rel8.Order( Order( Order ) )
36−import Rel8.Schema.HTable ( hfield, htabulateA )
37−import Rel8.Table ( Columns, toColumns )
38−import Rel8.Table.Eq ( EqTable )
39−
40−-- semigroupoids
41−import Data.Functor.Apply ( Apply, WrappedApplicative(..) )
42−
43−
44−-- | 'Window' is an applicative functor that represents expressions that
45−-- contain
46−-- [window functions](https://www.postgresql.org/docs/current/tutorial-window.html).
47−-- 'Rel8.Query.Window.window' can be used to
48−-- evaluate these expressions over a particular query.
49−type Window :: Type -> Type -> Type
50−newtype Window a b = Window (Opaleye.Windows a b)
51− deriving newtype (Profunctor)
52− deriving newtype (Functor, Applicative)
53− deriving (Apply) via (WrappedApplicative (Window a))
54−
55−
56−instance ProductProfunctor Window where
57− purePP = pure
58− (****) = (<*>)
59−
60−
61−-- | In PostgreSQL, window functions must specify the \"window\" or
62−-- \"partition\" over which they operate. The syntax for this looks like:
63−-- @SUM(salary) OVER (PARTITION BY department)@. The Rel8 type 'Partition'
64−-- represents everything that comes after @OVER@.
65−--
66−-- 'Partition' is a 'Monoid', so 'Window's created with 'partitionBy' and
67−-- 'orderWindowBy' can be combined using '<>'.
68−type Partition :: Type -> Type
69−newtype Partition a = Partition (Opaleye.Window a)
70− deriving newtype (Contravariant, Semigroup, Monoid)
71−
72−
73−-- | 'over' adds a 'Partition' to a 'Window' expression.
74−--
75−-- @@@
76−-- 'Rel8.Table.Window.cumulative' ('Rel8.Expr.Aggregate.sum' . salary) `over` 'partitionBy' department <> 'orderPartitionBy' (salary >$< 'Rel8.desc')
77−-- @@@
78−over :: Window a b -> Partition a -> Window a b
79−over (Window (Opaleye.Windows (Opaleye.PackMap w))) (Partition p) =
80− Window $ Opaleye.Windows $ Opaleye.PackMap $ \f ->
81− w (\(o, p') -> f (o, p' <> p))
82−infixl 1 `over`
83−
84−
85−-- | Restricts a window function to operate only the group of rows that share
86−-- the same value(s) for the given expression(s).
87−partitionBy :: forall b a. EqTable b => (a -> b) -> Partition a
88−partitionBy f =
89− Partition $ contramap (toColumns . f) $ getConst $
90− htabulateA @(Columns b) $ \field ->
91− Const $ Opaleye.partitionBy (toColumn . toPrimExpr . (`hfield` field))
92−
93−
94−-- | Controls the order in which rows are processed by window functions. This
95−-- does not need to match the ordering of the overall query.
96−orderPartitionBy :: Order a -> Partition a
97−orderPartitionBy (Order ordering) = Partition $ Opaleye.orderPartitionBy ordering
tests/Main.hs−94
@@ -1,4 +1,3 @@
1−{-# language BangPatterns #-}
2{-# language BlockArguments #-}
1{-# language BlockArguments #-}
3{-# language DeriveAnyClass #-}
2{-# language DeriveAnyClass #-}
4{-# language DeriveGeneric #-}
3{-# language DeriveGeneric #-}
@@ -6,7 +5,6 @@
6{-# language FlexibleContexts #-}
5{-# language FlexibleContexts #-}
7{-# language FlexibleInstances #-}
6{-# language FlexibleInstances #-}
8{-# language MonoLocalBinds #-}
7{-# language MonoLocalBinds #-}
9−{-# language NamedFieldPuns #-}
10{-# language OverloadedStrings #-}
8{-# language OverloadedStrings #-}
11{-# language RecordWildCards #-}
9{-# language RecordWildCards #-}
12{-# language ScopedTypeVariables #-}
10{-# language ScopedTypeVariables #-}
@@ -38,7 +36,6 @@ import qualified Data.ByteString.Lazy
38import Data.CaseInsensitive ( mk )
36import Data.CaseInsensitive ( mk )
39
37
40-- containers
38-- containers
41−import Data.Containers.ListUtils ( nubOrdOn )
42import qualified Data.Map.Strict as Map
39import qualified Data.Map.Strict as Map
43
40
44-- hasql
41-- hasql
@@ -116,13 +113,11 @@ tests =
116 , testCatMaybeTable getTestDatabase
113 , testCatMaybeTable getTestDatabase
117 , testCatMaybe getTestDatabase
114 , testCatMaybe getTestDatabase
118 , testMaybeTable getTestDatabase
115 , testMaybeTable getTestDatabase
119− , testAggregateMaybeTable getTestDatabase
120 , testNestedTables getTestDatabase
116 , testNestedTables getTestDatabase
121 , testMaybeTableApplicative getTestDatabase
117 , testMaybeTableApplicative getTestDatabase
122 , testLogicalFixities getTestDatabase
118 , testLogicalFixities getTestDatabase
123 , testUpdate getTestDatabase
119 , testUpdate getTestDatabase
124 , testDelete getTestDatabase
120 , testDelete getTestDatabase
125− , testUpsert getTestDatabase
126 , testSelectNestedPairs getTestDatabase
121 , testSelectNestedPairs getTestDatabase
127 , testSelectArray getTestDatabase
122 , testSelectArray getTestDatabase
128 , testNestedMaybeTable getTestDatabase
123 , testNestedMaybeTable getTestDatabase
@@ -138,7 +133,6 @@ tests =
138 flip run conn do
133 flip run conn do
139 sql "CREATE EXTENSION citext"
134 sql "CREATE EXTENSION citext"
140 sql "CREATE TABLE test_table ( column1 text not null, column2 bool not null )"
135 sql "CREATE TABLE test_table ( column1 text not null, column2 bool not null )"
141− sql "CREATE TABLE unique_table ( \"key\" text not null unique, \"value\" text not null )"
142 sql "CREATE SEQUENCE test_seq"
136 sql "CREATE SEQUENCE test_seq"
143
137
144 return db
138 return db
@@ -561,28 +555,6 @@ testMaybeTable = databasePropertyTest "maybeTable" \transaction -> evalM do
561 _ -> sort selected === sort rows
555 _ -> sort selected === sort rows
562
556
563
557
564−testAggregateMaybeTable :: IO TmpPostgres.DB -> TestTree
565−testAggregateMaybeTable = databasePropertyTest "aggregateMaybeTable" \transaction -> evalM do
566− rows <- forAll $ Gen.list (Range.linear 0 10) (Gen.maybe (Gen.int64 (Range.linear 0 10)))
567−
568− let
569− aggregate = go 0 False False
570− where
571− go !n nothing _ (Just a : as) = go (n + a) nothing True as
572− go n _ just (Nothing : as) = go n True just as
573− go _ False False [] = []
574− go _ True False [] = [Nothing]
575− go n False True [] = [Just n]
576− go n True True [] = [Nothing, Just n]
577−
578− transaction do
579− selected <- lift do
580− statement () $ Rel8.select do
581− Rel8.aggregate $ Rel8.aggregateMaybeTable Rel8.sum <$> Rel8.values (Rel8.lit <$> rows)
582−
583− sort selected === aggregate rows
584−
585−
586data TwoTestTables f =
558data TwoTestTables f =
587 TwoTestTables
559 TwoTestTables
588 { testTable1 :: TestTable f
560 { testTable1 :: TestTable f
@@ -713,72 +685,6 @@ testDelete = databasePropertyTest "Can DELETE TestTable" \transaction -> do
713 sort (deleted <> selected) === sort rows
685 sort (deleted <> selected) === sort rows
714
686
715
687
716−data UniqueTable f = UniqueTable
717− { uniqueTableKey :: Rel8.Column f Text
718− , uniqueTableValue :: Rel8.Column f Text
719− }
720− deriving stock Generic
721− deriving anyclass Rel8.Rel8able
722−
723−
724−deriving stock instance Eq (UniqueTable Result)
725−deriving stock instance Ord (UniqueTable Result)
726−deriving stock instance Show (UniqueTable Result)
727−
728−
729−uniqueTableSchema :: Rel8.TableSchema (UniqueTable Rel8.Name)
730−uniqueTableSchema =
731− Rel8.TableSchema
732− { name = "unique_table"
733− , schema = Nothing
734− , columns = UniqueTable
735− { uniqueTableKey = "key"
736− , uniqueTableValue = "value"
737− }
738− }
739−
740−
741−genUniqueTable :: Gen (UniqueTable Result)
742−genUniqueTable = do
743− uniqueTableKey <- Gen.text (Range.linear 0 5) Gen.alphaNum
744− uniqueTableValue <- Gen.text (Range.linear 0 5) Gen.alphaNum
745− pure UniqueTable {..}
746−
747−
748−testUpsert :: IO TmpPostgres.DB -> TestTree
749−testUpsert = databasePropertyTest "Can UPSERT UniqueTable" \transaction -> do
750− as <- unique $ forAll $ Gen.list (Range.linear 0 20) genUniqueTable
751− bs <- unique $ forAll $ Gen.list (Range.linear 0 20) genUniqueTable
752−
753− transaction do
754− selected <- lift do
755− statement () $ Rel8.insert Rel8.Insert
756− { into = uniqueTableSchema
757− , rows = Rel8.values $ Rel8.lit <$> as
758− , onConflict = Rel8.DoNothing
759− , returning = pure ()
760− }
761−
762− statement () $ Rel8.insert Rel8.Insert
763− { into = uniqueTableSchema
764− , rows = Rel8.values $ Rel8.lit <$> bs
765− , onConflict = Rel8.DoUpdate Rel8.Upsert
766− { index = uniqueTableKey
767− , set = \UniqueTable {uniqueTableValue} old -> old {uniqueTableValue}
768− , updateWhere = \_ _ -> Rel8.true
769− }
770− , returning = pure ()
771− }
772−
773− statement () $ Rel8.select do
774− Rel8.each uniqueTableSchema
775−
776− fromUniqueTables selected === fromUniqueTables bs <> fromUniqueTables as
777− where
778− unique = fmap (nubOrdOn uniqueTableKey)
779− fromUniqueTables = Map.fromList . map \(UniqueTable key value) -> (key, value)
780−
781−
782newtype HKNestedPair f = HKNestedPair { pairOne :: (TestTable f, TestTable f) }
688newtype HKNestedPair f = HKNestedPair { pairOne :: (TestTable f, TestTable f) }
783 deriving stock Generic
689 deriving stock Generic
784 deriving anyclass Rel8.Rel8able
690 deriving anyclass Rel8.Rel8able
tests/Rel8/Generic/Rel8able/Test.hs+1−10
@@ -5,7 +5,6 @@
5{-# language DuplicateRecordFields #-}
5{-# language DuplicateRecordFields #-}
6{-# language FlexibleInstances #-}
6{-# language FlexibleInstances #-}
7{-# language MultiParamTypeClasses #-}
7{-# language MultiParamTypeClasses #-}
8−{-# language StandaloneKindSignatures #-}
9{-# language TypeFamilies #-}
8{-# language TypeFamilies #-}
10{-# language UndecidableInstances #-}
9{-# language UndecidableInstances #-}
11
10
@@ -172,17 +171,9 @@ data NestedTableTestB f = NestedTableTestB
172 deriving anyclass Rel8able
171 deriving anyclass Rel8able
173
172
174
173
174+
175newtype IdRecord a f = IdRecord { recordId :: Column f a }
175newtype IdRecord a f = IdRecord { recordId :: Column f a }
176 deriving stock Generic
176 deriving stock Generic
177
177
178
178
179instance DBType a => Rel8able (IdRecord a)
179instance DBType a => Rel8able (IdRecord a)
180−
181−
182−type Nest :: KRel8able -> KRel8able -> KRel8able
183−data Nest t u f = Nest
184− { foo :: t f
185− , bar :: u f
186− }
187− deriving stock Generic
188− deriving anyclass Rel8able