From 96db92f5aa90bdbe8eb8b5620bf10498d546c0f8 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Thu, 27 Oct 2022 11:02:29 -0500 Subject: [PATCH 1/4] Add NULLS FIRST / NULLS LAST qualifiers for ordering --- src/Database/Esqueleto/PostgreSQL.hs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/Database/Esqueleto/PostgreSQL.hs b/src/Database/Esqueleto/PostgreSQL.hs index 36d122bac..57ed3402d 100644 --- a/src/Database/Esqueleto/PostgreSQL.hs +++ b/src/Database/Esqueleto/PostgreSQL.hs @@ -29,6 +29,10 @@ module Database.Esqueleto.PostgreSQL , insertSelectWithConflictCount , filterWhere , values + , ascNullsFirst + , ascNullsLast + , descNullsFirst + , descNullsLast -- * Internal , unsafeSqlAggregateFunction ) where @@ -434,3 +438,21 @@ values exprs = Ex.From $ do <> "(" <> TLB.fromLazyText colsAliases <> ")" , params ) + +-- | Ascending order of this field or SqlExpression with nulls coming first. +ascNullsFirst :: PersistField a => SqlExpr (Value a) -> SqlExpr OrderBy +ascNullsFirst = orderByExpr " ASC NULLS FIRST" + +-- | Ascending order of this field or SqlExpression with nulls coming last. +-- Note that this is the same as normal ascending ordering in Postgres, but it has been included for completeness. +ascNullsLast :: PersistField a => SqlExpr (Value a) -> SqlExpr OrderBy +ascNullsLast = orderByExpr " ASC NULLS FIRST" + +-- | Descending order of this field or SqlExpression with nulls coming first. +-- Note that this is the same as normal ascending ordering in Postgres, but it has been included for completeness. +ascNullsFirst :: PersistField a => SqlExpr (Value a) -> SqlExpr OrderBy +ascNullsFirst = orderByExpr " ASC NULLS FIRST" + +-- | Descending order of this field or SqlExpression with nulls coming last. +descNullsLast :: PersistField a => SqlExpr (Value a) -> SqlExpr OrderBy +descNullsLast = orderByExpr " DESC NULLS LAST" From e5c401b37895c1e3d4c16a754371ef8d2e71a4e1 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Thu, 27 Oct 2022 11:21:46 -0500 Subject: [PATCH 2/4] Fixup --- src/Database/Esqueleto/PostgreSQL.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Database/Esqueleto/PostgreSQL.hs b/src/Database/Esqueleto/PostgreSQL.hs index 57ed3402d..68d1d9e17 100644 --- a/src/Database/Esqueleto/PostgreSQL.hs +++ b/src/Database/Esqueleto/PostgreSQL.hs @@ -446,12 +446,12 @@ ascNullsFirst = orderByExpr " ASC NULLS FIRST" -- | Ascending order of this field or SqlExpression with nulls coming last. -- Note that this is the same as normal ascending ordering in Postgres, but it has been included for completeness. ascNullsLast :: PersistField a => SqlExpr (Value a) -> SqlExpr OrderBy -ascNullsLast = orderByExpr " ASC NULLS FIRST" +ascNullsLast = orderByExpr " ASC NULLS LAST" -- | Descending order of this field or SqlExpression with nulls coming first. -- Note that this is the same as normal ascending ordering in Postgres, but it has been included for completeness. -ascNullsFirst :: PersistField a => SqlExpr (Value a) -> SqlExpr OrderBy -ascNullsFirst = orderByExpr " ASC NULLS FIRST" +descNullsFirst :: PersistField a => SqlExpr (Value a) -> SqlExpr OrderBy +descNullsFirst = orderByExpr " DESC NULLS FIRST" -- | Descending order of this field or SqlExpression with nulls coming last. descNullsLast :: PersistField a => SqlExpr (Value a) -> SqlExpr OrderBy From a2a784f0cddb4b5272e236eac5208d6471948aea Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Thu, 27 Oct 2022 11:42:05 -0500 Subject: [PATCH 3/4] Add tests --- test/PostgreSQL/Test.hs | 49 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/PostgreSQL/Test.hs b/test/PostgreSQL/Test.hs index e239f0ea8..03b7b1826 100644 --- a/test/PostgreSQL/Test.hs +++ b/test/PostgreSQL/Test.hs @@ -1339,6 +1339,54 @@ testSubselectAliasingBehavior = do pure (str, val @Int 1) asserting noExceptions +testPostgresqlNullsOrdering :: SpecDb +testPostgresqlNullsOrdering = do + describe "Postgresql NULLS orderings work" $ do + itDb "ASC NULLS FIRST works" $ do + p1e <- insert' p1 + p2e <- insert' p2 -- p2 has a null age + p3e <- insert' p3 + p4e <- insert' p4 + ret <- select $ + from $ \p -> do + orderBy [EP.ascNullsFirst (p ^. PersonAge), EP.ascNullsFirst (p ^. PersonFavNum)] + return p + -- nulls come first + asserting $ ret `shouldBe` [ p2e, p3e, p4e, p1e ] + itDb "ASC NULLS LAST works" $ do + p1e <- insert' p1 + p2e <- insert' p2 -- p2 has a null age + p3e <- insert' p3 + p4e <- insert' p4 + ret <- select $ + from $ \p -> do + orderBy [EP.ascNullsLast (p ^. PersonAge), EP.ascNullsLast (p ^. PersonFavNum)] + return p + -- nulls come last + asserting $ ret `shouldBe` [ p3e, p4e, p1e, p2e ] + itDb "DESC NULLS FIRST works" $ do + p1e <- insert' p1 + p2e <- insert' p2 -- p2 has a null age + p3e <- insert' p3 + p4e <- insert' p4 + ret <- select $ + from $ \p -> do + orderBy [EP.descNullsFirst (p ^. PersonAge), EP.descNullsFirst (p ^. PersonFavNum)] + return p + -- nulls come first + asserting $ ret `shouldBe` [ p1e, p4e, p3e, p2e ] + itDb "DESC NULLS LAST works" $ do + p1e <- insert' p1 + p2e <- insert' p2 -- p2 has a null age + p3e <- insert' p3 + p4e <- insert' p4 + ret <- select $ + from $ \p -> do + orderBy [EP.descNullsLast (p ^. PersonAge), EP.descNullsLast (p ^. PersonFavNum)] + return p + -- nulls come last + asserting $ ret `shouldBe` [ p1e, p4e, p3e, p2e ] + type JSONValue = Maybe (JSONB A.Value) @@ -1434,6 +1482,7 @@ spec = beforeAll mkConnectionPool $ do testLateralQuery testValuesExpression testSubselectAliasingBehavior + testPostgresqlNullsOrdering insertJsonValues :: SqlPersistT IO () insertJsonValues = do From ff73edb0266beaaf63d8b84c4cee7aea201b6d2e Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Thu, 27 Oct 2022 11:49:08 -0500 Subject: [PATCH 4/4] Fix test --- test/PostgreSQL/Test.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/PostgreSQL/Test.hs b/test/PostgreSQL/Test.hs index 03b7b1826..9691a9723 100644 --- a/test/PostgreSQL/Test.hs +++ b/test/PostgreSQL/Test.hs @@ -1374,7 +1374,7 @@ testPostgresqlNullsOrdering = do orderBy [EP.descNullsFirst (p ^. PersonAge), EP.descNullsFirst (p ^. PersonFavNum)] return p -- nulls come first - asserting $ ret `shouldBe` [ p1e, p4e, p3e, p2e ] + asserting $ ret `shouldBe` [ p2e, p1e, p4e, p3e ] itDb "DESC NULLS LAST works" $ do p1e <- insert' p1 p2e <- insert' p2 -- p2 has a null age