on 8de0eec help sign in
74 files changed+666−1824
since splitsnapshots
unifiedsplit
.github/workflows/build.yaml+3−3 Changelog.md+2−92 cabal.project+5 cabal.project.haskell-nix+6 default.nix+2−2 docs/cookbook.rst+7−9 nix/sources.json+9−9 nix/sources.nix+1−21 rel8.cabal+8−19 shell.nix+1−1 src/Rel8.hs+8−277 src/Rel8/Aggregate.hs+43−19 src/Rel8/Column/Null.hs−26 src/Rel8/Expr.hs−1 src/Rel8/Expr/Aggregate.hs+64−25 src/Rel8/Expr/Num.hs+6−39 src/Rel8/Expr/Opaleye.hs+3−12 src/Rel8/Expr/Sequence.hs+1−2 src/Rel8/Expr/Text.hs+2−23 src/Rel8/Expr/Window.hs−121 src/Rel8/Generic/Construction/ADT.hs−1 src/Rel8/Generic/Record.hs−2 src/Rel8/Generic/Rel8able.hs+1−6 src/Rel8/Generic/Table/ADT.hs−1 src/Rel8/Order.hs+8−1 src/Rel8/Query.hs+10−23 src/Rel8/Query/Aggregate.hs−19 src/Rel8/Query/Distinct.hs+9−4 src/Rel8/Query/Evaluate.hs+22−3 src/Rel8/Query/Exists.hs+1−1 src/Rel8/Query/Indexed.hs−21 src/Rel8/Query/List.hs+7−9 src/Rel8/Query/Null.hs−15 src/Rel8/Query/Opaleye.hs+11−11 src/Rel8/Query/Rebind.hs−27 src/Rel8/Query/SQL.hs+1−1 src/Rel8/Query/Set.hs+6−6 src/Rel8/Query/These.hs+8−8 src/Rel8/Query/Window.hs−26 src/Rel8/Schema/Context/Nullify.hs+14−6 src/Rel8/Schema/HTable.hs+2−29 src/Rel8/Schema/HTable/Vectorize.hs+2−10 src/Rel8/Schema/Table.hs−3 src/Rel8/Statement/Delete.hs+27−20 src/Rel8/Statement/Insert.hs+28−18 src/Rel8/Statement/Returning.hs+7 src/Rel8/Statement/SQL.hs+3−3 src/Rel8/Statement/Select.hs+29−57 src/Rel8/Statement/Update.hs+26−18 src/Rel8/Statement/View.hs+17−32 src/Rel8/Table/Aggregate.hs−12 src/Rel8/Table/HKD.hs−1 src/Rel8/Table/List.hs+3−14 src/Rel8/Table/Maybe.hs−6 src/Rel8/Table/NonEmpty.hs+3−14 src/Rel8/Table/Null.hs−147 src/Rel8/Table/Nullify.hs−27 src/Rel8/Table/Opaleye.hs+47−31 src/Rel8/Table/Ord.hs+2−2 src/Rel8/Table/Projection.hs+1−1 src/Rel8/Table/These.hs+3−14 src/Rel8/Table/Window.hs−43 src/Rel8/Tabulate.hs+21−24 src/Rel8/Type.hs+8−9 src/Rel8/Type/Enum.hs−1 src/Rel8/Type/JSONBEncoded.hs−4 src/Rel8/Type/JSONEncoded.hs−4 src/Rel8/Type/Num.hs+1−4 src/Rel8/Type/Ord.hs+2 src/Rel8/Type/ReadShow.hs−3 src/Rel8/Type/Tag.hs−1 src/Rel8/Window.hs−97 tests/Main.hs+164−273 tests/Rel8/Generic/Rel8able/Test.hs+1−10
.github/workflows/build.yaml+3−3
@@ -9,14 +9,14 @@ jobs:
99 persist-credentials: false
1010 submodules: true
1111
12− - uses: cachix/install-nix-action@v14.1
12+ - uses: cachix/install-nix-action@v12
1313 with:
1414 nix_path: nixpkgs=channel:nixos-unstable
1515 extra_nix_config: |
1616 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
1818
19− - uses: cachix/cachix-action@v10
19+ - uses: cachix/cachix-action@v8
2020 with:
2121 name: rel8
2222 authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
Changelog.md+2−92
@@ -1,90 +1,4 @@
1−# 1.4.0.0 (2002-08-17)
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)
1+# 1.1.0.0 (??)
882
893## New features
904
@@ -96,7 +10,7 @@
9610 , myTableY :: f Text
9711 } deriving (Generic, Rel8able)
9812 ```
99−
13+
10014* 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:
10115
10216 ```haskell
@@ -113,8 +27,6 @@
11327
11428* 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))
11529
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−
11830## Breaking changes
11931
12032* `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))
@@ -133,8 +45,6 @@
13345 * `Table` has a new associated type - `FromExprs`. This was previously an open type family.
13446 * `Table` has a new associated type - `Transpose` - and `Recontextualise` has been renamed to `Transposes`. This `Transposes` class now operates in terms of `Transpose`.
13547
136−* `select`, `insert`, `update` and `delete` now produce Hasql `Statement`s, rather than actually running the statement as IO. This allows Rel8 to be used with transaction/connection-managing monads like [`hasql-transaction`](https://hackage.haskell.org/package/hasql-transaction). ([#94](https://github.com/circuithub/rel8/pull/94))
137−
13848## Bug fixes
13949
14050* Fixes a bug where cartesian products of queries using `catListTable`, `catNonEmptyTable`, `catList` and `catNonEmpty` would incorrectly be zipped instead. ([#61](https://github.com/circuithub/rel8/pull/61))
cabal.project+5
@@ -1 +1,6 @@
11packages: .
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 @@
22-- will interpret them as local packages, and try to build them when we cabal
33-- build. The only reason we have to specify these is for Haskell.nix to know to
44-- 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 @@
11let
22 haskellNix = import (import ./nix/sources.nix)."haskell.nix" {};
33
4− nixpkgsSrc = haskellNix.sources.nixpkgs-unstable;
4+ nixpkgsSrc = haskellNix.sources.nixpkgs-2009;
55
66 nixpkgsArgs = haskellNix.nixpkgsArgs;
77
8− compiler-nix-name = "ghc924";
8+ compiler-nix-name = "ghc901";
99
1010 pkgs = import nixpkgsSrc nixpkgsArgs;
1111
docs/cookbook.rst+7−9
@@ -142,25 +142,23 @@ rows, but can also return *tree*\s.
142142To understand what this means, we'll consider a small example query for blog
143143posts. We want our query to return:
144144
145−1. The latest 5 blog posts that have at least one tag each.
145+1. The latest 5 blog posts.
1461462. 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.
148148
149149In Rel8, we can write this query as::
150150
151151 latestBlogPosts = do
152152 post <- each postSchema
153153
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
156155 tag <- each tagSchema
157156 where_ (tagPostId tag ==. postId post)
158− return (tagName tag)
157+ return (listAgg (tagName tag))
159158
160− -- Returns a `ListTable a` which ends up as a `[a]` after the query is run
161− latestComments <-
162− many $
163− limit 3 $
159+ latestComments <-
160+ many $
161+ limit 3 $
164162 orderBy (commentCreatedAt >$< desc) do
165163 comment <- each commentSchema
166164 where_ (commentPostId comment ==. postId post)
nix/sources.json+9−9
@@ -5,10 +5,10 @@
55 "homepage": "https://input-output-hk.github.io/haskell.nix",
66 "owner": "input-output-hk",
77 "repo": "haskell.nix",
8− "rev": "0837156c56cb5350dd9bd84eb424187e00aa1e8a",
9− "sha256": "1djmv5a0vfj78csw2cad6nicrs8iqrp928b9b3v4h7lwwhhzliwl",
8+ "rev": "3856d2d24dca0ecc71fcfc314253a2a2d07a3c4f",
9+ "sha256": "0s69nasqhkv8n0qmdv075k83p3z584ja9vkzdnp2291mymghvx87",
1010 "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",
1212 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
1313 },
1414 "niv": {
@@ -17,10 +17,10 @@
1717 "homepage": "https://github.com/nmattia/niv",
1818 "owner": "nmattia",
1919 "repo": "niv",
20− "rev": "acdb1726783637852c01e0ad639ac9cbec28d8c8",
21− "sha256": "1wszl8q70z6wfgkh2dvgmhhry6qp9nar421qq2ifd17hq2jj9ck3",
20+ "rev": "e0ca65c81a2d7a4d82a189f1e23a48d59ad42070",
21+ "sha256": "1pq9nh1d8nn3xvbdny8fafzw87mj7gsmp6pxkdl65w2g18rmcmzx",
2222 "type": "tarball",
23− "url": "https://github.com/nmattia/niv/archive/acdb1726783637852c01e0ad639ac9cbec28d8c8.tar.gz",
23+ "url": "https://github.com/nmattia/niv/archive/e0ca65c81a2d7a4d82a189f1e23a48d59ad42070.tar.gz",
2424 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
2525 },
2626 "nixpkgs": {
@@ -29,10 +29,10 @@
2929 "homepage": null,
3030 "owner": "nixos",
3131 "repo": "nixpkgs",
32− "rev": "1c1f5649bb9c1b0d98637c8c365228f57126f361",
33− "sha256": "0f2nvdijyxfgl5kwyb4465pppd5vkhqxddx6v40k2s0z9jfhj0xl",
32+ "rev": "59b8d9cf24e9fcf10341a0923c9bdca088dca8c8",
33+ "sha256": "08f38v4b2kcxnbapdwrb54bglka92cxj9qlnqlk5px206jyq9v4c",
3434 "type": "tarball",
35− "url": "https://github.com/nixos/nixpkgs/archive/1c1f5649bb9c1b0d98637c8c365228f57126f361.tar.gz",
35+ "url": "https://github.com/nixos/nixpkgs/archive/59b8d9cf24e9fcf10341a0923c9bdca088dca8c8.tar.gz",
3636 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
3737 }
3838}
nix/sources.nix+1−21
@@ -31,28 +31,8 @@ let
3131 if spec ? branch then "refs/heads/${spec.branch}" else
3232 if spec ? tag then "refs/tags/${spec.tag}" else
3333 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;
5334 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; };
5636
5737 fetch_local = spec: spec.path;
5838
rel8.cabal+8−19
@@ -1,6 +1,6 @@
11cabal-version: 2.0
22name: rel8
3−version: 1.4.0.0
3+version: 1.0.0.1
44synopsis: Hey! Hey! Can u rel8?
55license: BSD3
66license-file: LICENSE
@@ -20,14 +20,14 @@ source-repository head
2020library
2121 build-depends:
2222 aeson
23− , base ^>= 4.14 || ^>=4.15 || ^>=4.16
23+ , base ^>= 4.14 || ^>=4.15
2424 , bifunctors
2525 , bytestring
2626 , case-insensitive
2727 , comonad
2828 , contravariant
29− , hasql ^>= 1.4.5.1 || ^>= 1.5.0.0 || ^>= 1.6.0.0
30− , opaleye ^>= 0.9.6.1
29+ , hasql ^>= 1.4.5.1
30+ , opaleye ^>= 0.7.3.0
3131 , pretty
3232 , profunctors
3333 , product-profunctors
@@ -37,6 +37,7 @@ library
3737 , text
3838 , these
3939 , time
40+ , transformers
4041 , uuid
4142 default-language:
4243 Haskell2010
@@ -46,7 +47,6 @@ library
4647 -Wno-missing-import-lists -Wno-prepositive-qualified-module
4748 -Wno-monomorphism-restriction
4849 -Wno-missing-local-signatures
49− -Wno-missing-kind-signatures
5050 hs-source-dirs:
5151 src
5252 exposed-modules:
@@ -66,7 +66,6 @@ library
6666 Rel8.Column.List
6767 Rel8.Column.Maybe
6868 Rel8.Column.NonEmpty
69− Rel8.Column.Null
7069 Rel8.Column.These
7170
7271 Rel8.Expr
@@ -82,7 +81,6 @@ library
8281 Rel8.Expr.Order
8382 Rel8.Expr.Sequence
8483 Rel8.Expr.Serialize
85− Rel8.Expr.Window
8684
8785 Rel8.FCF
8886
@@ -109,19 +107,16 @@ library
109107 Rel8.Query.Evaluate
110108 Rel8.Query.Exists
111109 Rel8.Query.Filter
112− Rel8.Query.Indexed
113110 Rel8.Query.Limit
114111 Rel8.Query.List
115112 Rel8.Query.Maybe
116113 Rel8.Query.Null
117114 Rel8.Query.Opaleye
118115 Rel8.Query.Order
119− Rel8.Query.Rebind
120116 Rel8.Query.Set
121117 Rel8.Query.SQL
122118 Rel8.Query.These
123119 Rel8.Query.Values
124− Rel8.Query.Window
125120
126121 Rel8.Schema.Context.Nullify
127122 Rel8.Schema.Dict
@@ -170,7 +165,6 @@ library
170165 Rel8.Table.Maybe
171166 Rel8.Table.Name
172167 Rel8.Table.NonEmpty
173− Rel8.Table.Null
174168 Rel8.Table.Nullify
175169 Rel8.Table.Opaleye
176170 Rel8.Table.Ord
@@ -181,7 +175,6 @@ library
181175 Rel8.Table.These
182176 Rel8.Table.Transpose
183177 Rel8.Table.Undefined
184− Rel8.Table.Window
185178
186179 Rel8.Type
187180 Rel8.Type.Array
@@ -200,9 +193,6 @@ library
200193 Rel8.Type.Sum
201194 Rel8.Type.Tag
202195
203− Rel8.Window
204−
205−
206196test-suite tests
207197 type: exitcode-stdio-1.0
208198 build-depends:
@@ -211,9 +201,9 @@ test-suite tests
211201 , case-insensitive
212202 , containers
213203 , hasql
214− , hasql-transaction
215− , hedgehog ^>= 1.0 || ^>= 1.1
216− , mmorph
204+ , hedgehog ^>=1.0.2
205+ , lifted-base ^>=0.2.3.12
206+ , monad-control ^>=1.0.2.3
217207 , rel8
218208 , scientific
219209 , tasty
@@ -221,7 +211,6 @@ test-suite tests
221211 , text
222212 , time
223213 , tmp-postgres ^>=1.34.1.0
224− , transformers
225214 , uuid
226215
227216 other-modules:
shell.nix+1−1
@@ -1,6 +1,6 @@
11let
22 sources = import ./nix/sources.nix;
3− pkgs = import sources.nixpkgs {overlays = [];};
3+ pkgs = import sources.nixpkgs {};
44 hsPkgs = import ./default.nix;
55in
66 hsPkgs.shellFor {
src/Rel8.hs+8−277
@@ -38,7 +38,6 @@ module Rel8
3838 , HMaybe
3939 , HList
4040 , HNonEmpty
41− , HNull
4241 , HThese
4342 , Lift
4443
@@ -47,18 +46,16 @@ module Rel8
4746 , Transposes
4847 , AltTable((<|>:))
4948 , AlternativeTable( emptyTable )
50− , EqTable(..), (==:), (/=:)
51− , OrdTable(..), (<:), (<=:), (>:), (>=:), ascTable, descTable, greatest, least
49+ , EqTable, (==:), (/=:)
50+ , OrdTable, ascTable, descTable
5251 , lit
5352 , bool
5453 , case_
55− , castTable
5654
5755 -- ** @MaybeTable@
5856 , MaybeTable
5957 , maybeTable, ($?), nothingTable, justTable
6058 , isNothingTable, isJustTable
61− , fromMaybeTable
6259 , optional
6360 , catMaybeTable
6461 , traverseMaybeTable
@@ -81,7 +78,6 @@ module Rel8
8178 , isThisTable, isThatTable, isThoseTable
8279 , hasHereTable, hasThereTable
8380 , justHereTable, justThereTable
84− , alignMaybeTable
8581 , alignBy
8682 , keepHereTable, loseHereTable
8783 , keepThereTable, loseThereTable
@@ -94,7 +90,7 @@ module Rel8
9490
9591 -- ** @ListTable@
9692 , ListTable
97− , listTable, ($*)
93+ , listTable
9894 , nameListTable
9995 , many
10096 , manyExpr
@@ -103,44 +99,21 @@ module Rel8
10399
104100 -- ** @NonEmptyTable@
105101 , NonEmptyTable
106− , nonEmptyTable, ($+)
102+ , nonEmptyTable
107103 , nameNonEmptyTable
108104 , some
109105 , someExpr
110106 , catNonEmptyTable
111107 , catNonEmpty
112108
113− -- ** @NullTable@
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
109+ -- ** @ADT@
127110 , ADT, ADTable
128−
129− -- *** Deconstruction of ADTs
130− -- $deconstruction
131− , DeconstructADT, deconstructADT
132−
133− -- *** Construction of ADTs
134− -- $construction
135111 , BuildADT, buildADT
136112 , ConstructADT, constructADT
137−
138− -- *** Other ADT operations
113+ , DeconstructADT, deconstructADT
114+ , NameADT, nameADT
139115 , AggregateADT, aggregateADT
140116
141− -- *** Miscellaneous notes
142− -- $misc-notes
143−
144117 -- ** @HKD@
145118 , HKD, HKDable
146119 , BuildHKD, buildHKD
@@ -165,7 +138,6 @@ module Rel8
165138 -- ** @null@
166139 , NotNull
167140 , Nullable
168− , Homonullable
169141 , null
170142 , nullify
171143 , nullable
@@ -184,7 +156,6 @@ module Rel8
184156 , (==.), (/=.), (==?), (/=?)
185157 , in_
186158 , boolExpr, caseExpr
187− , like, ilike
188159
189160 -- ** Ordering
190161 , DBOrd
@@ -253,11 +224,10 @@ module Rel8
253224 , countRows
254225 , groupBy
255226 , listAgg, listAggExpr
256− , mode
257227 , nonEmptyAgg, nonEmptyAggExpr
258228 , DBMax, max
259229 , DBMin, min
260− , DBSum, sum, sumWhere, avg
230+ , DBSum, sum, sumWhere
261231 , DBString, stringAgg
262232 , count
263233 , countStar
@@ -274,29 +244,6 @@ module Rel8
274244 , nullsFirst
275245 , nullsLast
276246
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−
300247 -- ** Bindings
301248 , rebind
302249
@@ -334,7 +281,6 @@ module Rel8
334281
335282 -- ** @CREATE VIEW@
336283 , createView
337− , createOrReplaceView
338284
339285 -- ** Sequences
340286 , nextval
@@ -353,7 +299,6 @@ import Rel8.Column.Lift
353299import Rel8.Column.List
354300import Rel8.Column.Maybe
355301import Rel8.Column.NonEmpty
356−import Rel8.Column.Null
357302import Rel8.Column.These
358303import Rel8.Expr
359304import Rel8.Expr.Aggregate
@@ -367,8 +312,6 @@ import Rel8.Expr.Ord
367312import Rel8.Expr.Order
368313import Rel8.Expr.Serialize
369314import Rel8.Expr.Sequence
370−import Rel8.Expr.Text ( like, ilike )
371−import Rel8.Expr.Window hiding ( cumulative )
372315import Rel8.Generic.Rel8able ( KRel8able, Rel8able )
373316import Rel8.Order
374317import Rel8.Query
@@ -379,18 +322,15 @@ import Rel8.Query.Either
379322import Rel8.Query.Evaluate
380323import Rel8.Query.Exists
381324import Rel8.Query.Filter
382−import Rel8.Query.Indexed
383325import Rel8.Query.Limit
384326import Rel8.Query.List
385327import Rel8.Query.Maybe
386328import Rel8.Query.Null
387329import Rel8.Query.Order
388−import Rel8.Query.Rebind
389330import Rel8.Query.SQL (showQuery)
390331import Rel8.Query.Set
391332import Rel8.Query.These
392333import Rel8.Query.Values
393−import Rel8.Query.Window
394334import Rel8.Schema.Field
395335import Rel8.Schema.HTable
396336import Rel8.Schema.Name
@@ -417,8 +357,6 @@ import Rel8.Table.List
417357import Rel8.Table.Maybe
418358import Rel8.Table.Name
419359import Rel8.Table.NonEmpty
420−import Rel8.Table.Null
421−import Rel8.Table.Opaleye ( castTable )
422360import Rel8.Table.Ord
423361import Rel8.Table.Order
424362import Rel8.Table.Projection
@@ -426,7 +364,6 @@ import Rel8.Table.Rel8able ()
426364import Rel8.Table.Serialize
427365import Rel8.Table.These
428366import Rel8.Table.Transpose
429−import Rel8.Table.Window
430367import Rel8.Type
431368import Rel8.Type.Composite
432369import Rel8.Type.Eq
@@ -441,7 +378,6 @@ import Rel8.Type.ReadShow
441378import Rel8.Type.Semigroup
442379import Rel8.Type.String
443380import Rel8.Type.Sum
444−import Rel8.Window
445381
446382
447383-- $running
@@ -449,208 +385,3 @@ import Rel8.Window
449385-- provides 'select', 'insert', 'update' and 'delete' functions. Note that
450386-- 'insert', 'update' and 'delete' will generally need the
451387-- `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 @@
22{-# language FlexibleContexts #-}
33{-# language FlexibleInstances #-}
44{-# language MultiParamTypeClasses #-}
5+{-# language NamedFieldPuns #-}
56{-# language RankNTypes #-}
67{-# language StandaloneKindSignatures #-}
78{-# language TypeFamilies #-}
89{-# language UndecidableInstances #-}
910
1011module Rel8.Aggregate
11− ( Aggregate(..), zipOutputs
12− , unsafeMakeAggregate
12+ ( Aggregate(..), foldInputs, mapInputs
13+ , Aggregator(..), unsafeMakeAggregate
1314 , Aggregates
1415 )
1516where
1617
1718-- base
18−import Control.Applicative ( liftA2 )
19+import Data.Functor.Const ( Const( Const ), getConst )
1920import Data.Functor.Identity ( Identity( Identity ) )
2021import Data.Kind ( Constraint, Type )
2122import Prelude
2223
23−-- profunctors
24−import Data.Profunctor ( dimap )
25−
2624-- opaleye
27−import qualified Opaleye.Aggregate as Opaleye
25+import qualified Opaleye.Internal.Aggregate as Opaleye
2826import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
29−import qualified Opaleye.Internal.Column as Opaleye
27+import qualified Opaleye.Internal.PackMap as Opaleye
3028
3129-- rel8
3230import Rel8.Expr ( Expr )
@@ -42,7 +40,11 @@ import Rel8.Table.Transpose ( Transposes )
4240import Rel8.Type ( DBType )
4341
4442
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.
4648type Aggregate :: K.Context
4749newtype Aggregate a = Aggregate (Opaleye.Aggregator () (Expr a))
4850
@@ -59,25 +61,47 @@ instance Sql DBType a => Table Aggregate (Aggregate a) where
5961 fromResult (HIdentity (Identity a)) = a
6062
6163
62−-- | @Aggregates a b@ means that the columns in @a@ are all 'Aggregate's
63−-- for the 'Expr' columns in @b@.
64+-- | @Aggregates a b@ means that the columns in @a@ are all 'Aggregate' 'Expr's
65+-- for the columns in @b@.
6466type Aggregates :: Type -> Type -> Constraint
6567class Transposes Aggregate Expr aggregates exprs => Aggregates aggregates exprs
6668instance Transposes Aggregate Expr aggregates exprs => Aggregates aggregates exprs
6769
6870
69−zipOutputs :: ()
70− => (Expr a -> Expr b -> Expr c) -> Aggregate a -> Aggregate b -> Aggregate c
71−zipOutputs f (Aggregate a) (Aggregate b) = Aggregate (liftA2 f a b)
71+foldInputs :: forall (a :: Type) (b :: Type). Monoid b
72+ => (Maybe Aggregator -> Opaleye.PrimExpr -> b) -> Aggregate 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+ }
7294
7395
74−unsafeMakeAggregate :: forall (input :: Type) (output :: Type) n n' a a'. ()
96+unsafeMakeAggregate :: forall (input :: Type) (output :: Type). ()
7597 => (Expr input -> Opaleye.PrimExpr)
7698 -> (Opaleye.PrimExpr -> Expr output)
77− -> Opaleye.Aggregator (Opaleye.Field_ n a) (Opaleye.Field_ n' a')
99+ -> Maybe Aggregator
78100 -> Expr input
79101 -> Aggregate output
80102unsafeMakeAggregate input output aggregator expr =
81− Aggregate $ dimap in_ out aggregator
82− where out = output . Opaleye.unColumn
83− in_ = Opaleye.Column . input . const expr
103+ Aggregate $ Opaleye.Aggregator $ Opaleye.PackMap $ \f _ ->
104+ output <$> f (tuplize <$> aggregator, input 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 @@
11{-# language DataKinds #-}
22{-# language DerivingStrategies #-}
3−{-# language FlexibleContexts #-}
43{-# language FlexibleInstances #-}
54{-# language MultiParamTypeClasses #-}
65{-# language ScopedTypeVariables #-}
src/Rel8/Expr/Aggregate.hs+64−25
@@ -10,7 +10,6 @@ module Rel8.Expr.Aggregate
1010 , and, or
1111 , min, max
1212 , sum, sumWhere
13− , avg
1413 , stringAgg
1514 , groupByExpr
1615 , listAggExpr, nonEmptyAggExpr
@@ -24,12 +23,10 @@ import Data.List.NonEmpty ( NonEmpty )
2423import Prelude hiding ( and, max, min, null, or, sum )
2524
2625-- opaleye
27−import qualified Opaleye.Internal.Aggregate as Opaleye
28−import Opaleye.Internal.Column ( Field_( Column ) )
29−import qualified Opaleye.Aggregate as Opaleye
26+import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
3027
3128-- rel8
32−import Rel8.Aggregate ( Aggregate, unsafeMakeAggregate )
29+import Rel8.Aggregate ( Aggregate, Aggregator(..), unsafeMakeAggregate )
3330import Rel8.Expr ( Expr )
3431import Rel8.Expr.Bool ( caseExpr )
3532import Rel8.Expr.Opaleye
@@ -53,13 +50,23 @@ import Rel8.Type.Sum ( DBSum )
5350
5451-- | Count the occurances of a single column. Corresponds to @COUNT(a)@
5552count :: 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+
5760
5861-- | Count the number of distinct occurances of a single column. Corresponds to
5962-- @COUNT(DISTINCT a)@
6063countDistinct :: Sql DBEq a => Expr a -> Aggregate Int64
6164countDistinct = unsafeMakeAggregate toPrimExpr fromPrimExpr $
62− Opaleye.distinctAggregator Opaleye.count
65+ Just Aggregator
66+ { operation = Opaleye.AggrCount
67+ , ordering = []
68+ , distinction = Opaleye.AggrDistinct
69+ }
6370
6471
6572-- | Corresponds to @COUNT(*)@.
@@ -74,39 +81,56 @@ countWhere condition = count (caseExpr [(condition, litExpr (Just True))] null)
7481
7582-- | Corresponds to @bool_and@.
7683and :: 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+ }
7890
7991
8092-- | Corresponds to @bool_or@.
8193or :: 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+ }
83100
84101
85102-- | Produce an aggregation for @Expr a@ using the @max@ function.
86103max :: 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+ }
88110
89111
90112-- | Produce an aggregation for @Expr a@ using the @max@ function.
91113min :: 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+ }
93120
94121-- | Corresponds to @sum@. Note that in SQL, @sum@ is type changing - for
95122-- 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
97124-- lead to overflows, and if you anticipate very large sums, you should upcast
98125-- your input.
99126sum :: Sql DBSum a => Expr a -> Aggregate a
100−sum = unsafeMakeAggregate toPrimExpr (castExpr . fromPrimExpr) Opaleye.unsafeSum
101−
127+sum = unsafeMakeAggregate toPrimExpr (castExpr . fromPrimExpr) $
128+ Just Aggregator
129+ { operation = Opaleye.AggrSum
130+ , ordering = []
131+ , distinction = Opaleye.AggrAll
132+ }
102133
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
110134
111135-- | Take the sum of all expressions that satisfy a predicate.
112136sumWhere :: (Sql DBNum a, Sql DBSum a)
@@ -118,12 +142,17 @@ sumWhere condition a = sum (caseExpr [(condition, a)] 0)
118142stringAgg :: Sql DBString a
119143 => Expr db -> Expr a -> Aggregate a
120144stringAgg 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+ }
122151
123152
124153-- | Aggregate a value by grouping by it.
125154groupByExpr :: Sql DBEq a => Expr a -> Aggregate a
126−groupByExpr = unsafeMakeAggregate toPrimExpr fromPrimExpr Opaleye.groupBy
155+groupByExpr = unsafeMakeAggregate toPrimExpr fromPrimExpr Nothing
127156
128157
129158-- | Collect expressions values as a list.
@@ -138,13 +167,23 @@ nonEmptyAggExpr = snonEmptyAggExpr typeInformation
138167
139168slistAggExpr :: ()
140169 => 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+ }
142176 where
143177 to = encodeArrayElement info . toPrimExpr
144178
145179
146180snonEmptyAggExpr :: ()
147181 => 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+ }
149188 where
150189 to = encodeArrayElement info . toPrimExpr
src/Rel8/Expr/Num.hs+6−39
@@ -4,24 +4,18 @@
44{-# options_ghc -fno-warn-redundant-constraints #-}
55
66module 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
128 )
139where
1410
1511-- base
16−import Prelude ( (+), (-), fst, negate, signum, snd )
12+import Prelude ()
1713
1814-- rel
1915import Rel8.Expr ( Expr( Expr ) )
20−import Rel8.Expr.Eq ( (==.) )
2116import Rel8.Expr.Function ( function )
2217import Rel8.Expr.Opaleye ( castExpr )
2318import Rel8.Schema.Null ( Homonullable, Sql )
24−import Rel8.Table.Bool ( bool )
2519import Rel8.Type.Num ( DBFractional, DBIntegral, DBNum )
2620
2721
@@ -48,41 +42,14 @@ ceiling :: (Sql DBFractional a, Sql DBIntegral b, Homonullable a b)
4842ceiling = function "ceiling"
4943
5044
51−-- | Emulates the behaviour of the Haskell function 'Prelude.div' in
52−-- PostgreSQL.
45+-- | Perform integral division. Corresponds to the @div()@ function.
5346div :: Sql DBIntegral a => Expr a -> Expr a -> Expr a
54−div n d = fst (divMod n d)
47+div = function "div"
5548
5649
57−-- | Emulates the behaviour of the Haskell function 'Prelude.mod' in
58−-- PostgreSQL.
50+-- | Corresponds to the @mod()@ function.
5951mod :: Sql DBIntegral a => Expr a -> Expr a -> Expr a
60−mod n d = snd (divMod n d)
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)
52+mod = function "mod"
8653
8754
8855-- | 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
1010 , scastExpr, sunsafeCastExpr
1111 , unsafeLiteral
1212 , fromPrimExpr, toPrimExpr, mapPrimExpr, zipPrimExprsWith, traversePrimExpr
13− , toColumn, fromColumn, traverseFieldP
13+ , toColumn, fromColumn
1414 )
1515where
1616
@@ -27,9 +27,6 @@ import Rel8.Schema.Null ( Unnullify, Sql )
2727import Rel8.Type ( DBType, typeInformation )
2828import Rel8.Type.Information ( TypeInformation(..) )
2929
30−-- profunctors
31−import Data.Profunctor ( Profunctor, dimap )
32−
3330
3431castExpr :: Sql DBType a => Expr a -> Expr a
3532castExpr = scastExpr typeInformation
@@ -83,15 +80,9 @@ traversePrimExpr :: Functor f
8380traversePrimExpr f = fmap fromPrimExpr . f . toPrimExpr
8481
8582
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
9384toColumn = Opaleye.Column
9485
9586
96−fromColumn :: Opaleye.Field_ n b -> Opaleye.PrimExpr
87+fromColumn :: Opaleye.Column b -> Opaleye.PrimExpr
9788fromColumn (Opaleye.Column a) = a
src/Rel8/Expr/Sequence.hs+1−2
@@ -11,7 +11,6 @@ import Prelude
1111import Rel8.Expr ( Expr )
1212import Rel8.Expr.Function ( function )
1313import Rel8.Expr.Serialize ( litExpr )
14−import Rel8.Expr.Text ( quoteIdent )
1514
1615-- text
1716import Data.Text ( pack )
@@ -19,4 +18,4 @@ import Data.Text ( pack )
1918
2019-- | See https://www.postgresql.org/docs/current/functions-sequence.html
2120nextval :: 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
1717 , pgClientEncoding, quoteIdent, quoteLiteral, quoteNullable, regexpReplace
1818 , regexpSplitToArray, repeat, replace, reverse, right, rpad, rtrim
1919 , splitPart, strpos, substr, translate
20−
21− -- * @LIKE@ and @ILIKE@
22− , like, ilike
2320 )
2421where
2522
@@ -27,7 +24,7 @@ where
2724import Data.Bool ( Bool )
2825import Data.Int ( Int32 )
2926import Data.Maybe ( Maybe( Nothing, Just ) )
30−import Prelude ( flip )
27+import Prelude ()
3128
3229-- bytestring
3330import Data.ByteString ( ByteString )
@@ -52,7 +49,7 @@ infixr 6 ++.
5249
5350
5451-- | Matches regular expression, case sensitive
55−--
52+--
5653-- Corresponds to the @~.@ operator.
5754(~.) :: Expr Text -> Expr Text -> Expr Bool
5855(~.) = binaryOperator "~."
@@ -276,21 +273,3 @@ substr a b Nothing = function "substr" a b
276273-- | Corresponds to the @translate@ function.
277274translate :: Expr Text -> Expr Text -> Expr Text -> Expr Text
278275translate = 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 @@
11{-# language AllowAmbiguousTypes #-}
22{-# language BlockArguments #-}
33{-# language DataKinds #-}
4−{-# language FlexibleContexts #-}
54{-# language FlexibleInstances #-}
65{-# language MultiParamTypeClasses #-}
76{-# language RankNTypes #-}
src/Rel8/Generic/Record.hs−2
@@ -1,6 +1,5 @@
11{-# language AllowAmbiguousTypes #-}
22{-# language DataKinds #-}
3−{-# language FlexibleContexts #-}
43{-# language FlexibleInstances #-}
54{-# language MultiParamTypeClasses #-}
65{-# language PolyKinds #-}
@@ -142,7 +141,6 @@ instance {-# OVERLAPPABLE #-} Snd (Count n rep) ~ rep => Countable n rep where
142141 uncount = id
143142
144143
145−type Record :: Type -> Type
146144newtype Record a = Record
147145 { unrecord :: a
148146 }
src/Rel8/Generic/Rel8able.hs+1−6
@@ -63,11 +63,6 @@ type KRel8able = K.Rel8able
6363-- This is almost 'Data.Type.Equality.==', but we add an extra case.
6464type (==) :: k -> k -> Bool
6565type 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−
7166 -- This extra case is needed to solve the equation "a == Identity a",
7267 -- which occurs when we have polymorphic Rel8ables
7368 -- (e.g., newtype T a f = T { x :: Column f a })
@@ -146,7 +141,7 @@ type instance Eval (TSerialize expr a) =
146141-- [Supplying @Rel8able@ instances]
147142--
148143-- 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
150145-- extensions:
151146--
152147-- @
src/Rel8/Generic/Table/ADT.hs−1
@@ -1,6 +1,5 @@
11{-# language AllowAmbiguousTypes #-}
22{-# language DataKinds #-}
3−{-# language FlexibleContexts #-}
43{-# language FlexibleInstances #-}
54{-# language LambdaCase #-}
65{-# language MultiParamTypeClasses #-}
src/Rel8/Order.hs+8−1
@@ -4,6 +4,7 @@
44
55module Rel8.Order
66 ( Order(..)
7+ , toOrderExprs
78 )
89where
910
@@ -16,7 +17,8 @@ import Prelude
1617import Data.Functor.Contravariant.Divisible ( Decidable, Divisible )
1718
1819-- 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
2022
2123
2224-- | An ordering expression for @a@. Primitive orderings are defined with
@@ -28,3 +30,8 @@ import qualified Opaleye.Order as Opaleye
2830type Order :: Type -> Type
2931newtype Order a = Order (Opaleye.Order a)
3032 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 #-}
21{-# language StandaloneKindSignatures #-}
3−{-# language UndecidableInstances #-}
42
53module Rel8.Query
64 ( Query( Query )
@@ -17,16 +15,15 @@ import Prelude
1715-- opaleye
1816import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
1917import qualified Opaleye.Internal.PackMap as Opaleye
20−import qualified Opaleye.Internal.PrimQuery as Opaleye hiding (lateral)
18+import qualified Opaleye.Internal.PrimQuery as Opaleye
2119import qualified Opaleye.Internal.QueryArr as Opaleye
2220import qualified Opaleye.Internal.Tag as Opaleye
2321
2422-- rel8
25−import Rel8.Expr ( Expr )
2623import Rel8.Query.Set ( unionAll )
2724import Rel8.Query.Opaleye ( fromOpaleye )
2825import Rel8.Query.Values ( values )
29−import Rel8.Table ( Table, fromColumns, toColumns )
26+import Rel8.Table ( fromColumns, toColumns )
3027import Rel8.Table.Alternative
3128 ( AltTable, (<|>:)
3229 , AlternativeTable, emptyTable
@@ -178,14 +175,14 @@ instance Bind Query where
178175
179176
180177instance Monad Query where
181− Query q >>= f = Query $ \dummies -> Opaleye.stateQueryArr $ \_ tag ->
178+ Query q >>= f = Query $ \dummies -> Opaleye.QueryArr $ \(_, tag) ->
182179 let
183− qa = q dummies
184− ((m, a), query, tag') = Opaleye.runStateQueryArr qa () tag
180+ Opaleye.QueryArr qa = q dummies
181+ ((m, a), query, tag') = qa ((), tag)
185182 Query q' = f a
186183 (dummies', query', tag'') =
187184 ( dummy : dummies
188− , query <> Opaleye.aRebind bindings
185+ , \lateral -> Opaleye.Rebind True bindings . query lateral
189186 , Opaleye.next tag'
190187 )
191188 where
@@ -193,11 +190,11 @@ instance Monad Query where
193190 where
194191 random = Opaleye.FunExpr "random" []
195192 name = Opaleye.extractAttr "dummy" tag'
196− qa' = Opaleye.lateral $ \_ -> q' dummies'
197− ((m'@(Any needsDummies), b), query'', tag''') = Opaleye.runStateQueryArr qa' () tag''
193+ Opaleye.QueryArr qa' = Opaleye.lateral $ \_ -> q' dummies'
194+ ((m'@(Any needsDummies), b), query'', tag''') = qa' ((), tag'')
198195 query'''
199− | needsDummies = query' <> query''
200− | otherwise = query <> query''
196+ | needsDummies = \lateral -> query'' lateral . query' lateral
197+ | otherwise = \lateral -> query'' lateral . query lateral
201198 m'' = m <> m'
202199 in
203200 ((m'', b), query''', tag''')
@@ -211,13 +208,3 @@ instance AltTable Query where
211208-- | 'emptyTable' = 'values' @[]@.
212209instance AlternativeTable Query where
213210 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 @@
11{-# language FlexibleContexts #-}
22{-# language MonoLocalBinds #-}
3−{-# language ScopedTypeVariables #-}
4−{-# language TypeApplications #-}
53
64module Rel8.Query.Aggregate
75 ( aggregate
86 , countRows
9− , mode
107 )
118where
129
1310-- base
14−import Data.Functor.Contravariant ( (>$<) )
1511import Data.Int ( Int64 )
1612import Prelude
1713
@@ -22,16 +18,9 @@ import qualified Opaleye.Aggregate as Opaleye
2218import Rel8.Aggregate ( Aggregates )
2319import Rel8.Expr ( Expr )
2420import Rel8.Expr.Aggregate ( countStar )
25−import Rel8.Expr.Order ( desc )
2621import Rel8.Query ( Query )
27−import Rel8.Query.Limit ( limit )
2822import Rel8.Query.Maybe ( optional )
2923import 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 )
3524import Rel8.Table.Opaleye ( aggregator )
3625import Rel8.Table.Maybe ( maybeTable )
3726
@@ -46,11 +35,3 @@ aggregate = mapOpaleye (Opaleye.aggregate aggregator)
4635-- will return @0@.
4736countRows :: Query a -> Query (Expr Int64)
4837countRows = 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+
13module Rel8.Query.Distinct
24 ( distinct
35 , distinctOn
@@ -6,11 +8,12 @@ module Rel8.Query.Distinct
68where
79
810-- base
9−import Prelude ()
11+import Prelude
1012
1113-- opaleye
1214import 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
1417
1518-- rel8
1619import Rel8.Order ( Order( Order ) )
@@ -30,11 +33,13 @@ distinct = mapOpaleye (Opaleye.distinctExplicit distinctspec)
3033-- to a projection. If multiple rows have the same projection, it is
3134-- unspecified which row will be returned. If this matters, use 'distinctOnBy'.
3235distinctOn :: EqTable b => (a -> b) -> Query a -> Query a
33−distinctOn proj = mapOpaleye (Opaleye.distinctOnExplicit unpackspec proj)
36+distinctOn proj =
37+ mapOpaleye (\q -> Opaleye.productQueryArr (Opaleye.distinctOn unpackspec proj . Opaleye.runSimpleQueryArr q))
3438
3539
3640-- | Select all distinct rows from a query, where rows are equivalent according
3741-- to a projection. If there are multiple rows with the same projection, the
3842-- first row according to the specified 'Order' will be returned.
3943distinctOnBy :: 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 @@
33
44module Rel8.Query.Evaluate
55 ( evaluate
6+ , rebind
67 )
78where
89
@@ -15,23 +16,28 @@ import Prelude hiding ( undefined )
1516
1617-- opaleye
1718import 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
1824
1925-- rel8
2026import Rel8.Expr ( Expr )
2127import Rel8.Expr.Bool ( (&&.) )
2228import Rel8.Expr.Opaleye ( fromPrimExpr )
2329import Rel8.Query ( Query( Query ) )
24−import Rel8.Query.Rebind ( rebind )
2530import Rel8.Table ( Table )
2631import Rel8.Table.Bool ( case_ )
27−import Rel8.Table.Undefined ( undefined )
32+import Rel8.Table.Opaleye ( unpackspec )
33+import Rel8.Table.Undefined
2834
2935
3036-- | 'evaluate' takes expressions that could potentially have side effects and
3137-- \"runs\" them in the 'Query' monad. The returned expressions have no side
3238-- effects and can safely be reused.
3339evaluate :: Table Expr a => a -> Query a
34−evaluate = laterally >=> rebind "eval"
40+evaluate = laterally >=> rebind
3541
3642
3743laterally :: Table Expr a => a -> Query a
@@ -45,5 +51,18 @@ laterally a = Query $ \bindings -> pure $ (Any True,) $
4551 go = fromPrimExpr . Opaleye.UnExpr Opaleye.OpIsNotNull
4652
4753
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+
4867foldl1' :: (a -> a -> a) -> NonEmpty a -> a
4968foldl1' f (a :| as) = foldl' f a as
src/Rel8/Query/Exists.hs+1−1
@@ -12,7 +12,7 @@ import Prelude hiding ( filter )
1212
1313-- opaleye
1414import qualified Opaleye.Exists as Opaleye
15−import qualified Opaleye.Operators as Opaleye
15+import qualified Opaleye.Operators as Opaleye hiding ( exists )
1616
1717-- rel8
1818import 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 )
2424import Rel8.Expr.Opaleye ( mapPrimExpr )
2525import Rel8.Query ( Query )
2626import Rel8.Query.Aggregate ( aggregate )
27+import Rel8.Query.Evaluate ( rebind )
2728import Rel8.Query.Maybe ( optional )
28−import Rel8.Query.Rebind ( rebind )
2929import Rel8.Schema.HTable.Vectorize ( hunvectorize )
3030import Rel8.Schema.Null ( Sql, Unnullify )
3131import Rel8.Schema.Spec ( Spec( Spec, info ) )
@@ -85,9 +85,8 @@ someExpr = aggregate . fmap nonEmptyAggExpr
8585--
8686-- @catListTable@ is an inverse to 'many'.
8787catListTable :: Table Expr a => ListTable Expr a -> Query a
88−catListTable (ListTable as) =
89− rebind "unnest" $ fromColumns $ runIdentity $
90− hunvectorize (\Spec {info} -> pure . sunnest info) as
88+catListTable (ListTable as) = rebind $ fromColumns $ runIdentity $
89+ hunvectorize (\Spec {info} -> pure . sunnest info) as
9190
9291
9392-- | Expand a 'NonEmptyTable' into a 'Query', where each row in the query is an
@@ -95,9 +94,8 @@ catListTable (ListTable as) =
9594--
9695-- @catNonEmptyTable@ is an inverse to 'some'.
9796catNonEmptyTable :: Table Expr a => NonEmptyTable Expr a -> Query a
98−catNonEmptyTable (NonEmptyTable as) =
99− rebind "unnest" $ fromColumns $ runIdentity $
100− hunvectorize (\Spec {info} -> pure . sunnest info) as
97+catNonEmptyTable (NonEmptyTable as) = rebind $ fromColumns $ runIdentity $
98+ hunvectorize (\Spec {info} -> pure . sunnest info) as
10199
102100
103101-- | Expand an expression that contains a list into a 'Query', where each row
@@ -105,7 +103,7 @@ catNonEmptyTable (NonEmptyTable as) =
105103--
106104-- @catList@ is an inverse to 'manyExpr'.
107105catList :: Sql DBType a => Expr [a] -> Query (Expr a)
108−catList = rebind "unnest" . sunnest typeInformation
106+catList = rebind . sunnest typeInformation
109107
110108
111109-- | Expand an expression that contains a non-empty list into a 'Query', where
@@ -113,7 +111,7 @@ catList = rebind "unnest" . sunnest typeInformation
113111--
114112-- @catNonEmpty@ is an inverse to 'someExpr'.
115113catNonEmpty :: Sql DBType a => Expr (NonEmpty a) -> Query (Expr a)
116−catNonEmpty = rebind "unnest" . sunnest typeInformation
114+catNonEmpty = rebind . sunnest typeInformation
117115
118116
119117sunnest :: TypeInformation (Unnullify a) -> Expr (list a) -> Expr a
src/Rel8/Query/Null.hs−15
@@ -1,8 +1,5 @@
1−{-# language FlexibleContexts #-}
2−
31module Rel8.Query.Null
42 ( catNull
5− , catNullTable
63 )
74where
85
@@ -12,8 +9,6 @@ import Prelude
129-- rel8
1310import Rel8.Expr ( Expr )
1411import Rel8.Expr.Null ( isNonNull, unsafeUnnullify )
15−import Rel8.Table ( Table )
16−import Rel8.Table.Null ( NullTable, isNonNullTable, unsafeUnnullifyTable )
1712import Rel8.Query ( Query )
1813import Rel8.Query.Filter ( where_ )
1914
@@ -26,13 +21,3 @@ catNull :: Expr (Maybe a) -> Query (Expr a)
2621catNull a = do
2722 where_ $ isNonNull a
2823 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
4141
4242unsafePeekQuery :: Query a -> a
4343unsafePeekQuery (Query q) = case q mempty of
44− f -> case Opaleye.runStateQueryArr f () Opaleye.start of
44+ Opaleye.QueryArr f -> case f ((), Opaleye.start) of
4545 ((_, a), _, _) -> a
4646
4747
4848mapping :: ()
4949 => (Opaleye.Select a -> Opaleye.Select b)
5050 -> 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) ->
5252 let
53− ((m, _), _, _) = Opaleye.runStateQueryArr q () tag
54− q' = (m,) <$> f (snd <$> q)
53+ ((m, _), _, _) = qa ((), tag)
54+ Opaleye.QueryArr qa' = (m,) <$> f (snd <$> q)
5555 in
56− Opaleye.runStateQueryArr q' () tag
56+ qa' ((), tag)
5757
5858
5959zipping :: Semigroup m
6060 => (Opaleye.Select a -> Opaleye.Select b -> Opaleye.Select c)
6161 -> Opaleye.Select (m, a) -> Opaleye.Select (m, b) -> Opaleye.Select (m, c)
62−zipping f q q' =
63− Opaleye.stateQueryArr $ \_ tag ->
62+zipping f q@(Opaleye.QueryArr qa) q'@(Opaleye.QueryArr qa') =
63+ Opaleye.QueryArr $ \(_, tag) ->
6464 let
65− ((m, _), _, _) = Opaleye.runStateQueryArr q () tag
66− ((m', _), _, _) = Opaleye.runStateQueryArr q' () tag
65+ ((m, _), _, _) = qa ((), tag)
66+ ((m', _), _, _) = qa' ((), tag)
6767 m'' = m <> m'
68− q'' = (m'',) <$> f (snd <$> q) (snd <$> q')
68+ Opaleye.QueryArr qa'' = (m'',) <$> f (snd <$> q) (snd <$> q')
6969 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/SQL.hs+1−1
@@ -18,4 +18,4 @@ import Rel8.Table ( Table )
1818
1919-- | Convert a 'Query' to a 'String' containing a @SELECT@ statement.
2020showQuery :: Table Expr a => Query a -> String
21−showQuery = show . ppSelect
21+showQuery = foldMap show . ppSelect
src/Rel8/Query/Set.hs+6−6
@@ -23,36 +23,36 @@ import Rel8.Table.Opaleye ( binaryspec )
2323
2424
2525-- | 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@.
2727union :: EqTable a => Query a -> Query a -> Query a
2828union = zipOpaleyeWith (Opaleye.unionExplicit binaryspec)
2929
3030
3131-- | 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@.
3333unionAll :: Table Expr a => Query a -> Query a -> Query a
3434unionAll = zipOpaleyeWith (Opaleye.unionAllExplicit binaryspec)
3535
3636
3737-- | 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@.
3939intersect :: EqTable a => Query a -> Query a -> Query a
4040intersect = zipOpaleyeWith (Opaleye.intersectExplicit binaryspec)
4141
4242
4343-- | 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@.
4545intersectAll :: EqTable a => Query a -> Query a -> Query a
4646intersectAll = zipOpaleyeWith (Opaleye.intersectAllExplicit binaryspec)
4747
4848
4949-- | 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@.
5151except :: EqTable a => Query a -> Query a -> Query a
5252except = zipOpaleyeWith (Opaleye.exceptExplicit binaryspec)
5353
5454
5555-- | 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@.
5757exceptAll :: EqTable a => Query a -> Query a -> Query a
5858exceptAll = zipOpaleyeWith (Opaleye.exceptAllExplicit binaryspec)
src/Rel8/Query/These.hs+8−8
@@ -48,11 +48,11 @@ import Rel8.Type.Tag ( EitherTag( IsLeft, IsRight ) )
4848alignBy :: ()
4949 => (a -> b -> Expr Bool)
5050 -> Query a -> Query b -> Query (TheseTable Expr a b)
51−alignBy condition = zipOpaleyeWith $ \left right -> Opaleye.stateQueryArr $ \_ t -> case t of
52− tag -> (tab, join', tag''')
51+alignBy condition = zipOpaleyeWith $ \left right -> Opaleye.QueryArr $ \i -> case i of
52+ (_, tag) -> (tab, join', tag''')
5353 where
54− (ma, left', tag') = Opaleye.runStateQueryArr (pure <$> left) () tag
55− (mb, right', tag'') = Opaleye.runStateQueryArr (pure <$> right) () tag'
54+ (ma, left', tag') = Opaleye.runSimpleQueryArr (pure <$> left) ((), tag)
55+ (mb, right', tag'') = Opaleye.runSimpleQueryArr (pure <$> right) ((), tag')
5656 MaybeTable hasHere a = ma
5757 MaybeTable hasThere b = mb
5858 (hasHere', lbindings) = Opaleye.run $ do
@@ -60,15 +60,15 @@ alignBy condition = zipOpaleyeWith $ \left right -> Opaleye.stateQueryArr $ \_ t
6060 (hasThere', rbindings) = Opaleye.run $ do
6161 traversePrimExpr (Opaleye.extractAttr "hasThere" tag'') hasThere
6262 tag''' = Opaleye.next tag''
63− join = Opaleye.Join Opaleye.FullJoin on left'' right''
63+ join lateral = Opaleye.Join Opaleye.FullJoin on left'' right''
6464 where
6565 on = toPrimExpr $ condition (extract a) (extract b)
66− left'' = (Opaleye.NonLateral, Opaleye.toPrimQuery (left' <> Opaleye.aRebind lbindings))
67− right'' = (Opaleye.NonLateral, Opaleye.toPrimQuery (right' <> Opaleye.aRebind rbindings))
66+ left'' = (lateral, Opaleye.Rebind True lbindings left')
67+ right'' = (lateral, Opaleye.Rebind True rbindings right')
6868 ma' = MaybeTable hasHere' a
6969 mb' = MaybeTable hasThere' b
7070 tab = TheseTable {here = ma', there = mb'}
71− join' = Opaleye.aProduct join
71+ join' lateral input = Opaleye.times lateral input (join lateral)
7272
7373
7474keepHereTable :: 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
1818import Data.Bool ( bool )
1919import Data.Functor.Identity ( Identity( Identity ) )
2020import Data.Kind ( Constraint, Type )
21+import Data.Monoid ( getFirst )
2122import Prelude hiding ( null )
2223
2324-- opaleye
2425import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
2526
2627-- rel8
27−import Rel8.Aggregate ( Aggregate(..), zipOutputs )
28+import Rel8.Aggregate ( Aggregate( Aggregate ), foldInputs, mapInputs )
2829import Rel8.Expr ( Expr )
2930import Rel8.Expr.Bool ( boolExpr )
3031import Rel8.Expr.Null ( nullify, unsafeUnnullify )
31−import Rel8.Expr.Opaleye ( fromPrimExpr )
32+import Rel8.Expr.Opaleye ( fromPrimExpr, toPrimExpr )
3233import Rel8.Kind.Context ( SContext(..) )
3334import Rel8.Schema.Field ( Field )
3435import qualified Rel8.Schema.Kind as K
@@ -94,10 +95,17 @@ guarder :: ()
9495 -> context (Maybe a)
9596 -> context (Maybe a)
9697guarder = \case
97− SAggregate -> \tag _ isNonNull -> zipOutputs (sguard . isNonNull) tag
98− SExpr -> \tag _ isNonNull -> sguard (isNonNull tag)
99− SField -> \_ _ _ -> id
100− SName -> \_ _ _ -> id
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
106+ SExpr -> \tag _ isNonNull a -> sguard (isNonNull tag) a
107+ SField -> \_ _ _ field -> field
108+ SName -> \_ _ _ name -> name
101109 SResult -> \(Identity tag) isNonNull _ (Identity a) ->
102110 Identity (bool Nothing a (isNonNull tag))
103111
src/Rel8/Schema/HTable.hs+2−29
@@ -16,12 +16,11 @@
1616module Rel8.Schema.HTable
1717 ( HTable (HField, HConstrainTable)
1818 , hfield, htabulate, htraverse, hdicts, hspecs
19− , hfoldMap, hmap, htabulateA, htraverseP, htraversePWithField
19+ , hmap, htabulateA
2020 )
2121where
2222
2323-- base
24−import Data.Functor.Const ( Const( Const ), getConst )
2524import Data.Kind ( Constraint, Type )
2625import Data.Functor.Compose ( Compose( Compose ), getCompose )
2726import Data.Proxy ( Proxy )
@@ -33,12 +32,6 @@ import GHC.Generics
3332 )
3433import Prelude
3534
36−-- profunctors
37−import Data.Profunctor ( rmap, Profunctor (lmap) )
38−
39−-- product-profunctors
40−import Data.Profunctor.Product ( ProductProfunctor ((****)) )
41−
4235-- rel8
4336import Rel8.Schema.Dict ( Dict )
4437import Rel8.Schema.Spec ( Spec )
@@ -48,6 +41,7 @@ import qualified Rel8.Schema.Kind as K
4841-- semigroupoids
4942import Data.Functor.Apply ( Apply, (<.>) )
5043
44+
5145-- | A @HTable@ is a functor-indexed/higher-kinded data type that is
5246-- representable ('htabulate'/'hfield'), constrainable ('hdicts'), and
5347-- specified ('hspecs').
@@ -120,11 +114,6 @@ class HTable t where
120114 {-# INLINABLE hspecs #-}
121115
122116
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−
128117hmap :: HTable t
129118 => (forall a. context a -> context' a) -> t context -> t context'
130119hmap f a = htabulate $ \field -> f (hfield a field)
@@ -135,22 +124,6 @@ htabulateA :: (HTable t, Apply m)
135124htabulateA f = htraverse getCompose $ htabulate $ Compose . f
136125{-# INLINABLE htabulateA #-}
137126
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)
154127
155128type GHField :: K.HTable -> Type -> Type
156129newtype 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
2222 , hvectorize, hunvectorize
2323 , happend, hempty
2424 , hproject
25− , hcolumn
2625 )
2726where
2827
2928-- base
30−import Data.Kind ( Constraint, Type )
29+import Data.Kind ( Type )
3130import Data.List.NonEmpty ( NonEmpty )
3231import GHC.Generics (Generic)
3332import Prelude
@@ -37,11 +36,9 @@ import Rel8.FCF ( Eval, Exp )
3736import Rel8.Schema.Dict ( Dict( Dict ) )
3837import qualified Rel8.Schema.Kind as K
3938import Rel8.Schema.HTable ( HTable, hfield, htabulate, htabulateA, hspecs )
40−import Rel8.Schema.HTable.Identity ( HIdentity( HIdentity ) )
4139import Rel8.Schema.HTable.MapTable
42− ( HMapTable( HMapTable ), HMapTableField( HMapTableField )
40+ ( HMapTable, HMapTableField( HMapTableField )
4341 , MapSpec, mapInfo
44− , Precompose( Precompose )
4542 )
4643import qualified Rel8.Schema.HTable.MapTable as HMapTable ( hproject )
4744import Rel8.Schema.Null ( Unnullify, NotNull, Nullity( NotNull ) )
@@ -53,7 +50,6 @@ import Rel8.Type.Information ( TypeInformation )
5350import Data.Zip ( Unzip, Zip, Zippy(..) )
5451
5552
56−type Vector :: (Type -> Type) -> Constraint
5753class Vector list where
5854 listNotNull :: proxy a -> Dict NotNull (list a)
5955 vectorTypeInformation :: ()
@@ -137,7 +133,3 @@ hproject :: ()
137133 => (forall ctx. t ctx -> t' ctx)
138134 -> HVectorize list t context -> HVectorize list t' context
139135hproject 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 @@
22{-# language DerivingStrategies #-}
33{-# language DisambiguateRecordFields #-}
44{-# language NamedFieldPuns #-}
5−{-# language StandaloneKindSignatures #-}
65
76module Rel8.Schema.Table
87 ( TableSchema(..)
@@ -11,7 +10,6 @@ module Rel8.Schema.Table
1110where
1211
1312-- base
14−import Data.Kind ( Type )
1513import Prelude
1614
1715-- opaleye
@@ -28,7 +26,6 @@ import Text.PrettyPrint ( Doc )
2826--
2927-- For each selectable table in your database, you should provide a
3028-- @TableSchema@ in order to interact with the table via Rel8.
31−type TableSchema :: Type -> Type
3229data TableSchema names = TableSchema
3330 { name :: String
3431 -- ^ The name of the table.
src/Rel8/Statement/Delete.hs+27−20
@@ -14,11 +14,14 @@ module Rel8.Statement.Delete
1414where
1515
1616-- base
17+import Control.Exception ( throwIO )
1718import Data.Kind ( Type )
1819import Prelude
1920
2021-- hasql
22+import Hasql.Connection ( Connection )
2123import qualified Hasql.Encoders as Hasql
24+import qualified Hasql.Session as Hasql
2225import qualified Hasql.Statement as Hasql
2326
2427-- pretty
@@ -29,7 +32,10 @@ import Rel8.Expr ( Expr )
2932import Rel8.Query ( Query )
3033import Rel8.Schema.Name ( Selects )
3134import Rel8.Schema.Table ( TableSchema, ppTable )
32−import Rel8.Statement.Returning ( Returning, decodeReturning, ppReturning )
35+import Rel8.Statement.Returning
36+ ( Returning
37+ , decodeReturning, emptyReturning, ppReturning
38+ )
3339import Rel8.Statement.Using ( ppUsing )
3440import Rel8.Statement.Where ( ppWhere )
3541
@@ -55,25 +61,26 @@ data Delete a where
5561 -> Delete a
5662
5763
58−ppDelete :: Delete a -> Doc
59−ppDelete Delete {..} = case ppUsing using of
60− Nothing ->
61− text "DELETE FROM" <+> ppTable from $$
62− text "WHERE false"
63− Just (usingDoc, i) ->
64− text "DELETE FROM" <+> ppTable from $$
65− usingDoc $$
66− ppWhere from (deleteWhere i) $$
67− ppReturning from returning
64+ppDelete :: Delete a -> Maybe Doc
65+ppDelete Delete {..} = do
66+ (usingDoc, i) <- ppUsing using
67+ pure $ text "DELETE FROM" <+> ppTable from
68+ $$ usingDoc
69+ $$ ppWhere from (deleteWhere i)
70+ $$ ppReturning from returning
6871
6972
7073-- | Run a 'Delete' statement.
71−delete :: Delete a -> Hasql.Statement () a
72−delete d@Delete {returning} = Hasql.Statement bytes params decode prepare
73− where
74− bytes = encodeUtf8 $ Text.pack sql
75− params = Hasql.noParams
76− decode = decodeReturning returning
77− prepare = False
78− sql = show doc
79− doc = ppDelete d
74+delete :: Connection -> Delete a -> IO a
75+delete connection d@Delete {returning} =
76+ case show <$> ppDelete d of
77+ Nothing -> pure (emptyReturning returning)
78+ Just sql ->
79+ Hasql.run session connection >>= either throwIO pure
80+ where
81+ session = Hasql.statement () statement
82+ statement = Hasql.Statement bytes params decode prepare
83+ bytes = encodeUtf8 $ Text.pack sql
84+ params = Hasql.noParams
85+ decode = decodeReturning returning
86+ prepare = False
src/Rel8/Statement/Insert.hs+28−18
@@ -15,12 +15,15 @@ module Rel8.Statement.Insert
1515where
1616
1717-- base
18+import Control.Exception ( throwIO )
1819import Data.Foldable ( toList )
1920import Data.Kind ( Type )
2021import Prelude
2122
2223-- hasql
24+import Hasql.Connection ( Connection )
2325import qualified Hasql.Encoders as Hasql
26+import qualified Hasql.Session as Hasql
2427import qualified Hasql.Statement as Hasql
2528
2629-- opaleye
@@ -34,8 +37,11 @@ import Rel8.Query ( Query )
3437import Rel8.Schema.Name ( Name, Selects, ppColumn )
3538import Rel8.Schema.Table ( TableSchema(..), ppTable )
3639import Rel8.Statement.OnConflict ( OnConflict, ppOnConflict )
37−import Rel8.Statement.Returning ( Returning, decodeReturning, ppReturning )
38−import Rel8.Statement.Select ( ppRows )
40+import Rel8.Statement.Returning
41+ ( Returning
42+ , decodeReturning, emptyReturning, ppReturning
43+ )
44+import Rel8.Statement.Select ( ppSelect )
3945import Rel8.Table ( Table )
4046import Rel8.Table.Name ( showNames )
4147
@@ -62,13 +68,13 @@ data Insert a where
6268 -> Insert a
6369
6470
65−ppInsert :: Insert a -> Doc
66−ppInsert Insert {..} =
67− text "INSERT INTO" <+>
68− ppInto into $$
69− ppRows rows $$
70− ppOnConflict into onConflict $$
71− ppReturning into returning
71+ppInsert :: Insert a -> Maybe Doc
72+ppInsert Insert {..} = do
73+ rows' <- ppSelect rows
74+ pure $ text "INSERT INTO" <+> ppInto into
75+ $$ rows'
76+ $$ ppOnConflict into onConflict
77+ $$ ppReturning into returning
7278
7379
7480ppInto :: Table Name a => TableSchema a -> Doc
@@ -78,12 +84,16 @@ ppInto table@TableSchema {columns} =
7884
7985
8086-- | Run an 'Insert' statement.
81−insert :: Insert a -> Hasql.Statement () a
82−insert i@Insert {returning} = Hasql.Statement bytes params decode prepare
83− where
84− bytes = encodeUtf8 $ Text.pack sql
85− params = Hasql.noParams
86− decode = decodeReturning returning
87− prepare = False
88− sql = show doc
89− doc = ppInsert i
87+insert :: Connection -> Insert a -> IO a
88+insert connection i@Insert {returning} =
89+ case show <$> ppInsert i of
90+ Nothing -> pure (emptyReturning returning)
91+ Just sql ->
92+ Hasql.run session connection >>= either throwIO pure
93+ where
94+ session = Hasql.statement () statement
95+ statement = Hasql.Statement bytes params decode prepare
96+ bytes = encodeUtf8 $ Text.pack sql
97+ params = Hasql.noParams
98+ decode = decodeReturning returning
99+ prepare = False
src/Rel8/Statement/Returning.hs+7
@@ -9,7 +9,9 @@
99
1010module Rel8.Statement.Returning
1111 ( Returning( NumberOfRowsAffected, Projection )
12+
1213 , decodeReturning
14+ , emptyReturning
1315 , ppReturning
1416 )
1517where
@@ -124,6 +126,11 @@ decodeReturning = runReturning
124126 (\decoder withRows -> withRows <$> Hasql.rowList decoder)
125127
126128
129+emptyReturning :: Returning names a -> a
130+emptyReturning =
131+ runReturning (\withCount -> withCount 0) (\_ withRows -> withRows [])
132+
133+
127134ppReturning :: TableSchema names -> Returning names a -> Doc
128135ppReturning schema returning = case projections schema returning of
129136 Nothing -> mempty
src/Rel8/Statement/SQL.hs+3−3
@@ -16,14 +16,14 @@ import Rel8.Statement.Update ( Update, ppUpdate )
1616
1717-- | Convert a 'Delete' to a 'String' containing a @DELETE@ statement.
1818showDelete :: Delete a -> String
19−showDelete = show . ppDelete
19+showDelete = foldMap show . ppDelete
2020
2121
2222-- | Convert an 'Insert' to a 'String' containing an @INSERT@ statement.
2323showInsert :: Insert a -> String
24−showInsert = show . ppInsert
24+showInsert = foldMap show . ppInsert
2525
2626
2727-- | Convert an 'Update' to a 'String' containing an @UPDATE@ statement.
2828showUpdate :: Update a -> String
29−showUpdate = show . ppUpdate
29+showUpdate = foldMap show . ppUpdate
src/Rel8/Statement/Select.hs+29−57
@@ -3,7 +3,6 @@
33{-# language FlexibleContexts #-}
44{-# language MonoLocalBinds #-}
55{-# language ScopedTypeVariables #-}
6−{-# language StandaloneKindSignatures #-}
76{-# language TypeApplications #-}
87
98module Rel8.Statement.Select
@@ -12,39 +11,34 @@ module Rel8.Statement.Select
1211
1312 , Optimized(..)
1413 , ppPrimSelect
15− , ppRows
1614 )
1715where
1816
1917-- base
20−import Data.Foldable ( toList )
21−import Data.Kind ( Type )
18+import Control.Exception ( throwIO )
2219import Data.Void ( Void )
23−import Prelude hiding ( undefined )
20+import Prelude
2421
2522-- hasql
23+import Hasql.Connection ( Connection )
2624import qualified Hasql.Decoders as Hasql
2725import qualified Hasql.Encoders as Hasql
26+import qualified Hasql.Session as Hasql
2827import qualified Hasql.Statement as Hasql
2928
3029-- opaleye
31−import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
3230import qualified Opaleye.Internal.HaskellDB.Sql as Opaleye
33−import qualified Opaleye.Internal.HaskellDB.Sql.Print as Opaleye
3431import qualified Opaleye.Internal.PrimQuery as Opaleye
3532import qualified Opaleye.Internal.Print as Opaleye
3633import qualified Opaleye.Internal.Optimize as Opaleye
3734import qualified Opaleye.Internal.QueryArr as Opaleye hiding ( Select )
38−import qualified Opaleye.Internal.Sql as Opaleye hiding ( Values )
39−import qualified Opaleye.Internal.Tag as Opaleye
35+import qualified Opaleye.Internal.Sql as Opaleye
4036
4137-- pretty
4238import Text.PrettyPrint ( Doc )
4339
4440-- rel8
4541import Rel8.Expr ( Expr )
46−import Rel8.Expr.Bool ( false )
47−import Rel8.Expr.Opaleye ( toPrimExpr )
4842import Rel8.Query ( Query )
4943import Rel8.Query.Opaleye ( toOpaleye )
5044import Rel8.Schema.Name ( Selects )
@@ -52,9 +46,7 @@ import Rel8.Table ( Table )
5246import Rel8.Table.Cols ( toCols )
5347import Rel8.Table.Name ( namesFromLabels )
5448import Rel8.Table.Opaleye ( castTable, exprsWithNames )
55−import qualified Rel8.Table.Opaleye as T
5649import Rel8.Table.Serialize ( Serializable, parse )
57−import Rel8.Table.Undefined ( undefined )
5850
5951-- text
6052import qualified Data.Text as Text
@@ -63,63 +55,43 @@ import Data.Text.Encoding ( encodeUtf8 )
6355
6456-- | Run a @SELECT@ statement, returning all rows.
6557select :: forall exprs a. Serializable exprs a
66− => Query exprs -> Hasql.Statement () [a]
67−select query = Hasql.Statement bytes params decode prepare
68− where
69− bytes = encodeUtf8 (Text.pack sql)
70− params = Hasql.noParams
71− decode = Hasql.rowList (parse @exprs @a)
72− prepare = False
73− sql = show doc
74− doc = ppSelect query
75−
76−
77−ppSelect :: Table Expr a => Query a -> Doc
78−ppSelect query =
79− Opaleye.ppSql $ primSelectWith names (toCols exprs') primQuery'
58+ => Connection -> Query exprs -> IO [a]
59+select c query = case ppSelect query of
60+ Nothing -> pure []
61+ Just doc -> Hasql.run session c >>= either throwIO pure
62+ where
63+ session = Hasql.statement () statement
64+ statement = Hasql.Statement bytes params decode prepare
65+ bytes = encodeUtf8 (Text.pack sql)
66+ params = Hasql.noParams
67+ decode = Hasql.rowList (parse @exprs @a)
68+ prepare = False
69+ sql = show doc
70+
71+
72+ppSelect :: Table Expr a => Query a -> Maybe Doc
73+ppSelect query = do
74+ primQuery' <- case optimize primQuery of
75+ Empty -> Nothing
76+ Unit -> Just Opaleye.Unit
77+ Optimized primQuery' -> Just primQuery'
78+ pure $ Opaleye.ppSql $ primSelectWith names (toCols exprs) primQuery'
8079 where
8180 names = namesFromLabels
8281 (exprs, primQuery, _) =
8382 Opaleye.runSimpleQueryArrStart (toOpaleye query) ()
84− (exprs', primQuery') = case optimize primQuery of
85− Empty -> (undefined, Opaleye.Product (pure (pure Opaleye.Unit)) never)
86− Unit -> (exprs, Opaleye.Unit)
87− Optimized pq -> (exprs, pq)
88− never = pure (toPrimExpr false)
89−
90−
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'
11183
11284
11385ppPrimSelect :: Query a -> (Optimized Doc, a)
11486ppPrimSelect query =
11587 (Opaleye.ppSql . primSelect <$> optimize primQuery, a)
11688 where
117− (a, primQuery, _) = Opaleye.runSimpleQueryArrStart (toOpaleye query) ()
89+ (a, primQuery, _) =
90+ Opaleye.runSimpleQueryArrStart (toOpaleye query) ()
11891
11992
120−type Optimized :: Type -> Type
12193data Optimized a = Empty | Unit | Optimized a
122− deriving stock (Functor, Foldable, Traversable, Show)
94+ deriving stock (Functor, Foldable, Traversable)
12395
12496
12597optimize :: Opaleye.PrimQuery' a -> Optimized (Opaleye.PrimQuery' Void)
src/Rel8/Statement/Update.hs+26−18
@@ -13,11 +13,14 @@ module Rel8.Statement.Update
1313where
1414
1515-- base
16+import Control.Exception ( throwIO )
1617import Data.Kind ( Type )
1718import Prelude
1819
1920-- hasql
21+import Hasql.Connection ( Connection )
2022import qualified Hasql.Encoders as Hasql
23+import qualified Hasql.Session as Hasql
2124import qualified Hasql.Statement as Hasql
2225
2326-- pretty
@@ -28,7 +31,10 @@ import Rel8.Expr ( Expr )
2831import Rel8.Query ( Query )
2932import Rel8.Schema.Name ( Selects )
3033import Rel8.Schema.Table ( TableSchema(..), ppTable )
31−import Rel8.Statement.Returning ( Returning, decodeReturning, ppReturning )
34+import Rel8.Statement.Returning
35+ ( Returning
36+ , decodeReturning, emptyReturning, ppReturning
37+ )
3238import Rel8.Statement.Set ( ppSet )
3339import Rel8.Statement.Using ( ppFrom )
3440import Rel8.Statement.Where ( ppWhere )
@@ -57,14 +63,12 @@ data Update a where
5763 -> Update a
5864
5965
60−ppUpdate :: Update a -> Doc
61−ppUpdate Update {..} = case ppFrom from of
62− Nothing ->
63− text "UPDATE" <+> ppTable target $$
64− ppSet target id $$
65− text "WHERE false"
66− Just (fromDoc, i) ->
67− text "UPDATE" <+> ppTable target $$
66+ppUpdate :: Update a -> Maybe Doc
67+ppUpdate Update {..} = do
68+ (fromDoc, i) <- ppFrom from
69+ pure $
70+ text "UPDATE" <+>
71+ ppTable target $$
6872 ppSet target (set i) $$
6973 fromDoc $$
7074 ppWhere target (updateWhere i) $$
@@ -72,12 +76,16 @@ ppUpdate Update {..} = case ppFrom from of
7276
7377
7478-- | Run an @UPDATE@ statement.
75−update :: Update a -> Hasql.Statement () a
76−update u@Update {returning} = Hasql.Statement bytes params decode prepare
77− where
78− bytes = encodeUtf8 $ Text.pack sql
79− params = Hasql.noParams
80− decode = decodeReturning returning
81− prepare = False
82− sql = show doc
83− doc = ppUpdate u
79+update :: Connection -> Update a -> IO a
80+update connection u@Update {returning} =
81+ case show <$> ppUpdate u of
82+ Nothing -> pure (emptyReturning returning)
83+ Just sql ->
84+ Hasql.run session connection >>= either throwIO pure
85+ where
86+ session = Hasql.statement () statement
87+ statement = Hasql.Statement bytes params decode prepare
88+ bytes = encodeUtf8 $ Text.pack sql
89+ params = Hasql.noParams
90+ decode = decodeReturning returning
91+ prepare = False
src/Rel8/Statement/View.hs+17−32
@@ -3,16 +3,20 @@
33
44module Rel8.Statement.View
55 ( createView
6− , createOrReplaceView
76 )
87where
98
109-- base
10+import Control.Exception ( throwIO )
11+import Data.Foldable ( fold )
12+import Data.Maybe ( fromMaybe )
1113import Prelude
1214
1315-- hasql
16+import Hasql.Connection ( Connection )
1417import qualified Hasql.Decoders as Hasql
1518import qualified Hasql.Encoders as Hasql
19+import qualified Hasql.Session as Hasql
1620import qualified Hasql.Statement as Hasql
1721
1822-- rel8
@@ -21,6 +25,7 @@ import Rel8.Schema.Name ( Selects )
2125import Rel8.Schema.Table ( TableSchema )
2226import Rel8.Statement.Insert ( ppInto )
2327import Rel8.Statement.Select ( ppSelect )
28+import Rel8.Table.Alternative ( emptyTable )
2429
2530-- pretty
2631import Text.PrettyPrint ( Doc, (<+>), ($$), text )
@@ -30,49 +35,29 @@ import qualified Data.Text as Text
3035import Data.Text.Encoding ( encodeUtf8 )
3136
3237
33−data CreateView = Create | CreateOrReplace
34−
35−
3638-- | Given a 'TableSchema' and 'Query', @createView@ runs a @CREATE VIEW@
3739-- statement that will save the given query as a view. This can be useful if
3840-- you want to share Rel8 queries with other applications.
3941createView :: Selects names exprs
40− => TableSchema names -> Query exprs -> Hasql.Statement () ()
41−createView =
42− createViewGeneric Create
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
42+ => Connection -> TableSchema names -> Query exprs -> IO ()
43+createView connection schema query =
44+ Hasql.run session connection >>= either throwIO pure
6045 where
46+ session = Hasql.statement () statement
47+ statement = Hasql.Statement bytes params decode prepare
6148 bytes = encodeUtf8 (Text.pack sql)
6249 params = Hasql.noParams
6350 decode = Hasql.noResult
6451 prepare = False
65− sql = show doc
66− doc = ppCreateView schema query replace
52+ sql = show (ppCreateView schema query)
6753
6854
6955ppCreateView :: Selects names exprs
70− => TableSchema names -> Query exprs -> CreateView -> Doc
71−ppCreateView schema query replace =
72− createOrReplace replace <+>
56+ => TableSchema names -> Query exprs -> Doc
57+ppCreateView schema query =
58+ text "CREATE VIEW" <+>
7359 ppInto schema $$
7460 text "AS" <+>
75− ppSelect query
61+ fromMaybe fallback (ppSelect query)
7662 where
77− createOrReplace Create = text "CREATE VIEW"
78− createOrReplace CreateOrReplace = text "CREATE OR REPLACE VIEW"
63+ fallback = fold (ppSelect (emptyTable `asTypeOf` query))
src/Rel8/Table/Aggregate.hs−12
@@ -37,18 +37,6 @@ import Rel8.Type.Eq ( DBEq )
3737
3838-- | Group equal tables together. This works by aggregating each column in the
3939-- 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−-- @
5240groupBy :: forall exprs aggregates. (EqTable exprs, Aggregates aggregates exprs)
5341 => exprs -> aggregates
5442groupBy = fromColumns . hgroupBy (eqTable @exprs) . toColumns
src/Rel8/Table/HKD.hs−1
@@ -157,7 +157,6 @@ instance
157157 => HKDable a
158158
159159
160−type Top_ :: Constraint
161160class Top_
162161instance Top_
163162
src/Rel8/Table/List.hs+3−14
@@ -11,9 +11,7 @@
1111
1212module Rel8.Table.List
1313 ( ListTable(..)
14− , ($*)
15− , listTable
16− , nameListTable
14+ , listTable, nameListTable
1715 )
1816where
1917
@@ -30,7 +28,7 @@ import Rel8.Schema.HTable.List ( HListTable )
3028import Rel8.Schema.HTable.Vectorize
3129 ( hvectorize, hunvectorize
3230 , happend, hempty
33− , hproject, hcolumn
31+ , hproject
3432 )
3533import qualified Rel8.Schema.Kind as K
3634import Rel8.Schema.Name ( Name( Name ) )
@@ -48,9 +46,7 @@ import Rel8.Table.Alternative
4846 )
4947import Rel8.Table.Eq ( EqTable, eqTable )
5048import Rel8.Table.Ord ( OrdTable, ordTable )
51−import Rel8.Table.Projection
52− ( Projectable, Projecting, Projection, project, apply
53− )
49+import Rel8.Table.Projection ( Projectable, project, apply )
5450import Rel8.Table.Serialize ( ToExprs )
5551
5652
@@ -120,13 +116,6 @@ instance (context ~ Expr, Table Expr a) =>
120116 mempty = ListTable $ hempty $ \Spec {info} -> sempty info
121117
122118
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−
130119-- | Construct a @ListTable@ from a list of expressions.
131120listTable :: Table Expr a => [a] -> ListTable Expr a
132121listTable =
src/Rel8/Table/Maybe.hs−6
@@ -15,7 +15,6 @@ module Rel8.Table.Maybe
1515 ( MaybeTable(..)
1616 , maybeTable, nothingTable, justTable
1717 , isNothingTable, isJustTable
18− , fromMaybeTable
1918 , ($?)
2019 , aggregateMaybeTable
2120 , nameMaybeTable
@@ -207,11 +206,6 @@ justTable :: a -> MaybeTable Expr a
207206justTable = MaybeTable mempty . pure
208207
209208
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−
215209-- | Project a single expression out of a 'MaybeTable'. You can think of this
216210-- operator like the '$' operator, but it also has the ability to return
217211-- @null@.
src/Rel8/Table/NonEmpty.hs+3−14
@@ -11,9 +11,7 @@
1111
1212module Rel8.Table.NonEmpty
1313 ( NonEmptyTable(..)
14− , ($+)
15− , nonEmptyTable
16− , nameNonEmptyTable
14+ , nonEmptyTable, nameNonEmptyTable
1715 )
1816where
1917
@@ -31,7 +29,7 @@ import Rel8.Schema.HTable.NonEmpty ( HNonEmptyTable )
3129import Rel8.Schema.HTable.Vectorize
3230 ( hvectorize, hunvectorize
3331 , happend
34− , hproject, hcolumn
32+ , hproject
3533 )
3634import qualified Rel8.Schema.Kind as K
3735import Rel8.Schema.Name ( Name( Name ) )
@@ -46,9 +44,7 @@ import Rel8.Table
4644import Rel8.Table.Alternative ( AltTable, (<|>:) )
4745import Rel8.Table.Eq ( EqTable, eqTable )
4846import Rel8.Table.Ord ( OrdTable, ordTable )
49−import Rel8.Table.Projection
50− ( Projectable, Projecting, Projection, project, apply
51− )
47+import Rel8.Table.Projection ( Projectable, project, apply )
5248import Rel8.Table.Serialize ( ToExprs )
5349
5450
@@ -114,13 +110,6 @@ instance (Table Expr a, context ~ Expr) => Semigroup (NonEmptyTable context a)
114110 happend (const sappend1) as bs
115111
116112
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−
124113-- | Construct a @NonEmptyTable@ from a non-empty list of expressions.
125114nonEmptyTable :: Table Expr a => NonEmpty a -> NonEmptyTable Expr a
126115nonEmptyTable =
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 @@
11{-# language DataKinds #-}
2−{-# language FlexibleContexts #-}
32{-# language FlexibleInstances #-}
43{-# language LambdaCase #-}
54{-# language MultiParamTypeClasses #-}
6−{-# language NamedFieldPuns #-}
75{-# language ScopedTypeVariables #-}
86{-# language StandaloneKindSignatures #-}
97{-# language TypeApplications #-}
@@ -14,13 +12,11 @@ module Rel8.Table.Nullify
1412 ( Nullify
1513 , aggregateNullify
1614 , guard
17− , isNull
1815 )
1916where
2017
2118-- base
2219import Control.Applicative ( liftA2 )
23−import Data.Functor.Const ( Const( Const ), getConst )
2420import Data.Functor.Identity ( runIdentity )
2521import Data.Kind ( Type )
2622import Prelude
@@ -31,8 +27,6 @@ import Control.Comonad ( Comonad, duplicate, extract, ComonadApply, (<@>) )
3127-- rel8
3228import Rel8.Aggregate ( Aggregate )
3329import Rel8.Expr ( Expr )
34−import Rel8.Expr.Bool ( (||.), false )
35−import qualified Rel8.Expr.Null as Expr
3630import Rel8.Kind.Context ( Reifiable, contextSing )
3731import Rel8.Schema.Context.Nullify
3832 ( Nullifiability( NAggregate, NExpr )
@@ -51,14 +45,12 @@ import Rel8.Schema.HTable.Nullify
5145 , hproject
5246 )
5347import qualified Rel8.Schema.Kind as K
54−import Rel8.Schema.Null ( Nullity( NotNull, Null ) )
5548import qualified Rel8.Schema.Result as R
5649import Rel8.Table
5750 ( Table, Columns, Context, toColumns, fromColumns
5851 , FromExprs, fromResult, toResult
5952 , Transpose
6053 )
61−import Rel8.Schema.Spec ( Spec( Spec, nullity ) )
6254import Rel8.Table.Eq ( EqTable, eqTable )
6355import Rel8.Table.Ord ( OrdTable, ordTable )
6456import Rel8.Table.Projection ( Projectable, apply, project )
@@ -190,22 +182,3 @@ guard :: (Reifiable context, HTable t)
190182 -> HNullify t context
191183guard tag isNonNull isNonNullExpr =
192184 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 @@
77{-# language TypeFamilies #-}
88{-# language ViewPatterns #-}
99
10−{-# options_ghc -Wno-deprecations #-}
11−
1210module Rel8.Table.Opaleye
1311 ( aggregator
1412 , attributes
@@ -28,15 +26,17 @@ where
2826-- base
2927import Data.Functor.Const ( Const( Const ), getConst )
3028import Data.List.NonEmpty ( NonEmpty )
31−import Prelude
29+import Prelude hiding ( undefined )
3230
3331-- opaleye
34−import qualified Opaleye.Adaptors as Opaleye
35−import qualified Opaleye.Field as Opaleye ( Field_ )
3632import qualified Opaleye.Internal.Aggregate as Opaleye
33+import qualified Opaleye.Internal.Binary as Opaleye
34+import qualified Opaleye.Internal.Distinct as Opaleye
3735import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
36+import qualified Opaleye.Internal.PackMap as Opaleye
37+import qualified Opaleye.Internal.Unpackspec as Opaleye
3838import qualified Opaleye.Internal.Values as Opaleye
39−import qualified Opaleye.Table as Opaleye
39+import qualified Opaleye.Internal.Table as Opaleye
4040
4141-- profunctors
4242import Data.Profunctor ( dimap, lmap )
@@ -46,25 +46,27 @@ import Rel8.Aggregate ( Aggregate( Aggregate ), Aggregates )
4646import Rel8.Expr ( Expr )
4747import Rel8.Expr.Opaleye
4848 ( fromPrimExpr, toPrimExpr
49− , scastExpr, traverseFieldP
49+ , traversePrimExpr
50+ , fromColumn, toColumn
51+ , scastExpr
5052 )
51−import Rel8.Schema.HTable ( htabulateA, hfield, hspecs, htabulate,
52− htraverseP, htraversePWithField )
53+import Rel8.Schema.HTable ( htabulateA, hfield, htraverse, hspecs, htabulate )
5354import Rel8.Schema.Name ( Name( Name ), Selects, ppColumn )
5455import Rel8.Schema.Spec ( Spec(..) )
5556import Rel8.Schema.Table ( TableSchema(..), ppTable )
5657import Rel8.Table ( Table, fromColumns, toColumns )
57−import Rel8.Type.Information ( typeName )
58+import Rel8.Table.Undefined ( undefined )
5859
5960-- semigroupoids
6061import Data.Functor.Apply ( WrappedApplicative(..) )
61−import Data.Profunctor.Product ( ProductProfunctor )
6262
6363
6464aggregator :: Aggregates aggregates exprs => Opaleye.Aggregator aggregates exprs
65−aggregator = dimap toColumns fromColumns $
66− htraverseP $
67− lmap (\(Aggregate a) -> (a, ())) Opaleye.aggregatorApply
65+aggregator = Opaleye.Aggregator $ Opaleye.PackMap $ \f aggregates ->
66+ fmap fromColumns $ unwrapApplicative $ htabulateA $ \field ->
67+ WrapApplicative $ case hfield (toColumns aggregates) field of
68+ Aggregate (Opaleye.Aggregator (Opaleye.PackMap inner)) ->
69+ inner f ()
6870
6971
7072attributes :: Selects names exprs => TableSchema names -> exprs
@@ -75,19 +77,22 @@ attributes schema@TableSchema {columns} = fromColumns $ htabulate $ \field ->
7577 show (ppTable schema) <> "." <> show (ppColumn column)
7678
7779
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−
8580binaryspec :: 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)
8786
8887
8988distinctspec :: 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
9196
9297
9398exprs :: Table Expr a => a -> NonEmpty Opaleye.PrimExpr
@@ -106,8 +111,8 @@ exprsWithNames names as = getConst $ htabulateA $ \field ->
106111table :: Selects names exprs => TableSchema names -> Opaleye.Table exprs exprs
107112table (TableSchema name schema columns) =
108113 case schema of
109− Nothing -> Opaleye.table name (tableFields columns)
110− Just schemaName -> Opaleye.tableWithSchema schemaName name (tableFields columns)
114+ Nothing -> Opaleye.Table name (tableFields columns)
115+ Just schemaName -> Opaleye.TableWithSchema schemaName name (tableFields columns)
111116
112117
113118tableFields :: Selects names exprs
@@ -119,19 +124,21 @@ tableFields (toColumns -> names) = dimap toColumns fromColumns $
119124 where
120125 go :: Name a -> Opaleye.TableFields (Expr a) (Expr a)
121126 go (Name name) =
122− traverseFieldP $
127+ dimap (toColumn . toPrimExpr) (fromPrimExpr . fromColumn) $
123128 Opaleye.requiredTableField name
124129
125130
126131unpackspec :: 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
128137{-# INLINABLE unpackspec #-}
129138
130139
131−valuesspec :: Table Expr a => Opaleye.Valuesspec a a
132−valuesspec = dimap toColumns fromColumns $
133− htraversePWithField (traverseFieldP . Opaleye.valuesspecFieldType . typeName)
134− where typeName = Rel8.Type.Information.typeName . info . hfield hspecs
140+valuesspec :: Table Expr a => Opaleye.ValuesspecSafe a a
141+valuesspec = Opaleye.ValuesspecSafe (toPackMap undefined) unpackspec
135142
136143
137144view :: Selects names exprs => names -> exprs
@@ -140,6 +147,15 @@ view columns = fromColumns $ htabulate $ \field ->
140147 Name column -> fromPrimExpr $ Opaleye.BaseTableAttrExpr column
141148
142149
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+
143159-- | Transform a table by adding 'CAST' to all columns. This is most useful for
144160-- finalising a SELECT or RETURNING statement, guaranteed that the output
145161-- matches what is encoded in each columns TypeInformation.
src/Rel8/Table/Ord.hs+2−2
@@ -146,9 +146,9 @@ infix 4 >=:
146146
147147-- | Given two 'Table's, return the table that sorts before the other.
148148least :: OrdTable a => a -> a -> a
149−least a b = bool b a (a <: b)
149+least a b = bool a b (a <: b)
150150
151151
152152-- | Given two 'Table's, return the table that sorts after the other.
153153greatest :: 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
3535 => Projecting a b
3636instance
3737 ( 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)
3939 )
4040 => Projecting a b
4141
src/Rel8/Table/These.hs+3−14
@@ -20,7 +20,6 @@ module Rel8.Table.These
2020 , isThisTable, isThatTable, isThoseTable
2121 , hasHereTable, hasThereTable
2222 , justHereTable, justThereTable
23− , alignMaybeTable
2423 , aggregateTheseTable
2524 , nameTheseTable
2625 )
@@ -30,13 +29,13 @@ where
3029import Data.Bifunctor ( Bifunctor, bimap )
3130import Data.Kind ( Type )
3231import Data.Maybe ( isJust )
33−import Prelude hiding ( null, undefined )
32+import Prelude hiding ( undefined )
3433
3534-- rel8
3635import Rel8.Aggregate ( Aggregate )
3736import Rel8.Expr ( Expr )
38−import Rel8.Expr.Bool ( (&&.), (||.), boolExpr, not_ )
39−import Rel8.Expr.Null ( null, isNonNull )
37+import Rel8.Expr.Bool ( (&&.), not_ )
38+import Rel8.Expr.Null ( isNonNull )
4039import Rel8.Kind.Context ( Reifiable )
4140import Rel8.Schema.Context.Nullify ( Nullifiable )
4241import Rel8.Schema.Dict ( Dict( Dict ) )
@@ -289,16 +288,6 @@ justThereTable :: TheseTable context a b -> MaybeTable context b
289288justThereTable = there
290289
291290
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−
302291-- | Construct a @TheseTable@. Corresponds to 'This'.
303292thisTable :: Table Expr b => a -> TheseTable Expr a b
304293thisTable 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 )
5858import Data.Functor.Contravariant ( Contravariant, (>$<), contramap )
5959import Data.Int ( Int64 )
6060import Data.Kind ( Type )
61−import Data.Maybe ( fromJust, fromMaybe )
61+import Data.Maybe ( fromMaybe )
6262import Prelude hiding ( lookup, zip, zipWith )
6363
6464-- bifunctors
@@ -69,7 +69,9 @@ import Control.Comonad ( extract )
6969
7070-- opaleye
7171import 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 )
7375
7476-- profunctors
7577import Data.Profunctor ( dimap, lmap )
@@ -90,10 +92,10 @@ import Rel8.Order ( Order( Order ) )
9092import Rel8.Query ( Query )
9193import qualified Rel8.Query.Exists as Q ( exists, present, absent )
9294import Rel8.Query.Filter ( where_ )
95+import Rel8.Query.Limit ( limit )
9396import Rel8.Query.List ( catNonEmptyTable )
9497import qualified Rel8.Query.Maybe as Q ( optional )
9598import Rel8.Query.Opaleye ( mapOpaleye, unsafePeekQuery )
96−import Rel8.Query.Rebind ( rebind )
9799import Rel8.Query.These ( alignBy )
98100import Rel8.Table ( Table, fromColumns, toColumns )
99101import Rel8.Table.Aggregate ( hgroupBy, listAgg, nonEmptyAgg )
@@ -177,7 +179,7 @@ ensure (Predicate mp) = traverse_ (\k -> traverse_ (\p -> where_ (p k)) mp)
177179-- stopping a 'Tabulation' from containing multiple rows with the same key, so
178180-- technically @Map k (NonEmpty a)@ is more accurate.
179181--
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'
181183-- and converted back to 'Query's with 'lookup' and 'toQuery' (though note the
182184-- caveats that come with the latter).
183185type Tabulation :: Type -> Type -> Type
@@ -234,13 +236,7 @@ instance EqTable k => Monad (Tabulation k) where
234236-- | If @'Tabulation' k a@ is @Map k (NonEmpty a)@, then @(<|>:)@ is
235237-- @unionWith (<>)@.
236238instance 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
244240
245241
246242instance EqTable k => AlternativeTable (Tabulation k) where
@@ -349,8 +345,19 @@ aggregate (Tabulation f) = Tabulation $
349345-- \"first\" value it encounters for each key, but note that \"first\" is
350346-- undefined unless you first call 'order'.
351347distinct :: EqTable k => Tabulation k a -> Tabulation k a
352−distinct (Tabulation f) = Tabulation $
353− mapOpaleye (Opaleye.distinctOnExplicit (key unpackspec) fst) . f
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
356+ ( Opaleye.distinctOn (key unpackspec) fst
357+ . Opaleye.runSimpleQueryArr q
358+ )
359+ )
360+ (f p)
354361
355362
356363-- | 'order' orders the /values/ of a 'Tabulation' within their
@@ -509,8 +516,8 @@ alignWith :: EqTable k
509516 -> Tabulation k a -> Tabulation k b -> Tabulation k c
510517alignWith f (Tabulation as) (Tabulation bs) = Tabulation $ \p -> do
511518 tkab <- liftF2 (alignBy condition) as bs p
512− k <- traverse (rebind "key") $ recover $ bimap fst fst tkab
513519 let
520+ k = recover $ bimap fst fst tkab
514521 tab = bimap snd snd tkab
515522 pure (k, f tab)
516523 where
@@ -628,13 +635,3 @@ similarity a b = a <* present b
628635-- @do@-notation.
629636difference :: EqTable k => Tabulation k a -> Tabulation k b -> Tabulation k a
630637difference 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 #-}
21{-# language FlexibleInstances #-}
32{-# language MonoLocalBinds #-}
43{-# language MultiWayIf #-}
@@ -128,10 +127,10 @@ instance DBType Int64 where
128127instance DBType Float where
129128 typeInformation = TypeInformation
130129 { encode = \x -> Opaleye.ConstExpr
131− if | x == (1 / 0) -> Opaleye.OtherLit "'Infinity'"
132− | isNaN x -> Opaleye.OtherLit "'NaN'"
133− | x == (-1 / 0) -> Opaleye.OtherLit "'-Infinity'"
134− | otherwise -> Opaleye.NumericLit $ realToFrac x
130+ if | x == (1 /0) -> Opaleye.OtherLit "'Infinity'"
131+ | isNaN x -> Opaleye.OtherLit "'NaN'"
132+ | x == (-1/0) -> Opaleye.OtherLit "'-Infinity'"
133+ | otherwise -> Opaleye.NumericLit $ realToFrac x
135134 , decode = Hasql.float4
136135 , typeName = "float4"
137136 }
@@ -141,10 +140,10 @@ instance DBType Float where
141140instance DBType Double where
142141 typeInformation = TypeInformation
143142 { encode = \x -> Opaleye.ConstExpr
144− if | x == (1 / 0) -> Opaleye.OtherLit "'Infinity'"
145− | isNaN x -> Opaleye.OtherLit "'NaN'"
146− | x == (-1 / 0) -> Opaleye.OtherLit "'-Infinity'"
147− | otherwise -> Opaleye.NumericLit $ realToFrac x
143+ if | x == (1 /0) -> Opaleye.OtherLit "'Infinity'"
144+ | isNaN x -> Opaleye.OtherLit "'NaN'"
145+ | x == (-1/0) -> Opaleye.OtherLit "'-Infinity'"
146+ | otherwise -> Opaleye.NumericLit $ realToFrac x
148147 , decode = Hasql.float8
149148 , typeName = "float8"
150149 }
src/Rel8/Type/Enum.hs−1
@@ -106,7 +106,6 @@ class (DBType a, Enumable a) => DBEnum a where
106106
107107-- | Types that are sum types, where each constructor is unary (that is, has no
108108-- fields).
109−type Enumable :: Type -> Constraint
110109class (Generic a, GEnumable (Rep a)) => Enumable a
111110instance (Generic a, GEnumable (Rep a)) => Enumable a
112111
src/Rel8/Type/JSONBEncoded.hs−4
@@ -1,5 +1,3 @@
1−{-# language StandaloneKindSignatures #-}
2−
31module Rel8.Type.JSONBEncoded ( JSONBEncoded(..) ) where
42
53-- aeson
@@ -8,7 +6,6 @@ import Data.Aeson.Types ( parseEither )
86
97-- base
108import Data.Bifunctor ( first )
11−import Data.Kind ( Type )
129import Prelude
1310
1411-- hasql
@@ -23,7 +20,6 @@ import Data.Text ( pack )
2320
2421
2522-- | Like 'Rel8.JSONEncoded', but works for @jsonb@ columns.
26−type JSONBEncoded :: Type -> Type
2723newtype JSONBEncoded a = JSONBEncoded { fromJSONBEncoded :: a }
2824
2925
src/Rel8/Type/JSONEncoded.hs−4
@@ -1,5 +1,3 @@
1−{-# language StandaloneKindSignatures #-}
2−
31module Rel8.Type.JSONEncoded ( JSONEncoded(..) ) where
42
53-- aeson
@@ -7,7 +5,6 @@ import Data.Aeson ( FromJSON, ToJSON, parseJSON, toJSON )
75import Data.Aeson.Types ( parseEither )
86
97-- base
10−import Data.Kind ( Type )
118import Prelude
129
1310-- rel8
@@ -18,7 +15,6 @@ import Rel8.Type.Information ( parseTypeInformation )
1815-- | A deriving-via helper type for column types that store a Haskell value
1916-- using a JSON encoding described by @aeson@'s 'ToJSON' and 'FromJSON' type
2017-- classes.
21−type JSONEncoded :: Type -> Type
2218newtype JSONEncoded a = JSONEncoded { fromJSONEncoded :: a }
2319
2420
src/Rel8/Type/Num.hs+1−4
@@ -18,7 +18,6 @@ import Prelude
1818
1919-- rel8
2020import Rel8.Type ( DBType )
21−import Rel8.Type.Ord ( DBOrd )
2221
2322-- scientific
2423import Data.Scientific ( Scientific )
@@ -40,14 +39,13 @@ instance DBNum Scientific
4039-- expressions. This is a Rel8 concept, and allows us to provide
4140-- 'fromIntegral'.
4241type DBIntegral :: Type -> Constraint
43−class (DBNum a, DBOrd a) => DBIntegral a
42+class DBNum a => DBIntegral a
4443instance DBIntegral Int16
4544instance DBIntegral Int32
4645instance DBIntegral Int64
4746
4847
4948-- | The class of database types that support the @/@ operator.
50−type DBFractional :: Type -> Constraint
5149class DBNum a => DBFractional a
5250instance DBFractional Float
5351instance DBFractional Double
@@ -55,7 +53,6 @@ instance DBFractional Scientific
5553
5654
5755-- | The class of database types that support the @/@ operator.
58−type DBFloating :: Type -> Constraint
5956class DBFractional a => DBFloating a
6057instance DBFloating Float
6158instance DBFloating Double
src/Rel8/Type/Ord.hs+2
@@ -75,6 +75,7 @@ instance Sql DBOrd a => DBOrd (NonEmpty a)
7575-- | The class of database types that support the @max@ aggregation function.
7676type DBMax :: Type -> Constraint
7777class DBOrd a => DBMax a
78+instance DBMax Bool
7879instance DBMax Char
7980instance DBMax Int16
8081instance DBMax Int32
@@ -100,6 +101,7 @@ instance Sql DBMax a => DBMax (NonEmpty a)
100101-- | The class of database types that support the @min@ aggregation function.
101102type DBMin :: Type -> Constraint
102103class DBOrd a => DBMin a
104+instance DBMin Bool
103105instance DBMin Char
104106instance DBMin Int16
105107instance DBMin Int32
src/Rel8/Type/ReadShow.hs−3
@@ -1,12 +1,10 @@
11{-# language ScopedTypeVariables #-}
2−{-# language StandaloneKindSignatures #-}
32{-# language TypeApplications #-}
43{-# language ViewPatterns #-}
54
65module Rel8.Type.ReadShow ( ReadShow(..) ) where
76
87-- base
9−import Data.Kind ( Type )
108import Data.Proxy ( Proxy( Proxy ) )
119import Data.Typeable ( Typeable, typeRep )
1210import Prelude
@@ -22,7 +20,6 @@ import qualified Data.Text as Text
2220
2321-- | A deriving-via helper type for column types that store a Haskell value
2422-- using a Haskell's 'Read' and 'Show' type classes.
25−type ReadShow :: Type -> Type
2623newtype ReadShow a = ReadShow { fromReadShow :: a }
2724
2825
src/Rel8/Type/Tag.hs−1
@@ -90,7 +90,6 @@ instance DBMonoid MaybeTag where
9090 memptyExpr = litExpr mempty
9191
9292
93−type Tag :: Type
9493newtype Tag = Tag Text
9594 deriving newtype
9695 ( 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+164−273
@@ -1,11 +1,12 @@
1−{-# language BangPatterns #-}
21{-# language BlockArguments #-}
2+{-# language DataKinds #-}
33{-# language DeriveAnyClass #-}
44{-# language DeriveGeneric #-}
55{-# language DerivingStrategies #-}
6+{-# language DisambiguateRecordFields #-}
67{-# language FlexibleContexts #-}
78{-# language FlexibleInstances #-}
8−{-# language MonoLocalBinds #-}
9+{-# language GADTs #-}
910{-# language NamedFieldPuns #-}
1011{-# language OverloadedStrings #-}
1112{-# language RecordWildCards #-}
@@ -13,15 +14,17 @@
1314{-# language StandaloneDeriving #-}
1415{-# language TypeApplications #-}
1516
17+{-# options_ghc -fno-warn-redundant-constraints #-}
18+
1619module Main
1720 ( main
1821 )
1922where
2023
2124-- base
22−import Control.Applicative ( empty, liftA2, liftA3 )
23−import Control.Exception ( bracket, throwIO )
24−import Control.Monad ( (>=>), void )
25+import Control.Applicative ( liftA2, liftA3 )
26+import Control.Monad (void)
27+import Control.Monad.IO.Class ( MonadIO, liftIO )
2528import Data.Bifunctor ( bimap )
2629import Data.Foldable ( for_ )
2730import Data.Int ( Int32, Int64 )
@@ -35,27 +38,25 @@ import GHC.Generics ( Generic )
3538import qualified Data.ByteString.Lazy
3639
3740-- case-insensitive
38−import Data.CaseInsensitive ( mk )
41+import Data.CaseInsensitive (mk)
3942
4043-- containers
41−import Data.Containers.ListUtils ( nubOrdOn )
4244import qualified Data.Map.Strict as Map
4345
4446-- hasql
45−import Hasql.Connection ( Connection, acquire, release )
47+import Hasql.Connection ( Connection, acquire, release )
4648import Hasql.Session ( sql, run )
4749
48−-- hasql-transaction
49−import Hasql.Transaction ( Transaction, condemn, statement )
50−import qualified Hasql.Transaction.Sessions as Hasql
51−
5250-- hedgehog
5351import Hedgehog ( property, (===), forAll, cover, diff, evalM, PropertyT, TestT, test, Gen )
5452import qualified Hedgehog.Gen as Gen
5553import qualified Hedgehog.Range as Range
5654
57−-- mmorph
58−import Control.Monad.Morph ( hoist )
55+-- lifted-base
56+import Control.Exception.Lifted ( bracket, throwIO, bracket_ )
57+
58+-- monad-control
59+import Control.Monad.Trans.Control ( MonadBaseControl )
5960
6061-- rel8
6162import Rel8 ( Result )
@@ -71,16 +72,12 @@ import Test.Tasty
7172import Test.Tasty.Hedgehog ( testProperty )
7273
7374-- text
74−import Data.Text ( Text, pack, unpack )
75+import Data.Text (Text, pack)
7576import qualified Data.Text.Lazy
76−import Data.Text.Encoding ( decodeUtf8 )
7777
7878-- time
7979import Data.Time
8080
81−-- transformers
82−import Control.Monad.Trans.Class ( lift )
83−
8481-- tmp-postgres
8582import qualified Database.Postgres.Temp as TmpPostgres
8683
@@ -116,13 +113,11 @@ tests =
116113 , testCatMaybeTable getTestDatabase
117114 , testCatMaybe getTestDatabase
118115 , testMaybeTable getTestDatabase
119− , testAggregateMaybeTable getTestDatabase
120116 , testNestedTables getTestDatabase
121117 , testMaybeTableApplicative getTestDatabase
122118 , testLogicalFixities getTestDatabase
123119 , testUpdate getTestDatabase
124120 , testDelete getTestDatabase
125− , testUpsert getTestDatabase
126121 , testSelectNestedPairs getTestDatabase
127122 , testSelectArray getTestDatabase
128123 , testNestedMaybeTable getTestDatabase
@@ -138,7 +133,6 @@ tests =
138133 flip run conn do
139134 sql "CREATE EXTENSION citext"
140135 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 )"
142136 sql "CREATE SEQUENCE test_seq"
143137
144138 return db
@@ -146,21 +140,19 @@ tests =
146140 stopTestDatabase = TmpPostgres.stop
147141
148142
149−connect :: TmpPostgres.DB -> IO Connection
150−connect = acquire . TmpPostgres.toConnectionString >=> either (maybe empty (fail . unpack . decodeUtf8)) pure
151−
152−
153143databasePropertyTest
154144 :: TestName
155− -> ((TestT Transaction () -> PropertyT IO ()) -> PropertyT IO ())
145+ -> (((Connection -> TestT IO ()) -> PropertyT IO ()) -> PropertyT IO ())
156146 -> IO TmpPostgres.DB -> TestTree
157147databasePropertyTest testName f getTestDatabase =
158− withResource (connect =<< getTestDatabase) release $ \c ->
148+ withResource connect release $ \c ->
159149 testProperty testName $ property do
160− connection <- lift c
161− f $ test . hoist \m -> do
162− e <- run (Hasql.transaction Hasql.Serializable Hasql.Write (m <* condemn)) connection
163− either throwIO pure e
150+ connection <- liftIO c
151+ f \g -> test $ rollingBack connection $ g connection
152+
153+ where
154+
155+ connect = either (error . show) return =<< acquire . TmpPostgres.toConnectionString =<< getTestDatabase
164156
165157
166158data TestTable f = TestTable
@@ -192,17 +184,17 @@ testSelectTestTable :: IO TmpPostgres.DB -> TestTree
192184testSelectTestTable = databasePropertyTest "Can SELECT TestTable" \transaction -> do
193185 rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable
194186
195− transaction do
196− selected <- lift do
197− statement () $ Rel8.insert Rel8.Insert
187+ transaction \connection -> do
188+ liftIO $ Rel8.insert connection
189+ Rel8.Insert
198190 { into = testTableSchema
199191 , rows = Rel8.values $ map Rel8.lit rows
200192 , onConflict = Rel8.DoNothing
201193 , returning = pure ()
202194 }
203195
204− statement () $ Rel8.select do
205− Rel8.each testTableSchema
196+ selected <- liftIO $ Rel8.select connection do
197+ Rel8.each testTableSchema
206198
207199 sort selected === sort rows
208200
@@ -219,12 +211,11 @@ testWhere_ = databasePropertyTest "WHERE (Rel8.where_)" \transaction -> do
219211
220212 let expected = filter (\t -> testTableColumn2 t == magicBool) rows
221213
222− transaction do
223− selected <- lift do
224− statement () $ Rel8.select do
225− t <- Rel8.values $ Rel8.lit <$> rows
226− Rel8.where_ $ testTableColumn2 t Rel8.==. Rel8.lit magicBool
227− return t
214+ transaction \connection -> do
215+ selected <- liftIO $ Rel8.select connection do
216+ t <- Rel8.values $ Rel8.lit <$> rows
217+ Rel8.where_ $ testTableColumn2 t Rel8.==. Rel8.lit magicBool
218+ return t
228219
229220 sort selected === sort expected
230221
@@ -237,12 +228,11 @@ testFilter :: IO TmpPostgres.DB -> TestTree
237228testFilter = databasePropertyTest "filter" \transaction -> do
238229 rows <- forAll $ Gen.list (Range.linear 1 10) genTestTable
239230
240− transaction do
231+ transaction \connection -> do
241232 let expected = filter testTableColumn2 rows
242233
243− selected <- lift do
244− statement () $ Rel8.select do
245− Rel8.filter testTableColumn2 =<< Rel8.values (Rel8.lit <$> rows)
234+ selected <- liftIO $ Rel8.select connection
235+ $ Rel8.filter testTableColumn2 =<< Rel8.values (Rel8.lit <$> rows)
246236
247237 sort selected === sort expected
248238
@@ -257,10 +247,9 @@ testLimit = databasePropertyTest "LIMIT (Rel8.limit)" \transaction -> do
257247
258248 n <- forAll $ Gen.integral (Range.linear 0 10)
259249
260− transaction do
261− selected <- lift do
262− statement () $ Rel8.select do
263− Rel8.limit n $ Rel8.values (Rel8.lit <$> rows)
250+ transaction \connection -> do
251+ selected <- liftIO $ Rel8.select connection do
252+ Rel8.limit n $ Rel8.values (Rel8.lit <$> rows)
264253
265254 diff (length selected) (<=) (fromIntegral n)
266255
@@ -278,10 +267,9 @@ testUnion = databasePropertyTest "UNION (Rel8.union)" \transaction -> evalM do
278267 left <- forAll $ Gen.list (Range.linear 0 10) genTestTable
279268 right <- forAll $ Gen.list (Range.linear 0 10) genTestTable
280269
281− transaction do
282− selected <- lift do
283− statement () $ Rel8.select do
284− Rel8.values (Rel8.lit <$> nub left) `Rel8.union` Rel8.values (Rel8.lit <$> nub right)
270+ transaction \connection -> do
271+ selected <- liftIO $ Rel8.select connection do
272+ Rel8.values (Rel8.lit <$> nub left) `Rel8.union` Rel8.values (Rel8.lit <$> nub right)
285273
286274 sort selected === sort (nub (left ++ right))
287275
@@ -290,11 +278,9 @@ testDistinct :: IO TmpPostgres.DB -> TestTree
290278testDistinct = databasePropertyTest "DISTINCT (Rel8.distinct)" \transaction -> do
291279 rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable
292280
293− transaction do
294− selected <- lift do
295− statement () $ Rel8.select do
296− Rel8.distinct do
297− Rel8.values (Rel8.lit <$> rows)
281+ transaction \connection -> do
282+ selected <- liftIO $ Rel8.select connection $ Rel8.distinct do
283+ Rel8.values (Rel8.lit <$> rows)
298284
299285 sort selected === nub (sort rows)
300286
@@ -307,10 +293,8 @@ testExists :: IO TmpPostgres.DB -> TestTree
307293testExists = databasePropertyTest "EXISTS (Rel8.exists)" \transaction -> do
308294 rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable
309295
310− transaction do
311− exists <- lift do
312− statement () $ Rel8.select do
313− Rel8.exists $ Rel8.values $ Rel8.lit <$> rows
296+ transaction \connection -> do
297+ exists <- liftIO $ Rel8.select connection $ Rel8.exists $ Rel8.values $ Rel8.lit <$> rows
314298
315299 case rows of
316300 [] -> exists === [False]
@@ -321,10 +305,9 @@ testOptional :: IO TmpPostgres.DB -> TestTree
321305testOptional = databasePropertyTest "Rel8.optional" \transaction -> do
322306 rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable
323307
324− transaction do
325− selected <- lift do
326− statement () $ Rel8.select do
327− Rel8.optional $ Rel8.values (Rel8.lit <$> rows)
308+ transaction \connection -> do
309+ selected <- liftIO $ Rel8.select connection do
310+ Rel8.optional $ Rel8.values (Rel8.lit <$> rows)
328311
329312 case rows of
330313 [] -> selected === [Nothing]
@@ -335,10 +318,9 @@ testAnd :: IO TmpPostgres.DB -> TestTree
335318testAnd = databasePropertyTest "AND (&&.)" \transaction -> do
336319 (x, y) <- forAll $ liftA2 (,) Gen.bool Gen.bool
337320
338− transaction do
339− [result] <- lift do
340− statement () $ Rel8.select do
341− pure $ Rel8.lit x Rel8.&&. Rel8.lit y
321+ transaction \connection -> do
322+ [result] <- liftIO $ Rel8.select connection $ pure $
323+ Rel8.lit x Rel8.&&. Rel8.lit y
342324
343325 result === (x && y)
344326
@@ -347,10 +329,9 @@ testOr :: IO TmpPostgres.DB -> TestTree
347329testOr = databasePropertyTest "OR (||.)" \transaction -> do
348330 (x, y) <- forAll $ liftA2 (,) Gen.bool Gen.bool
349331
350− transaction do
351− [result] <- lift do
352− statement () $ Rel8.select $ pure $
353− Rel8.lit x Rel8.||. Rel8.lit y
332+ transaction \connection -> do
333+ [result] <- liftIO $ Rel8.select connection $ pure $
334+ Rel8.lit x Rel8.||. Rel8.lit y
354335
355336 result === (x || y)
356337
@@ -359,10 +340,9 @@ testLogicalFixities :: IO TmpPostgres.DB -> TestTree
359340testLogicalFixities = databasePropertyTest "Logical operator fixities" \transaction -> do
360341 (u, v, w, x) <- forAll $ (,,,) <$> Gen.bool <*> Gen.bool <*> Gen.bool <*> Gen.bool
361342
362− transaction do
363− [result] <- lift do
364− statement () $ Rel8.select do
365− pure $ Rel8.lit u Rel8.||. Rel8.lit v Rel8.&&. Rel8.lit w Rel8.==. Rel8.lit x
343+ transaction \connection -> do
344+ [result] <- liftIO $ Rel8.select connection $ pure $
345+ Rel8.lit u Rel8.||. Rel8.lit v Rel8.&&. Rel8.lit w Rel8.==. Rel8.lit x
366346
367347 result === (u || v && w == x)
368348
@@ -371,10 +351,9 @@ testNot :: IO TmpPostgres.DB -> TestTree
371351testNot = databasePropertyTest "NOT (not_)" \transaction -> do
372352 x <- forAll Gen.bool
373353
374− transaction do
375− [result] <- lift do
376− statement () $ Rel8.select do
377− pure $ Rel8.not_ $ Rel8.lit x
354+ transaction \connection -> do
355+ [result] <- liftIO $ Rel8.select connection $ pure $
356+ Rel8.not_ $ Rel8.lit x
378357
379358 result === not x
380359
@@ -383,10 +362,9 @@ testBool :: IO TmpPostgres.DB -> TestTree
383362testBool = databasePropertyTest "ifThenElse_" \transaction -> do
384363 (x, y, z) <- forAll $ liftA3 (,,) Gen.bool Gen.bool Gen.bool
385364
386− transaction do
387− [result] <- lift do
388− statement () $ Rel8.select do
389− pure $ Rel8.bool (Rel8.lit z) (Rel8.lit y) (Rel8.lit x)
365+ transaction \connection -> do
366+ [result] <- liftIO $ Rel8.select connection $ pure $
367+ Rel8.bool (Rel8.lit z) (Rel8.lit y) (Rel8.lit x)
390368
391369 result === if x then y else z
392370
@@ -398,10 +376,9 @@ testAp = databasePropertyTest "Cartesian product (<*>)" \transaction -> do
398376 (Gen.list (Range.linear 1 10) genTestTable)
399377 (Gen.list (Range.linear 1 10) genTestTable)
400378
401− transaction do
402− result <- lift do
403− statement () $ Rel8.select do
404− liftA2 (,) (Rel8.values (Rel8.lit <$> rows1)) (Rel8.values (Rel8.lit <$> rows2))
379+ transaction \connection -> do
380+ result <- liftIO $ Rel8.select connection $ do
381+ liftA2 (,) (Rel8.values (Rel8.lit <$> rows1)) (Rel8.values (Rel8.lit <$> rows2))
405382
406383 sort result === sort (liftA2 (,) rows1 rows2)
407384
@@ -430,22 +407,21 @@ testDBType getTestDatabase = testGroup "DBType instances"
430407 where
431408 dbTypeTest :: (Eq a, Show a, Rel8.DBType a) => TestName -> Gen a -> TestTree
432409 dbTypeTest name generator = testGroup name
433− [ databasePropertyTest name (t generator) getTestDatabase
434− , databasePropertyTest ("Maybe " <> name) (t (Gen.maybe generator)) getTestDatabase
410+ [ databasePropertyTest name (t (==) generator) getTestDatabase
411+ , databasePropertyTest ("Maybe " <> name) (t (==) (Gen.maybe generator)) getTestDatabase
435412 ]
436413
437− t :: forall a b. (Eq a, Show a, Rel8.Sql Rel8.DBType a)
438− => Gen a
439− -> (TestT Transaction () -> PropertyT IO b)
414+ t :: forall a b. (Show a, Rel8.Sql Rel8.DBType a)
415+ => (a -> a -> Bool)
416+ -> Gen a
417+ -> ((Connection -> TestT IO ()) -> PropertyT IO b)
440418 -> PropertyT IO b
441− t generator transaction = do
419+ t eq generator transaction = do
442420 x <- forAll generator
443421
444− transaction do
445− [res] <- lift do
446− statement () $ Rel8.select do
447− pure (Rel8.litExpr x)
448− diff res (==) x
422+ transaction \connection -> do
423+ [res] <- liftIO $ Rel8.select connection $ pure (Rel8.litExpr x)
424+ diff res eq x
449425
450426 genDay :: Gen Day
451427 genDay = do
@@ -487,15 +463,13 @@ testDBEq getTestDatabase = testGroup "DBEq instances"
487463
488464 t :: forall a. (Eq a, Show a, Rel8.Sql Rel8.DBEq a)
489465 => Gen a
490− -> (TestT Transaction () -> PropertyT IO ())
466+ -> ((Connection -> TestT IO ()) -> PropertyT IO ())
491467 -> PropertyT IO ()
492468 t generator transaction = do
493469 (x, y) <- forAll (liftA2 (,) generator generator)
494470
495− transaction do
496− [res] <- lift do
497− statement () $ Rel8.select do
498− pure $ Rel8.litExpr x Rel8.==. Rel8.litExpr y
471+ transaction \connection -> do
472+ [res] <- liftIO $ Rel8.select connection $ pure $ Rel8.litExpr x Rel8.==. Rel8.litExpr y
499473 res === (x == y)
500474
501475
@@ -503,10 +477,9 @@ testTableEquality :: IO TmpPostgres.DB -> TestTree
503477testTableEquality = databasePropertyTest "TestTable equality" \transaction -> do
504478 (x, y) <- forAll $ liftA2 (,) genTestTable genTestTable
505479
506− transaction do
507− [eq] <- lift do
508− statement () $ Rel8.select do
509− pure $ Rel8.lit x Rel8.==: Rel8.lit y
480+ transaction \connection -> do
481+ [eq] <- liftIO $ Rel8.select connection do
482+ pure $ Rel8.lit x Rel8.==: Rel8.lit y
510483
511484 eq === (x == y)
512485
@@ -515,10 +488,8 @@ testFromString :: IO TmpPostgres.DB -> TestTree
515488testFromString = databasePropertyTest "FromString" \transaction -> do
516489 str <- forAll $ Gen.list (Range.linear 0 10) Gen.unicode
517490
518− transaction do
519− [result] <- lift do
520− statement () $ Rel8.select do
521− pure $ fromString str
491+ transaction \connection -> do
492+ [result] <- liftIO $ Rel8.select connection $ pure $ fromString str
522493 result === pack str
523494
524495
@@ -526,11 +497,10 @@ testCatMaybeTable :: IO TmpPostgres.DB -> TestTree
526497testCatMaybeTable = databasePropertyTest "catMaybeTable" \transaction -> do
527498 rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable
528499
529− transaction do
530− selected <- lift do
531− statement () $ Rel8.select do
532− testTable <- Rel8.values $ Rel8.lit <$> rows
533− Rel8.catMaybeTable $ Rel8.bool Rel8.nothingTable (pure testTable) (testTableColumn2 testTable)
500+ transaction \connection -> do
501+ selected <- liftIO $ Rel8.select connection do
502+ testTable <- Rel8.values $ Rel8.lit <$> rows
503+ Rel8.catMaybeTable $ Rel8.bool Rel8.nothingTable (pure testTable) (testTableColumn2 testTable)
534504
535505 sort selected === sort (filter testTableColumn2 rows)
536506
@@ -539,10 +509,9 @@ testCatMaybe :: IO TmpPostgres.DB -> TestTree
539509testCatMaybe = databasePropertyTest "catMaybe" \transaction -> evalM do
540510 rows <- forAll $ Gen.list (Range.linear 0 10) $ Gen.maybe Gen.bool
541511
542− transaction do
543− selected <- lift do
544− statement () $ Rel8.select do
545− Rel8.catNull =<< Rel8.values (map Rel8.lit rows)
512+ transaction \connection -> do
513+ selected <- evalM $ liftIO $ Rel8.select connection do
514+ Rel8.catNull =<< Rel8.values (map Rel8.lit rows)
546515
547516 sort selected === sort (catMaybes rows)
548517
@@ -551,38 +520,15 @@ testMaybeTable :: IO TmpPostgres.DB -> TestTree
551520testMaybeTable = databasePropertyTest "maybeTable" \transaction -> evalM do
552521 (rows, def) <- forAll $ liftA2 (,) (Gen.list (Range.linear 0 10) genTestTable) genTestTable
553522
554− transaction do
555− selected <- lift do
556− statement () $ Rel8.select do
557− Rel8.maybeTable (Rel8.lit def) id <$> Rel8.optional (Rel8.values (Rel8.lit <$> rows))
523+ transaction \connection -> do
524+ selected <- liftIO $ Rel8.select connection $
525+ Rel8.maybeTable (Rel8.lit def) id <$> Rel8.optional (Rel8.values (Rel8.lit <$> rows))
558526
559527 case rows of
560528 [] -> selected === [def]
561529 _ -> sort selected === sort rows
562530
563531
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−
586532data TwoTestTables f =
587533 TwoTestTables
588534 { testTable1 :: TestTable f
@@ -603,10 +549,9 @@ testNestedTables = databasePropertyTest "Nested TestTables" \transaction -> eval
603549 Gen.list (Range.linear 0 10) $
604550 liftA2 TwoTestTables genTestTable genTestTable
605551
606− transaction do
607− selected <- lift do
608− statement () $ Rel8.select do
609− Rel8.values (Rel8.lit <$> rows)
552+ transaction \connection -> do
553+ selected <- liftIO $ Rel8.select connection do
554+ Rel8.values (Rel8.lit <$> rows)
610555
611556 sort selected === sort rows
612557
@@ -616,12 +561,11 @@ testMaybeTableApplicative = databasePropertyTest "MaybeTable (<*>)" \transaction
616561 rows1 <- genRows
617562 rows2 <- genRows
618563
619− transaction do
620− selected <- lift do
621− statement () $ Rel8.select do
622− as <- Rel8.optional (Rel8.values (Rel8.lit <$> rows1))
623− bs <- Rel8.optional (Rel8.values (Rel8.lit <$> rows2))
624− pure $ liftA2 (,) as bs
564+ transaction \connection -> do
565+ selected <- liftIO $ Rel8.select connection do
566+ as <- Rel8.optional (Rel8.values (Rel8.lit <$> rows1))
567+ bs <- Rel8.optional (Rel8.values (Rel8.lit <$> rows2))
568+ pure $ liftA2 (,) as bs
625569
626570 case (rows1, rows2) of
627571 ([], []) -> selected === [Nothing]
@@ -633,6 +577,14 @@ testMaybeTableApplicative = databasePropertyTest "MaybeTable (<*>)" \transaction
633577 genRows = forAll do
634578 Gen.list (Range.linear 0 10) $ liftA2 TestTable (Gen.text (Range.linear 0 10) Gen.unicode) (pure True)
635579
580+rollingBack
581+ :: (MonadBaseControl IO m, MonadIO m)
582+ => Connection -> m a -> m a
583+rollingBack connection =
584+ bracket_
585+ (liftIO (run (sql "BEGIN") connection))
586+ (liftIO (run (sql "ROLLBACK") connection))
587+
636588
637589genTestTable :: Gen (TestTable Result)
638590genTestTable = do
@@ -645,16 +597,17 @@ testUpdate :: IO TmpPostgres.DB -> TestTree
645597testUpdate = databasePropertyTest "Can UPDATE TestTable" \transaction -> do
646598 rows <- forAll $ Gen.map (Range.linear 0 5) $ liftA2 (,) genTestTable genTestTable
647599
648− transaction do
649− selected <- lift do
650− statement () $ Rel8.insert Rel8.Insert
600+ transaction \connection -> do
601+ liftIO $ Rel8.insert connection
602+ Rel8.Insert
651603 { into = testTableSchema
652604 , rows = Rel8.values $ map Rel8.lit $ Map.keys rows
653605 , onConflict = Rel8.DoNothing
654606 , returning = pure ()
655607 }
656608
657− statement () $ Rel8.update Rel8.Update
609+ liftIO $ Rel8.update connection
610+ Rel8.Update
658611 { target = testTableSchema
659612 , from = pure ()
660613 , set = \_ r ->
@@ -675,8 +628,8 @@ testUpdate = databasePropertyTest "Can UPDATE TestTable" \transaction -> do
675628 , returning = pure ()
676629 }
677630
678− statement () $ Rel8.select do
679− Rel8.each testTableSchema
631+ selected <- liftIO $ Rel8.select connection do
632+ Rel8.each testTableSchema
680633
681634 sort selected === sort (Map.elems rows)
682635
@@ -689,96 +642,30 @@ testDelete :: IO TmpPostgres.DB -> TestTree
689642testDelete = databasePropertyTest "Can DELETE TestTable" \transaction -> do
690643 rows <- forAll $ Gen.list (Range.linear 0 5) genTestTable
691644
692− transaction do
693− (deleted, selected) <- lift do
694− statement () $ Rel8.insert Rel8.Insert
645+ transaction \connection -> do
646+ liftIO $ Rel8.insert connection
647+ Rel8.Insert
695648 { into = testTableSchema
696649 , rows = Rel8.values $ map Rel8.lit rows
697650 , onConflict = Rel8.DoNothing
698651 , returning = pure ()
699652 }
700653
701− deleted <- statement () $ Rel8.delete Rel8.Delete
654+ deleted <-
655+ liftIO $ Rel8.delete connection
656+ Rel8.Delete
702657 { from = testTableSchema
703658 , using = pure ()
704659 , deleteWhere = const testTableColumn2
705660 , returning = Rel8.Projection id
706661 }
707662
708− selected <- statement () $ Rel8.select do
709− Rel8.each testTableSchema
710−
711− pure (deleted, selected)
663+ selected <- liftIO $ Rel8.select connection do
664+ Rel8.each testTableSchema
712665
713666 sort (deleted <> selected) === sort rows
714667
715668
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−
782669newtype HKNestedPair f = HKNestedPair { pairOne :: (TestTable f, TestTable f) }
783670 deriving stock Generic
784671 deriving anyclass Rel8.Rel8able
@@ -792,10 +679,9 @@ testSelectNestedPairs :: IO TmpPostgres.DB -> TestTree
792679testSelectNestedPairs = databasePropertyTest "Can SELECT nested pairs" \transaction -> do
793680 rows <- forAll $ Gen.list (Range.linear 0 10) $ HKNestedPair <$> liftA2 (,) genTestTable genTestTable
794681
795− transaction do
796− selected <- lift do
797− statement () $ Rel8.select do
798− Rel8.values $ map Rel8.lit rows
682+ transaction \connection -> do
683+ selected <- liftIO $ Rel8.select connection do
684+ Rel8.values $ map Rel8.lit rows
799685
800686 sort selected === sort rows
801687
@@ -804,20 +690,18 @@ testSelectArray :: IO TmpPostgres.DB -> TestTree
804690testSelectArray = databasePropertyTest "Can SELECT Arrays (with aggregation)" \transaction -> do
805691 rows <- forAll $ Gen.list (Range.linear 1 10) Gen.bool
806692
807− transaction do
808− selected <- lift do
809− statement () $ Rel8.select do
810− Rel8.many $ Rel8.values (map Rel8.lit rows)
693+ transaction \connection -> do
694+ selected <- liftIO $ Rel8.select connection do
695+ Rel8.many $ Rel8.values (map Rel8.lit rows)
811696
812697 selected === [foldMap pure rows]
813698
814− selected' <- lift do
815− statement () $ Rel8.select do
816− a <- Rel8.catListTable =<< do
817− Rel8.many $ Rel8.values (map Rel8.lit rows)
818− b <- Rel8.catListTable =<< do
819− Rel8.many $ Rel8.values (map Rel8.lit rows)
820− pure (a, b)
699+ selected' <- liftIO $ Rel8.select connection $ do
700+ a <- Rel8.catListTable =<< do
701+ Rel8.many $ Rel8.values (map Rel8.lit rows)
702+ b <- Rel8.catListTable =<< do
703+ Rel8.many $ Rel8.values (map Rel8.lit rows)
704+ pure (a, b)
821705
822706 selected' === liftA2 (,) rows rows
823707
@@ -839,11 +723,10 @@ testNestedMaybeTable :: IO TmpPostgres.DB -> TestTree
839723testNestedMaybeTable = databasePropertyTest "Can nest MaybeTable within other tables" \transaction -> do
840724 let example = NestedMaybeTable { nmt1 = True, nmt2 = Just (TestTable "Hi" True) }
841725
842− transaction do
843− selected <- lift do
844− statement () $ Rel8.select do
845− x <- Rel8.values [Rel8.lit example]
846− pure $ Rel8.maybeTable (Rel8.lit False) (\_ -> Rel8.lit True) (nmt2 x)
726+ transaction \connection -> do
727+ selected <- liftIO $ Rel8.select connection do
728+ x <- Rel8.values [Rel8.lit example]
729+ pure $ Rel8.maybeTable (Rel8.lit False) (\_ -> Rel8.lit True) (nmt2 x)
847730
848731 selected === [True]
849732
@@ -851,12 +734,11 @@ testNestedMaybeTable = databasePropertyTest "Can nest MaybeTable within other ta
851734testEvaluate :: IO TmpPostgres.DB -> TestTree
852735testEvaluate = databasePropertyTest "evaluate has the evaluation order we expect" \transaction -> do
853736
854− transaction do
855− selected <- lift do
856− statement () $ Rel8.select do
857− x <- Rel8.values (Rel8.lit <$> ['a', 'b', 'c'])
858− y <- Rel8.evaluate (Rel8.nextval "test_seq")
859− pure (x, (y, y))
737+ transaction \connection -> do
738+ selected <- liftIO $ Rel8.select connection do
739+ x <- Rel8.values (Rel8.lit <$> ['a', 'b', 'c'])
740+ y <- Rel8.evaluate (Rel8.nextval "test_seq")
741+ pure (x, (y, y))
860742
861743 normalize selected ===
862744 [ ('a', (0, 0))
@@ -864,12 +746,11 @@ testEvaluate = databasePropertyTest "evaluate has the evaluation order we expect
864746 , ('c', (2, 2))
865747 ]
866748
867− selected' <- lift do
868− statement () $ Rel8.select do
869− x <- Rel8.values (Rel8.lit <$> ['a', 'b', 'c'])
870− y <- Rel8.values (Rel8.lit <$> ['d', 'e', 'f'])
871− z <- Rel8.evaluate (Rel8.nextval "test_seq")
872− pure ((x, y), (z, z))
749+ selected' <- liftIO $ Rel8.select connection do
750+ x <- Rel8.values (Rel8.lit <$> ['a', 'b', 'c'])
751+ y <- Rel8.values (Rel8.lit <$> ['d', 'e', 'f'])
752+ z <- Rel8.evaluate (Rel8.nextval "test_seq")
753+ pure ((x, y), (z, z))
873754
874755 normalize selected' ===
875756 [ (('a', 'd'), (0, 0))
@@ -887,3 +768,13 @@ testEvaluate = databasePropertyTest "evaluate has the evaluation order we expect
887768 normalize :: [(x, (Int64, Int64))] -> [(x, (Int64, Int64))]
888769 normalize [] = []
889770 normalize xs@((_, (i, _)) : _) = map (fmap (\(a, b) -> (a - i, b - i))) xs
771+
772+{-
773+ selected <- liftIO $ Rel8.select connection do
774+ x <- Rel8.values (Rel8.lit <$> ['a', 'b', 'c'])
775+ x <- Rel8.values (Rel8.lit <$> ['d', 'e', 'f'])
776+ y <- Rel8.evaluate (Rel8.nextval "test_seq")
777+ pure (x, y, y)
778+
779+ selected === [('a', 1, 1), ('b', 2, 2), ('c', 3, 3)]
780+-}
tests/Rel8/Generic/Rel8able/Test.hs+1−10
@@ -5,7 +5,6 @@
55{-# language DuplicateRecordFields #-}
66{-# language FlexibleInstances #-}
77{-# language MultiParamTypeClasses #-}
8−{-# language StandaloneKindSignatures #-}
98{-# language TypeFamilies #-}
109{-# language UndecidableInstances #-}
1110
@@ -172,17 +171,9 @@ data NestedTableTestB f = NestedTableTestB
172171 deriving anyclass Rel8able
173172
174173
174+
175175newtype IdRecord a f = IdRecord { recordId :: Column f a }
176176 deriving stock Generic
177177
178178
179179instance 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