@@ -6,11 +6,10 @@ import Control.Monad.Error.Class (throwError)
6import Data.Array as Array
6import Data.Array as Array
7import Data.Maybe (Maybe(..), isNothing)
7import Data.Maybe (Maybe(..), isNothing)
8import Effect.Aff (Aff)
8import Effect.Aff (Aff)
9−import Effect.Class (liftEffect)
9+import Effect.Class (class MonadEffect, liftEffect)
10−import Effect.Exception (error)
10+import Effect.Exception as Exception
11import Foreign (MultipleErrors)
11import Foreign (MultipleErrors)
12import Test.Spec (Spec, describe, it)
12import Test.Spec (Spec, describe, it)
13−import Test.Spec.Assertions (shouldEqual)
14import Type.Function (type (#))
13import Type.Function (type (#))
15import Type.Proxy (Proxy(..))
14import Type.Proxy (Proxy(..))
16import Yoga.Om as Om
15import Yoga.Om as Om
@@ -32,624 +31,400 @@ import Yoga.Postgres.ClientOm
32 , gt, gte, ilike, inArray, join, like_, lt, lte
31 , gt, gte, ilike, inArray, join, like_, lt, lte
33 , maxOf, minOf, minOfWhere, ne, not_, or_
32 , maxOf, minOf, minOfWhere, ne, not_, or_
34 , selectFrom, selectFromWhere, selectOneFrom, selectOneFromWhere
33 , selectFrom, selectFromWhere, selectOneFrom, selectOneFromWhere
35− , sumOf, sumOfWhere, updateWhere, upsert, upsertMany, withTransaction
34+ , sumOf, sumOfWhere, transactionally, updateWhere, upsert, upsertMany
37−import Yoga.Postgres.Schema (Table, PrimaryKey, AutoIncrement, Unique, Default, DefaultExpr, ForeignKey, References, PGUUID)
36+import Yoga.Postgres.Schema (Table, PrimaryKey, AutoIncrement, Unique, Default, ForeignKey, References, PGUUID)
39−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
41−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
43−type UsersTable = Table "test_om_users"
44− ( id :: PrimaryKey (AutoIncrement Int)
46− , email :: Unique String
47− , active :: Default "true" Boolean
51−usersTable :: Proxy UsersTable
54−type SettingsTable = Table "test_om_settings"
55− ( key :: PrimaryKey String
59−settingsTable :: Proxy SettingsTable
62−type UuidTable = Table "test_om_uuid_things"
63− ( id :: PGUUID # PrimaryKey # DefaultExpr "gen_random_uuid()"
67−uuidTable :: Proxy UuidTable
70−type PostsTable = Table "test_om_posts"
71− ( id :: PrimaryKey (AutoIncrement Int)
74− , user_id :: ForeignKey "test_om_users" References "id" Int
77−postsTable :: Proxy PostsTable
80−type CommentsTable = Table "test_om_comments"
81− ( id :: PrimaryKey (AutoIncrement Int)
83− , user_id :: ForeignKey "test_om_users" References "id" Int
86−commentsTable :: Proxy CommentsTable
89−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
91−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
93−testConnection :: Aff PG.Connection
96− { host: PG.PostgresHost "localhost"
97− , port: PG.PostgresPort 5433
98− , database: PG.PostgresDatabase "test_playground"
99− , username: PG.PostgresUsername "postgres"
100− , password: PG.PostgresPassword "postgres"
105− . Om.Om { postgresConnection :: PG.Connection } (parseError :: MultipleErrors) a
108−run om conn = Om.runOm { postgresConnection: conn }
109− { exception: \e -> throwError e
110− , parseError: \errs -> throwError (error ("Parse error: " <> show errs))
116− . Om.Om { postgresConnection :: PG.Connection } () a
119−runNoErr om conn = Om.runOm { postgresConnection: conn }
120− { exception: \e -> throwError e }
123−withUsersTable :: (PG.Connection -> Aff Unit) -> Aff Unit
124−withUsersTable test = do
125− conn <- testConnection
126− _ <- PG.executeSimple createDDL conn
128− _ <- PG.executeSimple dropDDL conn
131− dropDDL = PG.SQL "DROP TABLE IF EXISTS test_om_users CASCADE"
133− """DROP TABLE IF EXISTS test_om_users CASCADE;
134− CREATE TABLE test_om_users (
135− id SERIAL PRIMARY KEY,
137− email TEXT UNIQUE NOT NULL,
138− active BOOLEAN DEFAULT true NOT NULL,
142−withSettingsTable :: (PG.Connection -> Aff Unit) -> Aff Unit
143−withSettingsTable test = do
144− conn <- testConnection
145− _ <- PG.executeSimple createDDL conn
147− _ <- PG.executeSimple dropDDL conn
150− dropDDL = PG.SQL "DROP TABLE IF EXISTS test_om_settings CASCADE"
152− """DROP TABLE IF EXISTS test_om_settings CASCADE;
153− CREATE TABLE test_om_settings (
154− key TEXT PRIMARY KEY,
158−withUuidTable :: (PG.Connection -> Aff Unit) -> Aff Unit
159−withUuidTable test = do
160− conn <- testConnection
161− _ <- PG.executeSimple createDDL conn
163− _ <- PG.executeSimple dropDDL conn
166− dropDDL = PG.SQL "DROP TABLE IF EXISTS test_om_uuid_things CASCADE"
168− """DROP TABLE IF EXISTS test_om_uuid_things CASCADE;
169− CREATE TABLE test_om_uuid_things (
170− id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
174−withPostsTable :: (PG.Connection -> Aff Unit) -> Aff Unit
175−withPostsTable test = do
176− conn <- testConnection
177− _ <- PG.executeSimple createDDL conn
179− _ <- PG.executeSimple (PG.SQL "DROP TABLE IF EXISTS test_om_posts CASCADE") conn
180− _ <- PG.executeSimple (PG.SQL "DROP TABLE IF EXISTS test_om_users CASCADE") conn
184− """DROP TABLE IF EXISTS test_om_posts CASCADE;
185− DROP TABLE IF EXISTS test_om_users CASCADE;
186− CREATE TABLE test_om_users (
187− id SERIAL PRIMARY KEY,
189− email TEXT UNIQUE NOT NULL,
190− active BOOLEAN DEFAULT true NOT NULL,
193− CREATE TABLE test_om_posts (
194− id SERIAL PRIMARY KEY,
195− title TEXT NOT NULL,
197− user_id INTEGER NOT NULL REFERENCES test_om_users(id)
200−withPostsAndCommentsTable :: (PG.Connection -> Aff Unit) -> Aff Unit
201−withPostsAndCommentsTable test = do
202− conn <- testConnection
203− _ <- PG.executeSimple createDDL conn
205− _ <- PG.executeSimple (PG.SQL "DROP TABLE IF EXISTS test_om_comments CASCADE") conn
206− _ <- PG.executeSimple (PG.SQL "DROP TABLE IF EXISTS test_om_posts CASCADE") conn
207− _ <- PG.executeSimple (PG.SQL "DROP TABLE IF EXISTS test_om_users CASCADE") conn
211− """DROP TABLE IF EXISTS test_om_comments CASCADE;
212− DROP TABLE IF EXISTS test_om_posts CASCADE;
213− DROP TABLE IF EXISTS test_om_users CASCADE;
214− CREATE TABLE test_om_users (
215− id SERIAL PRIMARY KEY,
217− email TEXT UNIQUE NOT NULL,
218− active BOOLEAN DEFAULT true NOT NULL,
221− CREATE TABLE test_om_posts (
222− id SERIAL PRIMARY KEY,
223− title TEXT NOT NULL,
225− user_id INTEGER NOT NULL REFERENCES test_om_users(id)
227− CREATE TABLE test_om_comments (
228− id SERIAL PRIMARY KEY,
230− user_id INTEGER NOT NULL REFERENCES test_om_users(id)
233−shouldBeJust :: forall a. Maybe a -> Aff a
234−shouldBeJust (Just a) = pure a
235−shouldBeJust Nothing = throwError $ error "Expected Just but got Nothing"
237−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
239−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
241spec :: Spec Unit
38spec :: Spec Unit
242spec = describe "ClientOm" do
39spec = describe "ClientOm" do
244 describe "Creating records" do
41 describe "Creating records" do
245− it "inserts a record and returns it with generated fields" $ withUsersTable \conn -> do
42+ it "inserts a record and returns it with generated fields" $ withUsersTable do
246− created <- conn # run do usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
43+ created <- usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
247 alice <- shouldBeJust created
44 alice <- shouldBeJust created
248 alice.name `shouldEqual` "Alice"
45 alice.name `shouldEqual` "Alice"
249 (alice.id > 0) `shouldEqual` true
46 (alice.id > 0) `shouldEqual` true
251− it "lets the database fill in defaults" $ withUsersTable \conn -> do
48+ it "lets the database fill in defaults" $ withUsersTable do
252− created <- conn # run do usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
49+ created <- usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
253 alice <- shouldBeJust created
50 alice <- shouldBeJust created
254 alice.active `shouldEqual` true
51 alice.active `shouldEqual` true
256− it "inserts many records at once" $ withUsersTable \conn -> do
53+ it "inserts many records at once" $ withUsersTable do
257 let noUsers = [] :: Array { name :: String, email :: String, role :: String }
54 let noUsers = [] :: Array { name :: String, email :: String, role :: String }
258− empty <- conn # run do usersTable # createMany noUsers
55+ empty <- usersTable # createMany noUsers
259 Array.length empty `shouldEqual` 0
56 Array.length empty `shouldEqual` 0
260− users <- conn # run do
261− usersTable # createMany
57+ users <- usersTable # createMany
262− [ { name: "Alice", email: "a@t.com", role: "admin" }
58+ [ { name: "Alice", email: "a@t.com", role: "admin" }
263− , { name: "Bob", email: "b@t.com", role: "user" }
59+ , { name: "Bob", email: "b@t.com", role: "user" }
264− , { name: "Carol", email: "c@t.com", role: "user" }
60+ , { name: "Carol", email: "c@t.com", role: "user" }
266 Array.length users `shouldEqual` 3
62 Array.length users `shouldEqual` 3
267− n <- conn # runNoErr do usersTable # count
63+ n <- usersTable # count
268 n `shouldEqual` 3
64 n `shouldEqual` 3
270− it "skips insert when a conflict would occur" $ withSettingsTable \conn -> do
66+ it "skips insert when a conflict would occur" $ withSettingsTable do
271− inserted <- conn # run do settingsTable # createIfNotExists { key: "theme", value: "dark" }
67+ inserted <- settingsTable # createIfNotExists { key: "theme", value: "dark" }
272 r1 <- shouldBeJust inserted
68 r1 <- shouldBeJust inserted
273 r1.value `shouldEqual` "dark"
69 r1.value `shouldEqual` "dark"
274− skipped <- conn # run do settingsTable # createIfNotExists { key: "theme", value: "light" }
70+ skipped <- settingsTable # createIfNotExists { key: "theme", value: "light" }
275 isNothing skipped `shouldEqual` true
71 isNothing skipped `shouldEqual` true
276− found <- conn # run do settingsTable # findOneBy @"key" "theme"
72+ found <- settingsTable # findOneBy @"key" "theme"
277 existing <- shouldBeJust found
73 existing <- shouldBeJust found
278 existing.value `shouldEqual` "dark"
74 existing.value `shouldEqual` "dark"
280− it "upserts: inserts or updates on conflict" $ withSettingsTable \conn -> do
76+ it "upserts: inserts or updates on conflict" $ withSettingsTable do
281− inserted <- conn # run do settingsTable # upsert { key: "theme", value: "dark" }
77+ inserted <- settingsTable # upsert { key: "theme", value: "dark" }
282 r1 <- shouldBeJust inserted
78 r1 <- shouldBeJust inserted
283 r1.value `shouldEqual` "dark"
79 r1.value `shouldEqual` "dark"
284− updated <- conn # run do settingsTable # upsert { key: "theme", value: "light" }
80+ updated <- settingsTable # upsert { key: "theme", value: "light" }
285 r2 <- shouldBeJust updated
81 r2 <- shouldBeJust updated
286 r2.value `shouldEqual` "light"
82 r2.value `shouldEqual` "light"
287− n <- conn # runNoErr do settingsTable # count
83+ n <- settingsTable # count
288 n `shouldEqual` 1
84 n `shouldEqual` 1
290− it "bulk upserts a batch" $ withSettingsTable \conn -> do
86+ it "bulk upserts a batch" $ withSettingsTable do
292− settingsTable # upsertMany
87+ _ <- settingsTable # upsertMany
293− [ { key: "theme", value: "dark" }
88+ [ { key: "theme", value: "dark" }
294− , { key: "lang", value: "en" }
89+ , { key: "lang", value: "en" }
296− n1 <- conn # runNoErr do settingsTable # count
91+ n1 <- settingsTable # count
297 n1 `shouldEqual` 2
92 n1 `shouldEqual` 2
298− updated <- conn # run do
299− settingsTable # upsertMany
93+ updated <- settingsTable # upsertMany
300− [ { key: "theme", value: "light" }
94+ [ { key: "theme", value: "light" }
301− , { key: "lang", value: "de" }
95+ , { key: "lang", value: "de" }
302− , { key: "tz", value: "UTC" }
96+ , { key: "tz", value: "UTC" }
304 Array.length updated `shouldEqual` 3
98 Array.length updated `shouldEqual` 3
305− n2 <- conn # runNoErr do settingsTable # count
99+ n2 <- settingsTable # count
306 n2 `shouldEqual` 3
100 n2 `shouldEqual` 3
307− found <- conn # run do settingsTable # findOneBy @"key" "theme"
101+ found <- settingsTable # findOneBy @"key" "theme"
308 setting <- shouldBeJust found
102 setting <- shouldBeJust found
309 setting.value `shouldEqual` "light"
103 setting.value `shouldEqual` "light"
311 describe "Finding records" do
105 describe "Finding records" do
312− it "finds a single record by primary key" $ withUsersTable \conn -> do
106+ it "finds a single record by primary key" $ withUsersTable do
313− created <- conn # run do usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
107+ created <- usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
314 alice <- shouldBeJust created
108 alice <- shouldBeJust created
315− found <- conn # run do usersTable # findById alice.id
109+ found <- usersTable # findById alice.id
316 r <- shouldBeJust found
110 r <- shouldBeJust found
317 r.name `shouldEqual` "Alice"
111 r.name `shouldEqual` "Alice"
319− it "finds a single record by any unique column" $ withUsersTable \conn -> do
113+ it "finds a single record by any unique column" $ withUsersTable do
320− _ <- conn # run do usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
114+ _ <- usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
321− found <- conn # run do usersTable # findOneBy @"email" "a@t.com"
115+ found <- usersTable # findOneBy @"email" "a@t.com"
322 alice <- shouldBeJust found
116 alice <- shouldBeJust found
323 alice.name `shouldEqual` "Alice"
117 alice.name `shouldEqual` "Alice"
325− it "finds all records matching a column value" $ withUsersTable \conn -> do
119+ it "finds all records matching a column value" $ withUsersTable do
326− _ <- conn # run do usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
120+ _ <- usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
327− _ <- conn # run do usersTable # create { name: "Bob", email: "b@t.com", role: "admin" }
121+ _ <- usersTable # create { name: "Bob", email: "b@t.com", role: "admin" }
328− _ <- conn # run do usersTable # create { name: "Carol", email: "c@t.com", role: "user" }
122+ _ <- usersTable # create { name: "Carol", email: "c@t.com", role: "user" }
329− admins <- conn # run do usersTable # findBy @"role" "admin"
123+ admins <- usersTable # findBy @"role" "admin"
330 Array.length admins `shouldEqual` 2
124 Array.length admins `shouldEqual` 2
332− it "finds all records matching multiple conditions" $ withUsersTable \conn -> do
126+ it "finds all records matching multiple conditions" $ withUsersTable do
333− _ <- conn # run do usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
127+ _ <- usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
334− _ <- conn # run do usersTable # create { name: "Bob", email: "b@t.com", role: "admin" }
128+ _ <- usersTable # create { name: "Bob", email: "b@t.com", role: "admin" }
335− _ <- conn # run do usersTable # create { name: "Carol", email: "c@t.com", role: "user" }
129+ _ <- usersTable # create { name: "Carol", email: "c@t.com", role: "user" }
336− alices <- conn # run do usersTable # findAllWhere { name: "Alice", role: "admin" }
130+ alices <- usersTable # findAllWhere { name: "Alice", role: "admin" }
337 Array.length alices `shouldEqual` 1
131 Array.length alices `shouldEqual` 1
338 alice <- shouldBeJust (Array.head alices)
132 alice <- shouldBeJust (Array.head alices)
339 alice.email `shouldEqual` "a@t.com"
133 alice.email `shouldEqual` "a@t.com"
341− it "returns Nothing when no record matches" $ withUsersTable \conn -> do
135+ it "returns Nothing when no record matches" $ withUsersTable do
342− _ <- conn # run do usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
136+ _ <- usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
343− missing <- conn # run do usersTable # findOneBy @"email" "nobody@t.com"
137+ missing <- usersTable # findOneBy @"email" "nobody@t.com"
344 isNothing missing `shouldEqual` true
138 isNothing missing `shouldEqual` true
345− alsoMissing <- conn # run do usersTable # findOneWhere { name: "Alice", role: "user" }
139+ alsoMissing <- usersTable # findOneWhere { name: "Alice", role: "user" }
346 isNothing alsoMissing `shouldEqual` true
140 isNothing alsoMissing `shouldEqual` true
348− it "finds records by a list of primary keys" $ withUsersTable \conn -> do
142+ it "finds records by a list of primary keys" $ withUsersTable do
349− created1 <- conn # run do usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
143+ created1 <- usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
350 u1 <- shouldBeJust created1
144 u1 <- shouldBeJust created1
351− created2 <- conn # run do usersTable # create { name: "Bob", email: "b@t.com", role: "user" }
145+ created2 <- usersTable # create { name: "Bob", email: "b@t.com", role: "user" }
352 u2 <- shouldBeJust created2
146 u2 <- shouldBeJust created2
353− _ <- conn # run do usersTable # create { name: "Carol", email: "c@t.com", role: "user" }
147+ _ <- usersTable # create { name: "Carol", email: "c@t.com", role: "user" }
354− found <- conn # run do usersTable # findByIdIn [ u1.id, u2.id ]
148+ found <- usersTable # findByIdIn [ u1.id, u2.id ]
355 Array.length found `shouldEqual` 2
149 Array.length found `shouldEqual` 2
356− empty <- conn # run do usersTable # findByIdIn ([] :: Array Int)
150+ empty <- usersTable # findByIdIn ([] :: Array Int)
357 Array.length empty `shouldEqual` 0
151 Array.length empty `shouldEqual` 0
359− it "returns all records in the table" $ withUsersTable \conn -> do
153+ it "returns all records in the table" $ withUsersTable do
361− usersTable # createMany
154+ _ <- usersTable # createMany
362− [ { name: "Alice", email: "a@t.com", role: "admin" }
155+ [ { name: "Alice", email: "a@t.com", role: "admin" }
363− , { name: "Bob", email: "b@t.com", role: "user" }
156+ , { name: "Bob", email: "b@t.com", role: "user" }
365− all <- conn # run do usersTable # findAll
158+ all <- usersTable # findAll
366 Array.length all `shouldEqual` 2
159 Array.length all `shouldEqual` 2
368 describe "Filtering with operators" do
161 describe "Filtering with operators" do
369− it "compares with gt, gte, lt, lte" $ withUsersTable \conn -> do
162+ it "compares with gt, gte, lt, lte" $ withUsersTable do
371− usersTable # createMany
163+ _ <- usersTable # createMany
372− [ { name: "Alice", email: "a@t.com", role: "admin" }
164+ [ { name: "Alice", email: "a@t.com", role: "admin" }
373− , { name: "Bob", email: "b@t.com", role: "user" }
165+ , { name: "Bob", email: "b@t.com", role: "user" }
374− , { name: "Carol", email: "c@t.com", role: "user" }
166+ , { name: "Carol", email: "c@t.com", role: "user" }
376− all <- conn # run do usersTable # findAll
168+ all <- usersTable # findAll
378 id1 = case Array.head all of
170 id1 = case Array.head all of
379 Just u -> u.id
171 Just u -> u.id
380 Nothing -> 0
172 Nothing -> 0
381− gtRows <- conn # run do usersTable # findAllWhere { id: gt id1 }
173+ gtRows <- usersTable # findAllWhere { id: gt id1 }
382 Array.length gtRows `shouldEqual` 2
174 Array.length gtRows `shouldEqual` 2
383− gteRows <- conn # run do usersTable # findAllWhere { id: gte id1 }
175+ gteRows <- usersTable # findAllWhere { id: gte id1 }
384 Array.length gteRows `shouldEqual` 3
176 Array.length gteRows `shouldEqual` 3
385− ltRows <- conn # run do usersTable # findAllWhere { id: lt (id1 + 3) }
177+ ltRows <- usersTable # findAllWhere { id: lt (id1 + 3) }
386 Array.length ltRows `shouldEqual` 3
178 Array.length ltRows `shouldEqual` 3
387− lteRows <- conn # run do usersTable # findAllWhere { id: lte id1 }
179+ lteRows <- usersTable # findAllWhere { id: lte id1 }
388 Array.length lteRows `shouldEqual` 1
180 Array.length lteRows `shouldEqual` 1
390− it "excludes with ne" $ withUsersTable \conn -> do
182+ it "excludes with ne" $ withUsersTable do
392− usersTable # createMany
183+ _ <- usersTable # createMany
393− [ { name: "Alice", email: "a@t.com", role: "admin" }
184+ [ { name: "Alice", email: "a@t.com", role: "admin" }
394− , { name: "Bob", email: "b@t.com", role: "user" }
185+ , { name: "Bob", email: "b@t.com", role: "user" }
395− , { name: "Carol", email: "c@t.com", role: "user" }
186+ , { name: "Carol", email: "c@t.com", role: "user" }
397− rows <- conn # run do usersTable # findAllWhere { role: ne "admin" }
188+ rows <- usersTable # findAllWhere { role: ne "admin" }
398 Array.length rows `shouldEqual` 2
189 Array.length rows `shouldEqual` 2
400− it "pattern-matches with like_ and ilike" $ withUsersTable \conn -> do
191+ it "pattern-matches with like_ and ilike" $ withUsersTable do
402− usersTable # createMany
192+ _ <- usersTable # createMany
403− [ { name: "Alice", email: "a@t.com", role: "admin" }
193+ [ { name: "Alice", email: "a@t.com", role: "admin" }
404− , { name: "Bob", email: "b@t.com", role: "user" }
194+ , { name: "Bob", email: "b@t.com", role: "user" }
405− , { name: "Carol", email: "c@t.com", role: "user" }
195+ , { name: "Carol", email: "c@t.com", role: "user" }
407− likeRows <- conn # run do usersTable # findAllWhere { name: like_ "A%" }
197+ likeRows <- usersTable # findAllWhere { name: like_ "A%" }
408 Array.length likeRows `shouldEqual` 1
198 Array.length likeRows `shouldEqual` 1
409− ilikeRows <- conn # run do usersTable # findAllWhere { name: ilike "a%" }
199+ ilikeRows <- usersTable # findAllWhere { name: ilike "a%" }
410 Array.length ilikeRows `shouldEqual` 1
200 Array.length ilikeRows `shouldEqual` 1
412− it "checks membership with inArray" $ withUsersTable \conn -> do
202+ it "checks membership with inArray" $ withUsersTable do
414− usersTable # createMany
203+ _ <- usersTable # createMany
415− [ { name: "Alice", email: "a@t.com", role: "admin" }
204+ [ { name: "Alice", email: "a@t.com", role: "admin" }
416− , { name: "Bob", email: "b@t.com", role: "user" }
205+ , { name: "Bob", email: "b@t.com", role: "user" }
417− , { name: "Carol", email: "c@t.com", role: "mod" }
206+ , { name: "Carol", email: "c@t.com", role: "mod" }
419− rows <- conn # run do usersTable # findAllWhere { role: inArray [ "admin", "mod" ] }
208+ rows <- usersTable # findAllWhere { role: inArray [ "admin", "mod" ] }
420 Array.length rows `shouldEqual` 2
209 Array.length rows `shouldEqual` 2
422− it "combines conditions with or_" $ withUsersTable \conn -> do
211+ it "combines conditions with or_" $ withUsersTable do
424− usersTable # createMany
212+ _ <- usersTable # createMany
425− [ { name: "Alice", email: "a@t.com", role: "admin" }
213+ [ { name: "Alice", email: "a@t.com", role: "admin" }
426− , { name: "Bob", email: "b@t.com", role: "user" }
214+ , { name: "Bob", email: "b@t.com", role: "user" }
427− , { name: "Carol", email: "c@t.com", role: "mod" }
215+ , { name: "Carol", email: "c@t.com", role: "mod" }
429− rows <- conn # run do usersTable # findAllWhere { role: or_ (eq_ "admin") (eq_ "mod") }
217+ rows <- usersTable # findAllWhere { role: or_ (eq_ "admin") (eq_ "mod") }
430 Array.length rows `shouldEqual` 2
218 Array.length rows `shouldEqual` 2
432− it "negates conditions with not_" $ withUsersTable \conn -> do
220+ it "negates conditions with not_" $ withUsersTable do
434− usersTable # createMany
221+ _ <- usersTable # createMany
435− [ { name: "Alice", email: "a@t.com", role: "admin" }
222+ [ { name: "Alice", email: "a@t.com", role: "admin" }
436− , { name: "Bob", email: "b@t.com", role: "user" }
223+ , { name: "Bob", email: "b@t.com", role: "user" }
437− , { name: "Carol", email: "c@t.com", role: "user" }
224+ , { name: "Carol", email: "c@t.com", role: "user" }
439− rows <- conn # run do usersTable # findAllWhere { role: not_ (eq_ "admin") }
226+ rows <- usersTable # findAllWhere { role: not_ (eq_ "admin") }
440 Array.length rows `shouldEqual` 2
227 Array.length rows `shouldEqual` 2
442 describe "Updating and deleting" do
229 describe "Updating and deleting" do
443− it "updates rows matching a condition" $ withUsersTable \conn -> do
230+ it "updates rows matching a condition" $ withUsersTable do
445− usersTable # createMany
231+ _ <- usersTable # createMany
446− [ { name: "Alice", email: "a@t.com", role: "admin" }
232+ [ { name: "Alice", email: "a@t.com", role: "admin" }
447− , { name: "Bob", email: "b@t.com", role: "user" }
233+ , { name: "Bob", email: "b@t.com", role: "user" }
448− , { name: "Carol", email: "c@t.com", role: "user" }
234+ , { name: "Carol", email: "c@t.com", role: "user" }
450− updated <- conn # run do usersTable # updateWhere { role: "user" } { role: "member" }
236+ updated <- usersTable # updateWhere { role: "user" } { role: "member" }
451 Array.length updated `shouldEqual` 2
237 Array.length updated `shouldEqual` 2
452− members <- conn # run do usersTable # findBy @"role" "member"
238+ members <- usersTable # findBy @"role" "member"
453 Array.length members `shouldEqual` 2
239 Array.length members `shouldEqual` 2
455− it "deletes rows matching a condition" $ withUsersTable \conn -> do
241+ it "deletes rows matching a condition" $ withUsersTable do
457− usersTable # createMany
242+ _ <- usersTable # createMany
458− [ { name: "Alice", email: "a@t.com", role: "admin" }
243+ [ { name: "Alice", email: "a@t.com", role: "admin" }
459− , { name: "Bob", email: "b@t.com", role: "user" }
244+ , { name: "Bob", email: "b@t.com", role: "user" }
460− , { name: "Carol", email: "c@t.com", role: "user" }
245+ , { name: "Carol", email: "c@t.com", role: "user" }
462− deleted <- conn # runNoErr do usersTable # deleteWhere { role: "user" }
247+ deleted <- usersTable # deleteWhere { role: "user" }
463 deleted `shouldEqual` 2
248 deleted `shouldEqual` 2
464− remaining <- conn # runNoErr do usersTable # count
249+ remaining <- usersTable # count
465 remaining `shouldEqual` 1
250 remaining `shouldEqual` 1
467− it "deletes and returns the deleted rows" $ withUsersTable \conn -> do
252+ it "deletes and returns the deleted rows" $ withUsersTable do
469− usersTable # createMany
253+ _ <- usersTable # createMany
470− [ { name: "Alice", email: "a@t.com", role: "admin" }
254+ [ { name: "Alice", email: "a@t.com", role: "admin" }
471− , { name: "Bob", email: "b@t.com", role: "user" }
255+ , { name: "Bob", email: "b@t.com", role: "user" }
472− , { name: "Carol", email: "c@t.com", role: "user" }
256+ , { name: "Carol", email: "c@t.com", role: "user" }
474− deleted <- conn # run do usersTable # deleteWhereReturning { role: "user" }
258+ deleted <- usersTable # deleteWhereReturning { role: "user" }
475 Array.length deleted `shouldEqual` 2
259 Array.length deleted `shouldEqual` 2
476 Array.sort (map _.name deleted) `shouldEqual` [ "Bob", "Carol" ]
260 Array.sort (map _.name deleted) `shouldEqual` [ "Bob", "Carol" ]
477− remaining <- conn # runNoErr do usersTable # count
261+ remaining <- usersTable # count
478 remaining `shouldEqual` 1
262 remaining `shouldEqual` 1
480− it "deletes everything" $ withUsersTable \conn -> do
264+ it "deletes everything" $ withUsersTable do
481− _ <- conn # run do usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
265+ _ <- usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
482− _ <- conn # run do usersTable # create { name: "Bob", email: "b@t.com", role: "user" }
266+ _ <- usersTable # create { name: "Bob", email: "b@t.com", role: "user" }
483− deleted <- conn # runNoErr do usersTable # deleteAll
267+ deleted <- usersTable # deleteAll
484 deleted `shouldEqual` 2
268 deleted `shouldEqual` 2
485− n <- conn # runNoErr do usersTable # count
269+ n <- usersTable # count
486 n `shouldEqual` 0
270 n `shouldEqual` 0
488 describe "Ordering and pagination" do
272 describe "Ordering and pagination" do
489− it "orders results by a column" $ withUsersTable \conn -> do
273+ it "orders results by a column" $ withUsersTable do
491− usersTable # createMany
274+ _ <- usersTable # createMany
492− [ { name: "Carol", email: "c@t.com", role: "user" }
275+ [ { name: "Carol", email: "c@t.com", role: "user" }
493− , { name: "Alice", email: "a@t.com", role: "admin" }
276+ , { name: "Alice", email: "a@t.com", role: "admin" }
494− , { name: "Bob", email: "b@t.com", role: "user" }
277+ , { name: "Bob", email: "b@t.com", role: "user" }
496− asc <- conn # run do usersTable # findOrdered @"name" Asc
279+ asc <- usersTable # findOrdered @"name" Asc
497 (map _.name asc) `shouldEqual` [ "Alice", "Bob", "Carol" ]
280 (map _.name asc) `shouldEqual` [ "Alice", "Bob", "Carol" ]
498− desc <- conn # run do usersTable # findOrdered @"name" Desc
281+ desc <- usersTable # findOrdered @"name" Desc
499 (map _.name desc) `shouldEqual` [ "Carol", "Bob", "Alice" ]
282 (map _.name desc) `shouldEqual` [ "Carol", "Bob", "Alice" ]
501− it "filters, orders, and limits in one call" $ withUsersTable \conn -> do
284+ it "filters, orders, and limits in one call" $ withUsersTable do
503− usersTable # createMany
285+ _ <- usersTable # createMany
504− [ { name: "Alice", email: "a@t.com", role: "user" }
286+ [ { name: "Alice", email: "a@t.com", role: "user" }
505− , { name: "Bob", email: "b@t.com", role: "user" }
287+ , { name: "Bob", email: "b@t.com", role: "user" }
506− , { name: "Carol", email: "c@t.com", role: "user" }
288+ , { name: "Carol", email: "c@t.com", role: "user" }
507− , { name: "Dave", email: "d@t.com", role: "user" }
289+ , { name: "Dave", email: "d@t.com", role: "user" }
509− page1 <- conn # run do usersTable # findWhereLimited @"name" { role: "user" } Asc { limit: 2, offset: 0 }
291+ page1 <- usersTable # findWhereLimited @"name" { role: "user" } Asc { limit: 2, offset: 0 }
510 (map _.name page1) `shouldEqual` [ "Alice", "Bob" ]
292 (map _.name page1) `shouldEqual` [ "Alice", "Bob" ]
511− page2 <- conn # run do usersTable # findWhereLimited @"name" { role: "user" } Asc { limit: 2, offset: 2 }
293+ page2 <- usersTable # findWhereLimited @"name" { role: "user" } Asc { limit: 2, offset: 2 }
512 (map _.name page2) `shouldEqual` [ "Carol", "Dave" ]
294 (map _.name page2) `shouldEqual` [ "Carol", "Dave" ]
514− it "paginates forward with cursors" $ withUsersTable \conn -> do
296+ it "paginates forward with cursors" $ withUsersTable do
516− usersTable # createMany
297+ _ <- usersTable # createMany
517− [ { name: "Alice", email: "a@t.com", role: "admin" }
298+ [ { name: "Alice", email: "a@t.com", role: "admin" }
518− , { name: "Bob", email: "b@t.com", role: "user" }
299+ , { name: "Bob", email: "b@t.com", role: "user" }
519− , { name: "Carol", email: "c@t.com", role: "user" }
300+ , { name: "Carol", email: "c@t.com", role: "user" }
520− , { name: "Dave", email: "d@t.com", role: "user" }
301+ , { name: "Dave", email: "d@t.com", role: "user" }
521− , { name: "Eve", email: "e@t.com", role: "admin" }
302+ , { name: "Eve", email: "e@t.com", role: "admin" }
523− page1 <- conn # run do usersTable # findFirst @"id" 2
304+ page1 <- usersTable # findFirst @"id" 2
524 Array.length page1.items `shouldEqual` 2
305 Array.length page1.items `shouldEqual` 2
525 page1.hasMore `shouldEqual` true
306 page1.hasMore `shouldEqual` true
527 lastId = case Array.last page1.items of
308 lastId = case Array.last page1.items of
528 Just u -> u.id
309 Just u -> u.id
529 Nothing -> 0
310 Nothing -> 0
530− page2 <- conn # run do usersTable # findAfter @"id" lastId 2
311+ page2 <- usersTable # findAfter @"id" lastId 2
531 Array.length page2.items `shouldEqual` 2
312 Array.length page2.items `shouldEqual` 2
532 page2.hasMore `shouldEqual` true
313 page2.hasMore `shouldEqual` true
534 lastId2 = case Array.last page2.items of
315 lastId2 = case Array.last page2.items of
535 Just u -> u.id
316 Just u -> u.id
536 Nothing -> 0
317 Nothing -> 0
537− page3 <- conn # run do usersTable # findAfter @"id" lastId2 2
318+ page3 <- usersTable # findAfter @"id" lastId2 2
538 Array.length page3.items `shouldEqual` 1
319 Array.length page3.items `shouldEqual` 1
539 page3.hasMore `shouldEqual` false
320 page3.hasMore `shouldEqual` false
541− it "paginates backward with cursors" $ withUsersTable \conn -> do
322+ it "paginates backward with cursors" $ withUsersTable do
543− usersTable # createMany
323+ _ <- usersTable # createMany
544− [ { name: "Alice", email: "a@t.com", role: "admin" }
324+ [ { name: "Alice", email: "a@t.com", role: "admin" }
545− , { name: "Bob", email: "b@t.com", role: "user" }
325+ , { name: "Bob", email: "b@t.com", role: "user" }
546− , { name: "Carol", email: "c@t.com", role: "user" }
326+ , { name: "Carol", email: "c@t.com", role: "user" }
547− , { name: "Dave", email: "d@t.com", role: "user" }
327+ , { name: "Dave", email: "d@t.com", role: "user" }
548− , { name: "Eve", email: "e@t.com", role: "admin" }
328+ , { name: "Eve", email: "e@t.com", role: "admin" }
550− all <- conn # run do usersTable # findAll
330+ all <- usersTable # findAll
552 lastUser = case Array.last all of
332 lastUser = case Array.last all of
553 Just u -> u
333 Just u -> u
554 Nothing -> { id: 0, name: "", email: "", active: true, role: "" }
334 Nothing -> { id: 0, name: "", email: "", active: true, role: "" }
555− page <- conn # run do usersTable # findBefore @"id" lastUser.id 2
335+ page <- usersTable # findBefore @"id" lastUser.id 2
556 Array.length page.items `shouldEqual` 2
336 Array.length page.items `shouldEqual` 2
557 page.hasMore `shouldEqual` true
337 page.hasMore `shouldEqual` true
559− it "paginates with a filter applied" $ withUsersTable \conn -> do
339+ it "paginates with a filter applied" $ withUsersTable do
561− usersTable # createMany
340+ _ <- usersTable # createMany
562− [ { name: "Alice", email: "a@t.com", role: "admin" }
341+ [ { name: "Alice", email: "a@t.com", role: "admin" }
563− , { name: "Bob", email: "b@t.com", role: "user" }
342+ , { name: "Bob", email: "b@t.com", role: "user" }
564− , { name: "Carol", email: "c@t.com", role: "user" }
343+ , { name: "Carol", email: "c@t.com", role: "user" }
565− , { name: "Dave", email: "d@t.com", role: "user" }
344+ , { name: "Dave", email: "d@t.com", role: "user" }
566− , { name: "Eve", email: "e@t.com", role: "admin" }
345+ , { name: "Eve", email: "e@t.com", role: "admin" }
568− page1 <- conn # run do usersTable # findFirstWhere @"id" { role: "user" } 2
347+ page1 <- usersTable # findFirstWhere @"id" { role: "user" } 2
569 Array.length page1.items `shouldEqual` 2
348 Array.length page1.items `shouldEqual` 2
570 page1.hasMore `shouldEqual` true
349 page1.hasMore `shouldEqual` true
572 lastId = case Array.last page1.items of
351 lastId = case Array.last page1.items of
573 Just u -> u.id
352 Just u -> u.id
574 Nothing -> 0
353 Nothing -> 0
575− page2 <- conn # run do usersTable # findAfterWhere @"id" { role: "user" } lastId 2
354+ page2 <- usersTable # findAfterWhere @"id" { role: "user" } lastId 2
576 Array.length page2.items `shouldEqual` 1
355 Array.length page2.items `shouldEqual` 1
577 page2.hasMore `shouldEqual` false
356 page2.hasMore `shouldEqual` false
579 describe "Aggregations and checks" do
358 describe "Aggregations and checks" do
580− it "counts rows, optionally with a filter" $ withUsersTable \conn -> do
359+ it "counts rows, optionally with a filter" $ withUsersTable do
581− n0 <- conn # runNoErr do usersTable # count
360+ n0 <- usersTable # count
582 n0 `shouldEqual` 0
361 n0 `shouldEqual` 0
584− usersTable # createMany
362+ _ <- usersTable # createMany
585− [ { name: "Alice", email: "a@t.com", role: "admin" }
363+ [ { name: "Alice", email: "a@t.com", role: "admin" }
586− , { name: "Bob", email: "b@t.com", role: "user" }
364+ , { name: "Bob", email: "b@t.com", role: "user" }
587− , { name: "Carol", email: "c@t.com", role: "admin" }
365+ , { name: "Carol", email: "c@t.com", role: "admin" }
589− n3 <- conn # runNoErr do usersTable # count
367+ n3 <- usersTable # count
590 n3 `shouldEqual` 3
368 n3 `shouldEqual` 3
591− nAdmins <- conn # runNoErr do usersTable # countWhere { role: "admin" }
369+ nAdmins <- usersTable # countWhere { role: "admin" }
592 nAdmins `shouldEqual` 2
370 nAdmins `shouldEqual` 2
594− it "checks whether a record exists" $ withUsersTable \conn -> do
372+ it "checks whether a record exists" $ withUsersTable do
595− created <- conn # run do usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
373+ created <- usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
596 alice <- shouldBeJust created
374 alice <- shouldBeJust created
597− yes <- conn # runNoErr do usersTable # exists alice.id
375+ yes <- usersTable # exists alice.id
598 yes `shouldEqual` true
376 yes `shouldEqual` true
599− no <- conn # runNoErr do usersTable # exists 99999
377+ no <- usersTable # exists 99999
600 no `shouldEqual` false
378 no `shouldEqual` false
601− yesWhere <- conn # runNoErr do usersTable # existsWhere { name: "Alice", role: "admin" }
379+ yesWhere <- usersTable # existsWhere { name: "Alice", role: "admin" }
602 yesWhere `shouldEqual` true
380 yesWhere `shouldEqual` true
603− noWhere <- conn # runNoErr do usersTable # existsWhere { name: "Alice", role: "user" }
381+ noWhere <- usersTable # existsWhere { name: "Alice", role: "user" }
604 noWhere `shouldEqual` false
382 noWhere `shouldEqual` false
606− it "sums and averages a numeric column" $ withUsersTable \conn -> do
384+ it "sums and averages a numeric column" $ withUsersTable do
608− usersTable # createMany
385+ _ <- usersTable # createMany
609− [ { name: "Alice", email: "a@t.com", role: "admin" }
386+ [ { name: "Alice", email: "a@t.com", role: "admin" }
610− , { name: "Bob", email: "b@t.com", role: "user" }
387+ , { name: "Bob", email: "b@t.com", role: "user" }
612− total <- conn # runNoErr do usersTable # sumOf @"id"
389+ total <- usersTable # sumOf @"id"
613 (total > 0.0) `shouldEqual` true
390 (total > 0.0) `shouldEqual` true
614− emptySum <- conn # runNoErr do usersTable # sumOfWhere @"id" { role: "nobody" }
391+ emptySum <- usersTable # sumOfWhere @"id" { role: "nobody" }
615 emptySum `shouldEqual` 0.0
392 emptySum `shouldEqual` 0.0
616− avg <- conn # runNoErr do usersTable # avgOf @"id"
393+ avg <- usersTable # avgOf @"id"
617 isNothing avg `shouldEqual` false
394 isNothing avg `shouldEqual` false
618− emptyAvg <- conn # runNoErr do usersTable # avgOfWhere @"id" { role: "nobody" }
395+ emptyAvg <- usersTable # avgOfWhere @"id" { role: "nobody" }
619 isNothing emptyAvg `shouldEqual` true
396 isNothing emptyAvg `shouldEqual` true
621− it "finds the min and max of a column" $ withUsersTable \conn -> do
398+ it "finds the min and max of a column" $ withUsersTable do
623− usersTable # createMany
399+ _ <- usersTable # createMany
624− [ { name: "Alice", email: "a@t.com", role: "admin" }
400+ [ { name: "Alice", email: "a@t.com", role: "admin" }
625− , { name: "Bob", email: "b@t.com", role: "user" }
401+ , { name: "Bob", email: "b@t.com", role: "user" }
626− , { name: "Carol", email: "c@t.com", role: "user" }
402+ , { name: "Carol", email: "c@t.com", role: "user" }
628− minName <- conn # run do usersTable # minOf @"name"
404+ minName <- usersTable # minOf @"name"
629 minName `shouldEqual` (Just "Alice")
405 minName `shouldEqual` (Just "Alice")
630− maxName <- conn # run do usersTable # maxOf @"name"
406+ maxName <- usersTable # maxOf @"name"
631 maxName `shouldEqual` (Just "Carol")
407 maxName `shouldEqual` (Just "Carol")
632− noMin <- conn # run do usersTable # minOfWhere @"name" { role: "nobody" }
408+ noMin <- usersTable # minOfWhere @"name" { role: "nobody" }
633 isNothing noMin `shouldEqual` true
409 isNothing noMin `shouldEqual` true
635− it "lists distinct values of a column" $ withUsersTable \conn -> do
411+ it "lists distinct values of a column" $ withUsersTable do
637− usersTable # createMany
412+ _ <- usersTable # createMany
638− [ { name: "Alice", email: "a@t.com", role: "admin" }
413+ [ { name: "Alice", email: "a@t.com", role: "admin" }
639− , { name: "Bob", email: "b@t.com", role: "user" }
414+ , { name: "Bob", email: "b@t.com", role: "user" }
640− , { name: "Carol", email: "c@t.com", role: "user" }
415+ , { name: "Carol", email: "c@t.com", role: "user" }
642− roles <- conn # run do usersTable # findDistinct @"role"
417+ roles <- usersTable # findDistinct @"role"
643 Array.sort roles `shouldEqual` [ "admin", "user" ]
418 Array.sort roles `shouldEqual` [ "admin", "user" ]
644− activeRoles <- conn # run do usersTable # findDistinctWhere @"role" { active: true }
419+ activeRoles <- usersTable # findDistinctWhere @"role" { active: true }
645 Array.sort activeRoles `shouldEqual` [ "admin", "user" ]
420 Array.sort activeRoles `shouldEqual` [ "admin", "user" ]
647 describe "Selecting specific columns" do
422 describe "Selecting specific columns" do
648− it "fetches only the columns you need" $ withUsersTable \conn -> do
423+ it "fetches only the columns you need" $ withUsersTable do
649− _ <- conn # run do usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
424+ _ <- usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
650− _ <- conn # run do usersTable # create { name: "Bob", email: "b@t.com", role: "user" }
425+ _ <- usersTable # create { name: "Bob", email: "b@t.com", role: "user" }
651− _ <- conn # run do usersTable # create { name: "Carol", email: "c@t.com", role: "user" }
426+ _ <- usersTable # create { name: "Carol", email: "c@t.com", role: "user" }
652− rows <- conn # run do usersTable # selectFrom @"name, email"
427+ rows <- usersTable # selectFrom @"name, email"
653 Array.length rows `shouldEqual` 3
428 Array.length rows `shouldEqual` 3
655 first = case Array.head rows of
430 first = case Array.head rows of
@@ -657,31 +432,31 @@ spec = describe "ClientOm" do
657 Nothing -> { name: "", email: "" }
432 Nothing -> { name: "", email: "" }
658 first.name `shouldEqual` "Alice"
433 first.name `shouldEqual` "Alice"
659 first.email `shouldEqual` "a@t.com"
434 first.email `shouldEqual` "a@t.com"
660− filtered <- conn # run do usersTable # selectFromWhere @"name" { role: "user" }
435+ filtered <- usersTable # selectFromWhere @"name" { role: "user" }
661 Array.length filtered `shouldEqual` 2
436 Array.length filtered `shouldEqual` 2
662 Array.sort (map _.name filtered) `shouldEqual` [ "Bob", "Carol" ]
437 Array.sort (map _.name filtered) `shouldEqual` [ "Bob", "Carol" ]
664− it "fetches one row with specific columns and a filter" $ withUsersTable \conn -> do
439+ it "fetches one row with specific columns and a filter" $ withUsersTable do
665− _ <- conn # run do usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
440+ _ <- usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
666− _ <- conn # run do usersTable # create { name: "Bob", email: "b@t.com", role: "user" }
441+ _ <- usersTable # create { name: "Bob", email: "b@t.com", role: "user" }
667− found <- conn # run do usersTable # selectOneFrom @"name, role"
442+ found <- usersTable # selectOneFrom @"name, role"
668 r <- shouldBeJust found
443 r <- shouldBeJust found
669 r.name `shouldEqual` "Alice"
444 r.name `shouldEqual` "Alice"
670 r.role `shouldEqual` "admin"
445 r.role `shouldEqual` "admin"
671− byName <- conn # run do usersTable # selectOneFromWhere @"email" { name: "Bob" }
446+ byName <- usersTable # selectOneFromWhere @"email" { name: "Bob" }
672 r2 <- shouldBeJust byName
447 r2 <- shouldBeJust byName
673 r2.email `shouldEqual` "b@t.com"
448 r2.email `shouldEqual` "b@t.com"
674− missing <- conn # run do usersTable # selectOneFromWhere @"email" { name: "Nobody" }
449+ missing <- usersTable # selectOneFromWhere @"email" { name: "Nobody" }
675 isNothing missing `shouldEqual` true
450 isNothing missing `shouldEqual` true
677 describe "Relationships" do
452 describe "Relationships" do
678− it "joins a child table to its parent (flat)" $ withPostsTable \conn -> do
453+ it "joins a child table to its parent (flat)" $ withPostsTable do
679− _ <- conn # run do usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
454+ _ <- usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
680− _ <- conn # run do usersTable # create { name: "Bob", email: "b@t.com", role: "user" }
455+ _ <- usersTable # create { name: "Bob", email: "b@t.com", role: "user" }
681− _ <- conn # run do postsTable # create { title: "First Post", body: "Hello", user_id: 1 }
456+ _ <- postsTable # create { title: "First Post", body: "Hello", user_id: 1 }
682− _ <- conn # run do postsTable # create { title: "Second Post", body: "World", user_id: 1 }
457+ _ <- postsTable # create { title: "Second Post", body: "World", user_id: 1 }
683− _ <- conn # run do postsTable # create { title: "Bob's Post", body: "Hi", user_id: 2 }
458+ _ <- postsTable # create { title: "Bob's Post", body: "Hi", user_id: 2 }
684− rows <- conn # run do findWith postsTable usersTable
459+ rows <- findWith postsTable usersTable
685 Array.length rows `shouldEqual` 3
460 Array.length rows `shouldEqual` 3
687 r = case Array.head rows of
462 r = case Array.head rows of
@@ -690,20 +465,20 @@ spec = describe "ClientOm" do
690 r.test_om_posts.title `shouldEqual` "First Post"
465 r.test_om_posts.title `shouldEqual` "First Post"
691 r.test_om_users.name `shouldEqual` "Alice"
466 r.test_om_users.name `shouldEqual` "Alice"
693− it "finds a joined record by primary key" $ withPostsTable \conn -> do
468+ it "finds a joined record by primary key" $ withPostsTable do
694− _ <- conn # run do usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
469+ _ <- usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
695− _ <- conn # run do postsTable # create { title: "My Post", body: "Content", user_id: 1 }
470+ _ <- postsTable # create { title: "My Post", body: "Content", user_id: 1 }
696− found <- conn # run do findByIdWith 1 postsTable usersTable
471+ found <- findByIdWith 1 postsTable usersTable
697 r <- shouldBeJust found
472 r <- shouldBeJust found
698 r.test_om_posts.title `shouldEqual` "My Post"
473 r.test_om_posts.title `shouldEqual` "My Post"
699 r.test_om_users.name `shouldEqual` "Alice"
474 r.test_om_users.name `shouldEqual` "Alice"
701− it "joins with a filter" $ withPostsTable \conn -> do
476+ it "joins with a filter" $ withPostsTable do
702− _ <- conn # run do usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
477+ _ <- usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
703− _ <- conn # run do usersTable # create { name: "Bob", email: "b@t.com", role: "user" }
478+ _ <- usersTable # create { name: "Bob", email: "b@t.com", role: "user" }
704− _ <- conn # run do postsTable # create { title: "Post A", body: "A", user_id: 1 }
479+ _ <- postsTable # create { title: "Post A", body: "A", user_id: 1 }
705− _ <- conn # run do postsTable # create { title: "Post B", body: "B", user_id: 2 }
480+ _ <- postsTable # create { title: "Post B", body: "B", user_id: 2 }
706− rows <- conn # run do findWithWhere { user_id: 1 } postsTable usersTable
481+ rows <- findWithWhere { user_id: 1 } postsTable usersTable
707 Array.length rows `shouldEqual` 1
482 Array.length rows `shouldEqual` 1
709 r = case Array.head rows of
484 r = case Array.head rows of
@@ -711,12 +486,12 @@ spec = describe "ClientOm" do
711 Nothing -> { test_om_posts: { id: 0, title: "", body: "", user_id: 0 }, test_om_users: { id: 0, name: "", email: "", active: false, role: "" } }
486 Nothing -> { test_om_posts: { id: 0, title: "", body: "", user_id: 0 }, test_om_users: { id: 0, name: "", email: "", active: false, role: "" } }
712 r.test_om_posts.title `shouldEqual` "Post A"
487 r.test_om_posts.title `shouldEqual` "Post A"
714− it "nests children under their parent" $ withPostsTable \conn -> do
489+ it "nests children under their parent" $ withPostsTable do
715− _ <- conn # run do usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
490+ _ <- usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
716− _ <- conn # run do usersTable # create { name: "Bob", email: "b@t.com", role: "user" }
491+ _ <- usersTable # create { name: "Bob", email: "b@t.com", role: "user" }
717− _ <- conn # run do postsTable # create { title: "Post 1", body: "A", user_id: 1 }
492+ _ <- postsTable # create { title: "Post 1", body: "A", user_id: 1 }
718− _ <- conn # run do postsTable # create { title: "Post 2", body: "B", user_id: 1 }
493+ _ <- postsTable # create { title: "Post 2", body: "B", user_id: 1 }
719− rows <- conn # run do findIncluding @"posts" usersTable postsTable
494+ rows <- findIncluding @"posts" usersTable postsTable
720 Array.length rows `shouldEqual` 2
495 Array.length rows `shouldEqual` 2
722 alice = case Array.head rows of
497 alice = case Array.head rows of
@@ -725,22 +500,22 @@ spec = describe "ClientOm" do
725 alice.name `shouldEqual` "Alice"
500 alice.name `shouldEqual` "Alice"
726 Array.length alice.posts `shouldEqual` 2
501 Array.length alice.posts `shouldEqual` 2
728− it "nests children for a single parent by id" $ withPostsTable \conn -> do
503+ it "nests children for a single parent by id" $ withPostsTable do
729− _ <- conn # run do usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
504+ _ <- usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
730− _ <- conn # run do postsTable # create { title: "Post 1", body: "A", user_id: 1 }
505+ _ <- postsTable # create { title: "Post 1", body: "A", user_id: 1 }
731− _ <- conn # run do postsTable # create { title: "Post 2", body: "B", user_id: 1 }
506+ _ <- postsTable # create { title: "Post 2", body: "B", user_id: 1 }
732− found <- conn # run do findByIdIncluding @"posts" 1 usersTable postsTable
507+ found <- findByIdIncluding @"posts" 1 usersTable postsTable
733 r <- shouldBeJust found
508 r <- shouldBeJust found
734 r.name `shouldEqual` "Alice"
509 r.name `shouldEqual` "Alice"
735 Array.length r.posts `shouldEqual` 2
510 Array.length r.posts `shouldEqual` 2
737− it "uses the join combinator to compose tables" $ withPostsTable \conn -> do
512+ it "uses the join combinator to compose tables" $ withPostsTable do
738− _ <- conn # run do usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
513+ _ <- usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
739− _ <- conn # run do usersTable # create { name: "Bob", email: "b@t.com", role: "user" }
514+ _ <- usersTable # create { name: "Bob", email: "b@t.com", role: "user" }
740− _ <- conn # run do postsTable # create { title: "Post 1", body: "A", user_id: 1 }
515+ _ <- postsTable # create { title: "Post 1", body: "A", user_id: 1 }
741− _ <- conn # run do postsTable # create { title: "Post 2", body: "B", user_id: 1 }
516+ _ <- postsTable # create { title: "Post 2", body: "B", user_id: 1 }
742− _ <- conn # run do postsTable # create { title: "Post 3", body: "C", user_id: 2 }
517+ _ <- postsTable # create { title: "Post 3", body: "C", user_id: 2 }
743− rows <- conn # run do (usersTable # join postsTable) # findAll
518+ rows <- (usersTable # join postsTable) # findAll
744 Array.length rows `shouldEqual` 2
519 Array.length rows `shouldEqual` 2
746 alice = case Array.head rows of
521 alice = case Array.head rows of
@@ -749,12 +524,12 @@ spec = describe "ClientOm" do
749 alice.name `shouldEqual` "Alice"
524 alice.name `shouldEqual` "Alice"
750 Array.length alice.test_om_posts `shouldEqual` 2
525 Array.length alice.test_om_posts `shouldEqual` 2
752− it "joins multiple child tables at once" $ withPostsAndCommentsTable \conn -> do
527+ it "joins multiple child tables at once" $ withPostsAndCommentsTable do
753− _ <- conn # run do usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
528+ _ <- usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
754− _ <- conn # run do postsTable # create { title: "Post 1", body: "A", user_id: 1 }
529+ _ <- postsTable # create { title: "Post 1", body: "A", user_id: 1 }
755− _ <- conn # run do postsTable # create { title: "Post 2", body: "B", user_id: 1 }
530+ _ <- postsTable # create { title: "Post 2", body: "B", user_id: 1 }
756− _ <- conn # run do commentsTable # create { text: "Nice!", user_id: 1 }
531+ _ <- commentsTable # create { text: "Nice!", user_id: 1 }
757− rows <- conn # run do (usersTable # join postsTable # join commentsTable) # findAll
532+ rows <- (usersTable # join postsTable # join commentsTable) # findAll
758 Array.length rows `shouldEqual` 1
533 Array.length rows `shouldEqual` 1
760 r = case Array.head rows of
535 r = case Array.head rows of
@@ -765,27 +540,239 @@ spec = describe "ClientOm" do
765 Array.length r.test_om_comments `shouldEqual` 1
540 Array.length r.test_om_comments `shouldEqual` 1
767 describe "Transactions" do
542 describe "Transactions" do
768− it "commits multiple inserts atomically" $ withUsersTable \conn -> do
543+ it "commits multiple inserts atomically" $ withUsersTable do
769− conn # run do
544+ transactionally do
771− _ <- usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
545+ _ <- usersTable # create { name: "Alice", email: "a@t.com", role: "admin" }
772− _ <- usersTable # create { name: "Bob", email: "b@t.com", role: "user" }
546+ _ <- usersTable # create { name: "Bob", email: "b@t.com", role: "user" }
773− pure unit
547+ pure unit
774− n <- conn # runNoErr do usersTable # count
548+ n <- usersTable # count
775 n `shouldEqual` 2
549 n `shouldEqual` 2
777 describe "UUIDs and newtypes" do
551 describe "UUIDs and newtypes" do
778− it "round-trips a UUID primary key through create/find/exists" $ withUuidTable \conn -> do
552+ it "round-trips a UUID primary key through create/find/exists" $ withUuidTable do
779− created <- conn # run do uuidTable # create { label: "first" }
553+ created <- uuidTable # create { label: "first" }
780 row <- shouldBeJust created
554 row <- shouldBeJust created
781 row.label `shouldEqual` "first"
555 row.label `shouldEqual` "first"
782− found <- conn # run do uuidTable # findById row.id
556+ found <- uuidTable # findById row.id
783 r2 <- shouldBeJust found
557 r2 <- shouldBeJust found
784 r2.label `shouldEqual` "first"
558 r2.label `shouldEqual` "first"
785− byCol <- conn # run do uuidTable # findBy @"label" "first"
559+ byCol <- uuidTable # findBy @"label" "first"
786 Array.length byCol `shouldEqual` 1
560 Array.length byCol `shouldEqual` 1
787− byId <- conn # run do uuidTable # findOneBy @"id" row.id
561+ byId <- uuidTable # findOneBy @"id" row.id
788 r3 <- shouldBeJust byId
562 r3 <- shouldBeJust byId
789 r3.label `shouldEqual` "first"
563 r3.label `shouldEqual` "first"
790− yes <- conn # runNoErr do uuidTable # exists row.id
564+ yes <- uuidTable # exists row.id
791 yes `shouldEqual` true
565 yes `shouldEqual` true
567+-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
569+-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
571+type UsersTable = Table "test_om_users"
572+ ( id :: PrimaryKey (AutoIncrement Int)
574+ , email :: Unique String
575+ , active :: Default "true" Boolean
579+usersTable :: Proxy UsersTable
582+type SettingsTable = Table "test_om_settings"
583+ ( key :: PrimaryKey String
587+settingsTable :: Proxy SettingsTable
588+settingsTable = Proxy
590+type UuidTable = Table "test_om_uuid_things"
591+ ( id :: PGUUID # PrimaryKey # Default "gen_random_uuid()"
595+uuidTable :: Proxy UuidTable
598+type PostsTable = Table "test_om_posts"
599+ ( id :: PrimaryKey (AutoIncrement Int)
602+ , user_id :: ForeignKey "test_om_users" References "id" Int
605+postsTable :: Proxy PostsTable
608+type CommentsTable = Table "test_om_comments"
609+ ( id :: PrimaryKey (AutoIncrement Int)
611+ , user_id :: ForeignKey "test_om_users" References "id" Int
614+commentsTable :: Proxy CommentsTable
615+commentsTable = Proxy
617+-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
619+-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
621+type TestOm = Om.Om { postgresConnection :: PG.Connection } (parseError :: MultipleErrors) Unit
623+shouldEqual :: forall m t. MonadEffect m => Show t => Eq t => t -> t -> m Unit
624+shouldEqual v1 v2 = when (v1 /= v2) $ liftEffect $ Exception.throwException $ Exception.error $ show v1 <> " ≠ " <> show v2
626+shouldBeJust :: forall m a. MonadEffect m => Maybe a -> m a
627+shouldBeJust (Just a) = pure a
628+shouldBeJust Nothing = liftEffect $ Exception.throwException $ Exception.error "Expected Just but got Nothing"
630+testConnection :: Aff PG.Connection
633+ { host: PG.PostgresHost "localhost"
634+ , port: PG.PostgresPort 5433
635+ , database: PG.PostgresDatabase "test_playground"
636+ , username: PG.PostgresUsername "postgres"
637+ , password: PG.PostgresPassword "postgres"
640+runOm :: TestOm -> Aff Unit
642+ conn <- testConnection
643+ Om.runOm { postgresConnection: conn }
644+ { exception: \e -> throwError e
645+ , parseError: \errs -> throwError (Exception.error ("Parse error: " <> show errs))
650+withUsersTable :: TestOm -> Aff Unit
651+withUsersTable test = do
652+ conn <- testConnection
653+ _ <- PG.executeSimple createDDL conn
654+ Om.runOm { postgresConnection: conn }
655+ { exception: \e -> throwError e
656+ , parseError: \errs -> throwError (Exception.error ("Parse error: " <> show errs))
659+ _ <- PG.executeSimple dropDDL conn
662+ dropDDL = PG.SQL "DROP TABLE IF EXISTS test_om_users CASCADE"
664+ """DROP TABLE IF EXISTS test_om_users CASCADE;
665+ CREATE TABLE test_om_users (
666+ id SERIAL PRIMARY KEY,
668+ email TEXT UNIQUE NOT NULL,
669+ active BOOLEAN DEFAULT true NOT NULL,
673+withSettingsTable :: TestOm -> Aff Unit
674+withSettingsTable test = do
675+ conn <- testConnection
676+ _ <- PG.executeSimple createDDL conn
677+ Om.runOm { postgresConnection: conn }
678+ { exception: \e -> throwError e
679+ , parseError: \errs -> throwError (Exception.error ("Parse error: " <> show errs))
682+ _ <- PG.executeSimple dropDDL conn
685+ dropDDL = PG.SQL "DROP TABLE IF EXISTS test_om_settings CASCADE"
687+ """DROP TABLE IF EXISTS test_om_settings CASCADE;
688+ CREATE TABLE test_om_settings (
689+ key TEXT PRIMARY KEY,
693+withUuidTable :: TestOm -> Aff Unit
694+withUuidTable test = do
695+ conn <- testConnection
696+ _ <- PG.executeSimple createDDL conn
697+ Om.runOm { postgresConnection: conn }
698+ { exception: \e -> throwError e
699+ , parseError: \errs -> throwError (Exception.error ("Parse error: " <> show errs))
702+ _ <- PG.executeSimple dropDDL conn
705+ dropDDL = PG.SQL "DROP TABLE IF EXISTS test_om_uuid_things CASCADE"
707+ """DROP TABLE IF EXISTS test_om_uuid_things CASCADE;
708+ CREATE TABLE test_om_uuid_things (
709+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
713+withPostsTable :: TestOm -> Aff Unit
714+withPostsTable test = do
715+ conn <- testConnection
716+ _ <- PG.executeSimple createDDL conn
717+ Om.runOm { postgresConnection: conn }
718+ { exception: \e -> throwError e
719+ , parseError: \errs -> throwError (Exception.error ("Parse error: " <> show errs))
722+ _ <- PG.executeSimple (PG.SQL "DROP TABLE IF EXISTS test_om_posts CASCADE") conn
723+ _ <- PG.executeSimple (PG.SQL "DROP TABLE IF EXISTS test_om_users CASCADE") conn
727+ """DROP TABLE IF EXISTS test_om_posts CASCADE;
728+ DROP TABLE IF EXISTS test_om_users CASCADE;
729+ CREATE TABLE test_om_users (
730+ id SERIAL PRIMARY KEY,
732+ email TEXT UNIQUE NOT NULL,
733+ active BOOLEAN DEFAULT true NOT NULL,
736+ CREATE TABLE test_om_posts (
737+ id SERIAL PRIMARY KEY,
738+ title TEXT NOT NULL,
740+ user_id INTEGER NOT NULL REFERENCES test_om_users(id)
743+withPostsAndCommentsTable :: TestOm -> Aff Unit
744+withPostsAndCommentsTable test = do
745+ conn <- testConnection
746+ _ <- PG.executeSimple createDDL conn
747+ Om.runOm { postgresConnection: conn }
748+ { exception: \e -> throwError e
749+ , parseError: \errs -> throwError (Exception.error ("Parse error: " <> show errs))
752+ _ <- PG.executeSimple (PG.SQL "DROP TABLE IF EXISTS test_om_comments CASCADE") conn
753+ _ <- PG.executeSimple (PG.SQL "DROP TABLE IF EXISTS test_om_posts CASCADE") conn
754+ _ <- PG.executeSimple (PG.SQL "DROP TABLE IF EXISTS test_om_users CASCADE") conn
758+ """DROP TABLE IF EXISTS test_om_comments CASCADE;
759+ DROP TABLE IF EXISTS test_om_posts CASCADE;
760+ DROP TABLE IF EXISTS test_om_users CASCADE;
761+ CREATE TABLE test_om_users (
762+ id SERIAL PRIMARY KEY,
764+ email TEXT UNIQUE NOT NULL,
765+ active BOOLEAN DEFAULT true NOT NULL,
768+ CREATE TABLE test_om_posts (
769+ id SERIAL PRIMARY KEY,
770+ title TEXT NOT NULL,
772+ user_id INTEGER NOT NULL REFERENCES test_om_users(id)
774+ CREATE TABLE test_om_comments (
775+ id SERIAL PRIMARY KEY,
777+ user_id INTEGER NOT NULL REFERENCES test_om_users(id)