From 5d8f5b53e6f736eb74e0934ad8fe7a3d9abf5289 Mon Sep 17 00:00:00 2001 From: Esteban Ibarra Date: Thu, 8 Aug 2019 12:23:10 -0500 Subject: [PATCH] Add between (#127) * Update between so it works with SQL values * Add support for composite keys in between clause * Remove unused values from ERaw in construct * Update unsafeSqlBinOp to handle composite keys and between to use >=., <=. and &&. * Support composite keys in unsafeSqlBinOp correctly * Updated changelog * Update version number of between to 3.1.0 --- changelog.md | 2 + src/Database/Esqueleto.hs | 2 +- src/Database/Esqueleto/Internal/Internal.hs | 18 ++++++-- src/Database/Esqueleto/Internal/Language.hs | 2 +- test/Common/Test.hs | 48 +++++++++++++++++++++ 5 files changed, 67 insertions(+), 5 deletions(-) diff --git a/changelog.md b/changelog.md index 615e185..1b0c222 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,8 @@ Unreleased - @Vlix - [#128](https://github.com/bitemyapp/esqueleto/pull/128): Added `Database.Esqueleto.PostgreSQL.JSON` module with JSON operators and `JSONB` data type. +- @ibarrae + - [#127](https://github.com/bitemyapp/esqueleto/pull/127): Added `between` and support for composite keys in `unsafeSqlBinOp`. 3.0.0 ======= diff --git a/src/Database/Esqueleto.hs b/src/Database/Esqueleto.hs index 14799bc..52175c6 100644 --- a/src/Database/Esqueleto.hs +++ b/src/Database/Esqueleto.hs @@ -44,7 +44,7 @@ module Database.Esqueleto , val, isNothing, just, nothing, joinV, withNonNull , countRows, count, countDistinct , not_, (==.), (>=.), (>.), (<=.), (<.), (!=.), (&&.), (||.) - , (+.), (-.), (/.), (*.) + , between, (+.), (-.), (/.), (*.) , random_, round_, ceiling_, floor_ , min_, max_, sum_, avg_, castNum, castNumM , coalesce, coalesceDefault diff --git a/src/Database/Esqueleto/Internal/Internal.hs b/src/Database/Esqueleto/Internal/Internal.hs index 071b50f..151af41 100644 --- a/src/Database/Esqueleto/Internal/Internal.hs +++ b/src/Database/Esqueleto/Internal/Internal.hs @@ -455,6 +455,11 @@ not_ (ECompositeKey _) = throw (CompositeKeyErr NotError) (*.) :: PersistField a => SqlExpr (Value a) -> SqlExpr (Value a) -> SqlExpr (Value a) (*.) = unsafeSqlBinOp " * " +-- | @BETWEEN@. +-- +-- @since: 3.1.0 +between :: PersistField a => SqlExpr (Value a) -> (SqlExpr (Value a), SqlExpr (Value a)) -> SqlExpr (Value Bool) +a `between` (b, c) = a >=. b &&. a <=. c random_ :: (PersistField a, Num a) => SqlExpr (Value a) random_ = unsafeSqlValue "RANDOM()" @@ -1242,7 +1247,6 @@ data CompositeKeyError = | CombineInsertionError | FoldHelpError | SqlCaseError - | SqlBinOpError | SqlCastAsError | MakeOnClauseError | MakeExcError @@ -1641,7 +1645,16 @@ unsafeSqlBinOp op (ERaw p1 f1) (ERaw p2 f2) = ERaw Parens f (b2, vals2) = f2 info in ( parensM p1 b1 <> op <> parensM p2 b2 , vals1 <> vals2 ) -unsafeSqlBinOp _ _ _ = throw (CompositeKeyErr SqlBinOpError) +unsafeSqlBinOp op a b = unsafeSqlBinOp op (construct a) (construct b) + where construct :: SqlExpr (Value a) -> SqlExpr (Value a) + construct (ERaw p f) = ERaw Parens $ \info -> + let (b1, vals) = f info + build ("?", [PersistList vals']) = + (uncommas $ replicate (length vals') "?", vals') + build expr = expr + in build (parensM p b1, vals) + construct (ECompositeKey f) = + ERaw Parens $ \info -> (uncommas $ f info, mempty) {-# INLINE unsafeSqlBinOp #-} @@ -2785,4 +2798,3 @@ insertSelect = void . insertSelectCount insertSelectCount :: (MonadIO m, PersistEntity a) => SqlQuery (SqlExpr (Insertion a)) -> SqlWriteT m Int64 insertSelectCount = rawEsqueleto INSERT_INTO . fmap EInsertFinal - diff --git a/src/Database/Esqueleto/Internal/Language.hs b/src/Database/Esqueleto/Internal/Language.hs index f2698f4..dfa4985 100644 --- a/src/Database/Esqueleto/Internal/Language.hs +++ b/src/Database/Esqueleto/Internal/Language.hs @@ -47,7 +47,7 @@ module Database.Esqueleto.Internal.Language , val, isNothing, just, nothing, joinV, withNonNull , countRows, count, countDistinct , not_, (==.), (>=.), (>.), (<=.), (<.), (!=.), (&&.), (||.) - , (+.), (-.), (/.), (*.) + , between, (+.), (-.), (/.), (*.) , random_, round_, ceiling_, floor_ , min_, max_, sum_, avg_, castNum, castNumM , coalesce, coalesceDefault diff --git a/test/Common/Test.hs b/test/Common/Test.hs index 1770a6b..ce88058 100644 --- a/test/Common/Test.hs +++ b/test/Common/Test.hs @@ -630,6 +630,54 @@ testSelectWhere run = do return p liftIO $ ret `shouldBe` [ p3e ] + describe "when using between" $ do + it "works for a simple example with [uses just . val]" $ + run $ do + p1e <- insert' p1 + _ <- insert' p2 + _ <- insert' p3 + ret <- select $ + from $ \p -> do + where_ ((p ^. PersonAge) `between` (just $ val 20, just $ val 40)) + return p + liftIO $ ret `shouldBe` [ p1e ] + it "works for a proyected fields value" $ + run $ do + _ <- insert' p1 >> insert' p2 >> insert' p3 + ret <- + select $ + from $ \p -> do + where_ $ + just (p ^. PersonFavNum) + `between` + (p ^. PersonAge, p ^. PersonWeight) + liftIO $ ret `shouldBe` [] + describe "when projecting composite keys" $ do + it "works when using composite keys with val" $ + run $ do + insert_ $ Point 1 2 "" + ret <- + select $ + from $ \p -> do + where_ $ + p ^. PointId + `between` + ( val $ PointKey 1 2 + , val $ PointKey 5 6 ) + liftIO $ ret `shouldBe` [()] + it "works when using ECompositeKey constructor" $ + run $ do + insert_ $ Point 1 2 "" + ret <- + select $ + from $ \p -> do + where_ $ + p ^. PointId + `between` + ( EI.ECompositeKey $ const ["3", "4"] + , EI.ECompositeKey $ const ["5", "6"] ) + liftIO $ ret `shouldBe` [] + it "works with avg_" $ run $ do _ <- insert' p1