git.iko.soy/rowtype-yoga/purescript-yoga-postgres
on main help sign in
Unify limit/offset with ParseLimitOffset typeclass: limit @"10" for literals, limit @"$n" for params
commitff55060f933325de39283b6b26c9925122b3d944 authorMark <mark.eibes@gmail.com> dateFri 13 Feb 2026, 22:41 (8 months ago) parentbb5dcf5
1 file changed+97−87
test/Schema.purs+97−87
test/Schema.purs+97−87
@@ -2,7 +2,6 @@ module Test.Postgres.Schema where
22
33import Prelude
44
5−import Data.Array as Array
65import Data.Date (canonicalDate)
76import Data.DateTime (DateTime(..))
87import Data.Enum (toEnum)
@@ -17,8 +16,8 @@ import Foreign (Foreign, unsafeToForeign)
1716import JS.BigInt (BigInt)
1817import JS.BigInt as BigInt
1918import Prim.Boolean (True)
20−import Test.Spec (Spec, around, describe, it)
21−import Test.Spec.Assertions (shouldEqual, shouldSatisfy)
19+import Test.Spec (Spec, describe, it)
20+import Test.Spec.Assertions (shouldEqual)
2221import Type.Proxy (Proxy(..))
2322import Yoga.Postgres as PG
2423import Yoga.Postgres.Schema
@@ -781,15 +780,30 @@ spec = do
781780 it "builds nested parentheses" do
782781 (typedNestedParens # toSQL) `shouldEqual` "SELECT * FROM users WHERE (age > $a OR age < $b) AND name = $name"
783782
783+resetUsers :: PG.Connection -> Aff Unit
784+resetUsers conn = do
785+ _ <- PG.executeSimple (PG.SQL "DELETE FROM users") conn
786+ _ <- PG.executeSimple (PG.SQL "ALTER SEQUENCE users_id_seq RESTART WITH 1") conn
787+ _ <- PG.execute (PG.SQL "INSERT INTO users (name, email, age) VALUES ($1, $2, $3)")
788+ [ PG.toPGValue "Alice", PG.toPGValue "alice@example.com", PG.toPGValue 22 ]
789+ conn
790+ _ <- PG.execute (PG.SQL "INSERT INTO users (name, email, age) VALUES ($1, $2, $3)")
791+ [ PG.toPGValue "Bob", PG.toPGValue "bob@example.com", PG.toPGValue 30 ]
792+ conn
793+ pure unit
794+
784795integrationSpec :: PG.Connection -> Spec Unit
785796integrationSpec conn = do
786797 describe "Builder query execution" do
787798 it "selectAll returns all rows" do
788− rows <- from usersTable # selectAll # runQuery conn {}
789− Array.length rows `shouldEqual` 2
790− let names = map _.name rows
791− names `shouldSatisfy` Array.elem "Alice"
792− names `shouldSatisfy` Array.elem "Bob"
799+ resetUsers conn
800+ rows <- from usersTable # select @"name, email, age"
801+ # orderBy @"name"
802+ # runQuery conn {}
803+ rows `shouldEqual`
804+ [ { name: "Alice", email: "alice@example.com", age: Just 22 }
805+ , { name: "Bob", email: "bob@example.com", age: Just 30 }
806+ ]
793807
794808 it "select with columns returns projected rows" do
795809 rows <- from usersTable
@@ -801,6 +815,7 @@ integrationSpec conn = do
801815 it "select with alias" do
802816 rows <- from usersTable
803817 # select @"name, email AS e"
818+ # orderBy @"name"
804819 # runQuery conn {}
805820 rows `shouldEqual`
806821 [ { name: "Alice", e: "alice@example.com" }
@@ -836,42 +851,40 @@ integrationSpec conn = do
836851 # select @"title, tags, view_count"
837852 # where_ @"title = $title"
838853 # runQuery conn { title: "Launch" }
839− Array.length rows `shouldEqual` 1
840− (map _.title rows) `shouldEqual` [ "Launch" ]
854+ rows `shouldEqual` [ { title: "Launch", tags: [ "release", "v1" ], view_count: BigInt.fromInt 42 } ]
841855
842856 it "inserts with returningAll" do
843857 rows <- from eventsTable
844858 # insert { title: "Minimal", metadata: Jsonb (unsafeToForeign {}), tags: ([] :: Array String), created_at: testDateTime, view_count: BigInt.fromInt 0 }
845− # returning @"id, title"
859+ # returning @"title"
846860 # runQuery conn {}
847− Array.length rows `shouldEqual` 1
848− (map _.title rows) `shouldEqual` [ "Minimal" ]
861+ rows `shouldEqual` [ { title: "Minimal" } ]
849862
850863 it "WHERE with JSONB @> operator" do
851864 rows <- from eventsTable
852865 # select @"title"
853866 # where_ @"metadata @> $metadata"
854867 # runQuery conn { metadata: Jsonb (unsafeToForeign { key: "value" }) }
855− Array.length rows `shouldEqual` 1
856− (map _.title rows) `shouldEqual` [ "Launch" ]
868+ rows `shouldEqual` [ { title: "Launch" } ]
857869
858870 it "WHERE with = ANY() array param" do
859871 rows <- from usersTable
860872 # select @"name"
861873 # where_ @"id = ANY($id)"
874+ # orderBy @"name"
862875 # runQuery conn { id: [ 1, 2 ] }
863− Array.length rows `shouldSatisfy` (_ >= 1)
876+ rows `shouldEqual` [ { name: "Alice" }, { name: "Bob" } ]
864877
865878 it "complex where with multiple params" do
866879 rows <- from usersTable
867− # selectAll
880+ # select @"name, age"
868881 # where_ @"name = $name AND age > $age"
869882 # runQuery conn { name: "Bob", age: 20 }
870− Array.length rows `shouldEqual` 1
871− (map _.age rows) `shouldEqual` [ Just 30 ]
883+ rows `shouldEqual` [ { name: "Bob", age: Just 30 } ]
872884
873885 describe "Builder insert execution" do
874886 it "inserts a row and returns count" do
887+ resetUsers conn
875888 count <- from usersTable
876889 # insert { name: "Charlie", email: "charlie@example.com", age: Just 28 }
877890 # runExecute conn {}
@@ -880,19 +893,18 @@ integrationSpec conn = do
880893 # select @"name"
881894 # where_ @"name = $name"
882895 # runQuery conn { name: "Charlie" }
883− Array.length rows `shouldEqual` 1
896+ rows `shouldEqual` [ { name: "Charlie" } ]
884897
885898 it "inserts with RETURNING" do
886899 rows <- from usersTable
887900 # insert { name: "Diana", email: "diana@example.com" }
888− # returning @"id, name, email"
901+ # returning @"name, email"
889902 # runQuery conn {}
890− Array.length rows `shouldEqual` 1
891− (map _.name rows) `shouldEqual` [ "Diana" ]
892− (map _.email rows) `shouldEqual` [ "diana@example.com" ]
903+ rows `shouldEqual` [ { name: "Diana", email: "diana@example.com" } ]
893904
894905 describe "Builder update execution" do
895906 it "updates a row" do
907+ resetUsers conn
896908 count <- from usersTable
897909 # set { email: "alice-updated@example.com" }
898910 # where_ @"name = $name"
@@ -902,9 +914,7 @@ integrationSpec conn = do
902914 # select @"email"
903915 # where_ @"name = $name"
904916 # runQueryOne conn { name: "Alice" }
905− case result of
906− Just r -> r.email `shouldEqual` "alice-updated@example.com"
907− Nothing -> shouldEqual "found" "nothing"
917+ result `shouldEqual` Just { email: "alice-updated@example.com" }
908918
909919 it "updates with RETURNING" do
910920 rows <- from usersTable
@@ -912,11 +922,11 @@ integrationSpec conn = do
912922 # where_ @"name = $name"
913923 # returning @"name, age"
914924 # runQuery conn { name: "Bob" }
915− Array.length rows `shouldEqual` 1
916− (map _.age rows) `shouldEqual` [ Just 99 ]
925+ rows `shouldEqual` [ { name: "Bob", age: Just 99 } ]
917926
918927 describe "Builder upsert execution" do
919928 it "ON CONFLICT DO NOTHING skips duplicate" do
929+ resetUsers conn
920930 count <- from usersTable
921931 # insert { name: "Duplicate", email: "bob@example.com" }
922932 # onConflictDoNothing @"email"
@@ -929,26 +939,24 @@ integrationSpec conn = do
929939 # onConflict @"email" @"DO UPDATE SET name = EXCLUDED.name, age = EXCLUDED.age"
930940 # returning @"name, age"
931941 # runQuery conn {}
932− Array.length rows `shouldEqual` 1
933− (map _.name rows) `shouldEqual` [ "Updated" ]
934− (map _.age rows) `shouldEqual` [ Just 40 ]
942+ rows `shouldEqual` [ { name: "Updated", age: Just 40 } ]
935943
936944 describe "Builder order by / limit / offset execution" do
937945 it "ORDER BY sorts results" do
946+ resetUsers conn
938947 rows <- from usersTable
939948 # select @"name"
940949 # orderBy @"name ASC"
941950 # runQuery conn {}
942− let names = map _.name rows
943− names `shouldEqual` (Array.sort names)
951+ rows `shouldEqual` [ { name: "Alice" }, { name: "Bob" } ]
944952
945953 it "LIMIT restricts row count" do
946954 rows <- from usersTable
947− # selectAll
955+ # select @"name"
948956 # orderBy @"name"
949957 # limit @"1"
950958 # runQuery conn {}
951− Array.length rows `shouldEqual` 1
959+ rows `shouldEqual` [ { name: "Alice" } ]
952960
953961 it "OFFSET skips rows" do
954962 rows <- from usersTable
@@ -957,16 +965,11 @@ integrationSpec conn = do
957965 # limit @"1"
958966 # offset @"1"
959967 # runQuery conn {}
960− Array.length rows `shouldEqual` 1
961− allRows <- from usersTable
962− # select @"name"
963− # orderBy @"name ASC"
964− # runQuery conn {}
965− let allNames = map _.name allRows
966− (map _.name rows) `shouldEqual` (Array.take 1 (Array.drop 1 allNames))
968+ rows `shouldEqual` [ { name: "Bob" } ]
967969
968970 describe "Builder delete execution" do
969971 it "deletes with RETURNING" do
972+ resetUsers conn
970973 _ <- from usersTable
971974 # insert { name: "ToDelete", email: "delete@example.com", age: Just 50 }
972975 # runExecute conn {}
@@ -975,24 +978,25 @@ integrationSpec conn = do
975978 # where_ @"name = $name"
976979 # returning @"name, email"
977980 # runQuery conn { name: "ToDelete" }
978− Array.length rows `shouldEqual` 1
979− (map _.name rows) `shouldEqual` [ "ToDelete" ]
980− -- Verify actually deleted
981+ rows `shouldEqual` [ { name: "ToDelete", email: "delete@example.com" } ]
981982 result <- from usersTable
982− # selectAll
983+ # select @"name"
983984 # where_ @"name = $name"
984985 # runQueryOne conn { name: "ToDelete" }
985986 result `shouldEqual` Nothing
986987
987988 describe "Builder JOIN execution" do
988989 it "INNER JOIN returns matching rows" do
990+ resetUsers conn
989991 rows <- from usersTable
990992 # innerJoin @"users.id = posts.user_id" postsTable
991993 # select @"users.name, posts.title"
994+ # orderBy @"users.name"
992995 # runQuery conn {}
993− Array.length rows `shouldEqual` 2
994− (map _.name rows) `shouldSatisfy` Array.elem "Alice"
995− (map _.title rows) `shouldSatisfy` Array.elem "Alice's Post"
996+ rows `shouldEqual`
997+ [ { name: "Alice", title: "Alice's Post" }
998+ , { name: "Bob", title: "Bob's Post" }
999+ ]
9961000
9971001 it "INNER JOIN with WHERE filters correctly" do
9981002 rows <- from usersTable
@@ -1000,18 +1004,18 @@ integrationSpec conn = do
10001004 # select @"users.name, posts.title"
10011005 # where_ @"users.age > $age"
10021006 # runQuery conn { age: 25 }
1003− Array.length rows `shouldEqual` 1
1004− (map _.name rows) `shouldEqual` [ "Bob" ]
1005− (map _.title rows) `shouldEqual` [ "Bob's Post" ]
1007+ rows `shouldEqual` [ { name: "Bob", title: "Bob's Post" } ]
10061008
10071009 it "INNER JOIN with aliases" do
10081010 rows <- from usersTable
10091011 # innerJoin @"users.id = posts.user_id" postsTable
10101012 # select @"users.name AS author, posts.title AS post_title"
1013+ # orderBy @"users.name"
10111014 # runQuery conn {}
1012− Array.length rows `shouldEqual` 2
1013− (map _.author rows) `shouldSatisfy` Array.elem "Alice"
1014− (map _.post_title rows) `shouldSatisfy` Array.elem "Bob's Post"
1015+ rows `shouldEqual`
1016+ [ { author: "Alice", post_title: "Alice's Post" }
1017+ , { author: "Bob", post_title: "Bob's Post" }
1018+ ]
10151019
10161020 it "INNER JOIN with ORDER BY and LIMIT" do
10171021 rows <- from usersTable
@@ -1020,8 +1024,7 @@ integrationSpec conn = do
10201024 # orderBy @"users.name ASC"
10211025 # limit @"1"
10221026 # runQuery conn {}
1023− Array.length rows `shouldEqual` 1
1024− (map _.name rows) `shouldEqual` [ "Alice" ]
1027+ rows `shouldEqual` [ { name: "Alice", title: "Alice's Post" } ]
10251028
10261029 it "LEFT JOIN includes unmatched rows" do
10271030 _ <- PG.execute (PG.SQL "INSERT INTO users (name, email, age) VALUES ($1, $2, $3)")
@@ -1032,11 +1035,11 @@ integrationSpec conn = do
10321035 # select @"users.name, posts.title"
10331036 # orderBy @"users.name ASC"
10341037 # runQuery conn {}
1035− Array.length rows `shouldSatisfy` (_ >= 3)
1036− let noPostRow = Array.find (\r -> r.name == "NoPost") rows
1037− case noPostRow of
1038− Just r -> r.title `shouldEqual` Nothing
1039− Nothing -> shouldEqual "found" "nothing"
1038+ rows `shouldEqual`
1039+ [ { name: "Alice", title: Just "Alice's Post" }
1040+ , { name: "Bob", title: Just "Bob's Post" }
1041+ , { name: "NoPost", title: Nothing }
1042+ ]
10401043 _ <- PG.execute (PG.SQL "DELETE FROM users WHERE name = $1")
10411044 [ PG.toPGValue "NoPost" ]
10421045 conn
@@ -1048,23 +1051,22 @@ integrationSpec conn = do
10481051 # select @"users.name, posts.title"
10491052 # where_ @"users.name = $name"
10501053 # runQueryOne conn { name: "Alice" }
1051− case result of
1052− Just r -> r.title `shouldEqual` "Alice's Post"
1053− Nothing -> shouldEqual "found" "nothing"
1054+ result `shouldEqual` Just { name: "Alice", title: "Alice's Post" }
10541055
10551056 describe "Parameterized LIMIT/OFFSET execution" do
10561057 it "parameterized LIMIT restricts rows" do
1058+ resetUsers conn
10571059 rows <- from usersTable # select @"name" # orderBy @"name"
10581060 # limit @"$n"
10591061 # runQuery conn { n: 1 }
1060− Array.length rows `shouldEqual` 1
1062+ rows `shouldEqual` [ { name: "Alice" } ]
10611063
10621064 it "parameterized OFFSET skips rows" do
10631065 rows <- from usersTable # select @"name" # orderBy @"name"
10641066 # limit @"$n"
10651067 # offset @"$off"
10661068 # runQuery conn { n: 1, off: 1 }
1067− Array.length rows `shouldEqual` 1
1069+ rows `shouldEqual` [ { name: "Bob" } ]
10681070
10691071 it "parameterized LIMIT with WHERE" do
10701072 rows <- from usersTable # select @"name"
@@ -1072,62 +1074,69 @@ integrationSpec conn = do
10721074 # orderBy @"name"
10731075 # limit @"$n"
10741076 # runQuery conn { age: 0, n: 1 }
1075− Array.length rows `shouldEqual` 1
1077+ rows `shouldEqual` [ { name: "Alice" } ]
10761078
10771079 describe "UNION / INTERSECT / EXCEPT execution" do
10781080 it "UNION deduplicates" do
1081+ resetUsers conn
10791082 rows <-
10801083 (from usersTable # select @"name")
1081− `union` (from usersTable # select @"name")
1084+ # union (from usersTable # select @"name")
1085+ # orderBy @"name"
10821086 # runQuery conn {}
1083− Array.length rows `shouldEqual` 2
1087+ rows `shouldEqual` [ { name: "Alice" }, { name: "Bob" } ]
10841088
10851089 it "UNION ALL keeps duplicates" do
10861090 rows <-
10871091 (from usersTable # select @"name")
1088− `unionAll` (from usersTable # select @"name")
1092+ # unionAll (from usersTable # select @"name")
1093+ # orderBy @"name"
10891094 # runQuery conn {}
1090− Array.length rows `shouldEqual` 4
1095+ rows `shouldEqual`
1096+ [ { name: "Alice" }
1097+ , { name: "Alice" }
1098+ , { name: "Bob" }
1099+ , { name: "Bob" }
1100+ ]
10911101
10921102 it "UNION with params from both sides" do
10931103 rows <-
10941104 (from usersTable # select @"name" # where_ @"age > $a")
10951105 `union` (from usersTable # select @"name" # where_ @"age < $b")
10961106 # runQuery conn { a: 100, b: 0 }
1097− Array.length rows `shouldEqual` 0
1107+ rows `shouldEqual` []
10981108
10991109 it "INTERSECT returns common rows" do
11001110 rows <-
11011111 (from usersTable # select @"name")
1102− `intersect` (from usersTable # select @"name")
1112+ # intersect (from usersTable # select @"name")
1113+ # orderBy @"name"
11031114 # runQuery conn {}
1104− Array.length rows `shouldEqual` 2
1115+ rows `shouldEqual` [ { name: "Alice" }, { name: "Bob" } ]
11051116
11061117 it "EXCEPT removes matching rows" do
11071118 rows <-
11081119 (from usersTable # select @"name")
11091120 `except_` (from usersTable # select @"name" # where_ @"name = $name")
11101121 # runQuery conn { name: "Alice" }
1111− Array.length rows `shouldEqual` 1
1112− (map _.name rows) `shouldEqual` [ "Bob" ]
1122+ rows `shouldEqual` [ { name: "Bob" } ]
11131123
11141124 it "UNION with ORDER BY and LIMIT" do
11151125 rows <-
1116− from usersTable # select @"name"
1117− # union (from usersTable # select @"name")
1126+ (from usersTable # select @"name")
1127+ `union` (from usersTable # select @"name")
11181128 # orderBy @"name"
11191129 # limit @"1"
11201130 # runQuery conn {}
1121− Array.length rows `shouldEqual` 1
1122− (map _.name rows) `shouldEqual` [ "Alice" ]
1131+ rows `shouldEqual` [ { name: "Alice" } ]
11231132
11241133 describe "WHERE features execution" do
11251134 it "BETWEEN filters range" do
1135+ resetUsers conn
11261136 rows <- from usersTable # select @"name"
11271137 # where_ @"age BETWEEN $lo AND $hi"
11281138 # runQuery conn { lo: 20, hi: 26 }
1129− Array.length rows `shouldEqual` 1
1130− (map _.name rows) `shouldEqual` [ "Alice" ]
1139+ rows `shouldEqual` [ { name: "Alice" } ]
11311140
11321141 it "IS NULL matches null values" do
11331142 _ <- from usersTable
@@ -1136,7 +1145,7 @@ integrationSpec conn = do
11361145 rows <- from usersTable # select @"name"
11371146 # where_ @"age IS NULL"
11381147 # runQuery conn {}
1139− (map _.name rows) `shouldSatisfy` Array.elem "NullAge"
1148+ rows `shouldEqual` [ { name: "NullAge" } ]
11401149 _ <- PG.execute (PG.SQL "DELETE FROM users WHERE name = $1")
11411150 [ PG.toPGValue "NullAge" ]
11421151 conn
@@ -1148,8 +1157,9 @@ integrationSpec conn = do
11481157 # runExecute conn {}
11491158 rows <- from usersTable # select @"name"
11501159 # where_ @"age IS NOT NULL"
1160+ # orderBy @"name"
11511161 # runQuery conn {}
1152− (map _.name rows) `shouldSatisfy` (not <<< Array.elem "NullAge2")
1162+ rows `shouldEqual` [ { name: "Alice" }, { name: "Bob" } ]
11531163 _ <- PG.execute (PG.SQL "DELETE FROM users WHERE name = $1")
11541164 [ PG.toPGValue "NullAge2" ]
11551165 conn
@@ -1159,4 +1169,4 @@ integrationSpec conn = do
11591169 rows <- from usersTable # select @"name"
11601170 # where_ @"(age > $a OR age < $b) AND name = $name"
11611171 # runQuery conn { a: 100, b: 0, name: "Alice" }
1162− Array.length rows `shouldEqual` 0
1172+ rows `shouldEqual` ([] :: Array { name :: String })