From 9a762e9f20e0d1a0b8d3cb51e15c984a9fd0f447 Mon Sep 17 00:00:00 2001 From: Ben Levy Date: Sun, 22 Mar 2020 10:30:45 -0500 Subject: [PATCH] Update (^.) to fix natural key handling (#177) * Update (^.) to only treat natural keys with more than one component as ECompositeKey. Fixes #176. * Update article metadata test to ensure the correct response was being returned instead of just check if an exception was thrown * Add article metadata to cleanDB before deleting all articles to fix foreign key constraint errors * Bump version number and add changelog entry --- changelog.md | 6 ++++++ esqueleto.cabal | 2 +- src/Database/Esqueleto/Internal/Internal.hs | 13 ++++++++++--- test/Common/Test.hs | 16 +++++++++++++++- 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/changelog.md b/changelog.md index 7fb7592..fdecf6d 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,9 @@ +3.3.2 +======== + +- @belevy + - [#177](https://github.com/bitemyapp/esqueleto/pull/177) Fix natural key handling in (^.) + 3.3.1.1 ======== diff --git a/esqueleto.cabal b/esqueleto.cabal index 4882743..7dcb29c 100644 --- a/esqueleto.cabal +++ b/esqueleto.cabal @@ -1,7 +1,7 @@ cabal-version: 1.12 name: esqueleto -version: 3.3.1.1 +version: 3.3.2 synopsis: Type-safe EDSL for SQL queries on persistent backends. description: @esqueleto@ is a bare bones, type-safe EDSL for SQL queries that works with unmodified @persistent@ SQL backends. Its language closely resembles SQL, so you don't have to learn new concepts, just new syntax, and it's fairly easy to predict the generated SQL and optimize it for your backend. Most kinds of errors committed when writing SQL are caught as compile-time errors---although it is possible to write type-checked @esqueleto@ queries that fail at runtime. . diff --git a/src/Database/Esqueleto/Internal/Internal.hs b/src/Database/Esqueleto/Internal/Internal.hs index 1559f5d..1a8b717 100644 --- a/src/Database/Esqueleto/Internal/Internal.hs +++ b/src/Database/Esqueleto/Internal/Internal.hs @@ -522,10 +522,17 @@ subSelectUnsafe = sub SELECT -> EntityField val typ -> SqlExpr (Value typ) EEntity ident ^. field - | isComposite = ECompositeKey $ \info -> dot info <$> compositeFields pdef - | otherwise = ERaw Never $ \info -> (dot info $ persistFieldDef field, []) + | isIdField field = idFieldValue + | otherwise = ERaw Never $ \info -> (dot info $ persistFieldDef field, []) where - isComposite = isIdField field && hasCompositeKey ed + idFieldValue = + case entityKeyFields ed of + idField:[] -> + ERaw Never $ \info -> (dot info idField, []) + + idFields -> + ECompositeKey $ \info -> dot info <$> idFields + dot info x = useIdent info ident <> "." <> fromDBName info (fieldDB x) ed = entityDef $ getEntityVal (Proxy :: Proxy (SqlExpr (Entity val))) Just pdef = entityPrimary ed diff --git a/test/Common/Test.hs b/test/Common/Test.hs index 1ae2059..820c21e 100644 --- a/test/Common/Test.hs +++ b/test/Common/Test.hs @@ -165,6 +165,10 @@ share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistUpperCase| frontcoverNumber Int Foreign Frontcover fkfrontcover frontcoverNumber deriving Eq Show + ArticleMetadata + articleId ArticleId + Primary articleId + deriving Eq Show Tag name String maxlen=100 Primary name @@ -753,7 +757,16 @@ testSelectJoin run = do liftIO $ do retFc `shouldBe` fc fcPk `shouldBe` thePk - + it "allows using a primary key that is itself a key of another table" $ + run $ do + let number = 101 + insert_ $ Frontcover number "" + articleId <- insert $ Article "title" number + articleMetaE <- insert' (ArticleMetadata articleId) + result <- select . from $ \articleMetadata -> do + where_ $ (articleMetadata ^. ArticleMetadataId) ==. (val ((ArticleMetadataKey articleId))) + pure articleMetadata + liftIO $ [articleMetaE] `shouldBe` result it "works with a ForeignKey to a non-id primary key returning both entities" $ run $ do let fc = Frontcover number "" @@ -2332,6 +2345,7 @@ cleanDB = do delete $ from $ \(_ :: SqlExpr (Entity CcList)) -> return () delete $ from $ \(_ :: SqlExpr (Entity ArticleTag)) -> return () + delete $ from $ \(_ :: SqlExpr (Entity ArticleMetadata)) -> return () delete $ from $ \(_ :: SqlExpr (Entity Article)) -> return () delete $ from $ \(_ :: SqlExpr (Entity Article2)) -> return () delete $ from $ \(_ :: SqlExpr (Entity Tag)) -> return ()