Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type error on upsert. #885

Merged
merged 9 commits into from
Apr 11, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions persistent-mongoDB/test/EmbedTestMongo.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{-# OPTIONS_GHC -fno-warn-unused-binds -fno-warn-orphans -O0 #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE UndecidableInstances #-}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change requires UndecidableInstances. I give a nice error message for upgrading.

{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
Expand Down
1 change: 1 addition & 0 deletions persistent-mongoDB/test/EntityEmbedTestMongo.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# OPTIONS_GHC -fno-warn-unused-binds #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE EmptyDataDecls #-}
Expand Down
4 changes: 1 addition & 3 deletions persistent-mongoDB/test/MongoInit.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
{-# OPTIONS_GHC -fno-warn-orphans #-}

module MongoInit (
isTravis
, BackendMonad
BackendMonad
, runConn
, MonadIO
, persistSettings
Expand Down Expand Up @@ -71,7 +70,6 @@ import qualified Data.ByteString as BS
import Data.Text (Text)
import Database.Persist
import Database.Persist.TH ()
import System.Environment (getEnvironment)

import Database.Persist.Sql (PersistFieldSql(..))
import Database.Persist.TH (mkPersistSettings)
Expand Down
1 change: 1 addition & 0 deletions persistent-mongoDB/test/main.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{-# LANGUAGE ScopedTypeVariables #-}
{-# language RankNTypes #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
Expand Down
1 change: 1 addition & 0 deletions persistent-mysql/test/InsertDuplicateUpdate.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# OPTIONS_GHC -fno-warn-unused-binds -fno-warn-orphans -O0 #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE EmptyDataDecls #-}
Expand Down
1 change: 1 addition & 0 deletions persistent-mysql/test/main.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# OPTIONS_GHC -fno-warn-unused-binds #-}
{-# OPTIONS_GHC -fno-warn-unused-imports #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE CPP #-}
Expand Down
1 change: 1 addition & 0 deletions persistent-postgresql/test/EquivalentTypeTestPostgres.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# OPTIONS_GHC -fno-warn-unused-binds #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
Expand Down
1 change: 1 addition & 0 deletions persistent-postgresql/test/main.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# OPTIONS_GHC -fno-warn-unused-binds #-}
{-# OPTIONS_GHC -fno-warn-unused-imports #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE CPP #-}
Expand Down
7 changes: 4 additions & 3 deletions persistent-redis/tests/basic-test.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}
{-# LANGUAGE TypeFamilies, EmptyDataDecls, GADTs #-}
{-# LANGUAGE TypeSynonymInstances, MultiParamTypeClasses, GeneralizedNewtypeDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
module Main where

import qualified Database.Redis as R
Expand All @@ -12,7 +13,7 @@ import Control.Monad.IO.Class (liftIO)
import Data.Text (Text, pack, unpack)

let redisSettings = mkPersistSettings (ConT ''RedisBackend)
in share [mkPersist redisSettings] [persistLowerCase|
in share [mkPersist redisSettings] [persistLowerCase|
Person
name String
age Int
Expand All @@ -34,7 +35,7 @@ mkKey s = case keyFromValues [PersistText s] of
Left a -> fail (unpack a)

main :: IO ()
main =
main =
withRedisConn redisConf $ runRedisPool $ do
_ <- liftIO $ print "Inserting..."
s <- insert $ Person "Test" 12
Expand All @@ -45,4 +46,4 @@ main =
g <- get key :: RedisT IO (Maybe Person)
liftIO $ print g
delete s
return ()
return ()
14 changes: 4 additions & 10 deletions persistent-sqlite/test/SqliteInit.hs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ module SqliteInit (
, truncateUTCTime
, arbText
, liftA2
, MonadFail
) where

import Init
Expand All @@ -57,12 +58,9 @@ import Init
)

-- re-exports
import Control.Applicative (liftA2)
import Test.QuickCheck.Instances ()
import Data.Char (generalCategory, GeneralCategory(..))
import qualified Data.Text as T
import Data.Fixed (Pico,Micro)
import Data.Time
-- import Data.Fixed (Pico,Micro)
-- import Data.Time
import Control.Applicative as A ((<$>), (<*>))
import Control.Exception (SomeException)
import Control.Monad (void, replicateM, liftM, when, forM_)
Expand All @@ -72,22 +70,18 @@ import Test.Hspec

-- testing
import Test.HUnit ((@?=),(@=?), Assertion, assertFailure, assertBool)
import Test.QuickCheck

import qualified Data.ByteString as BS
import Data.Text (Text, unpack)
import Data.Text (Text)
import Database.Persist
import Database.Persist.TH ()
import System.Environment (getEnvironment)

import Control.Monad.Logger
import Control.Monad.Trans.Resource (ResourceT, runResourceT)
import Database.Persist.Sql
import System.Log.FastLogger (fromLogStr)

import Database.Persist.Sqlite
import Data.IORef (newIORef, IORef, writeIORef, readIORef)
import System.IO.Unsafe (unsafePerformIO)

import Control.Monad (unless, (>=>))
import Control.Monad.IO.Unlift (MonadUnliftIO)
Expand Down
1 change: 1 addition & 0 deletions persistent-sqlite/test/main.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# OPTIONS_GHC -fno-warn-unused-binds #-}
{-# OPTIONS_GHC -fno-warn-unused-imports #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE CPP #-}
Expand Down
4 changes: 4 additions & 0 deletions persistent-template/ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.7.0

* Depends on `persistent-2.10.0` which provides the `OnlyOneUniqueKey` and `AtLeastOneUniqueKey` classes. Automatically generates instances for these classes based on how many unique keys the entity definition gets. This changes requires `UndecidableInstances` to be enabled on each module that generates entity definitions. [TODO: add issue number]()

## 2.6.0
* [persistent#846](https://github.com/yesodweb/persistent/pull/846): Improve error message when marshalling fails
* [persistent#826](https://github.com/yesodweb/persistent/pull/826): Change `Unique` derive `Show`
Expand Down
97 changes: 91 additions & 6 deletions persistent-template/Database/Persist/TH.hs
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,16 @@ module Database.Persist.TH
, packPTH
, lensPTH
, parseReferences
, AtLeastOneUniqueKey(..)
, OnlyOneUniqueKey(..)
) where

import Prelude hiding ((++), take, concat, splitAt, exp)

import qualified Data.List.NonEmpty as NEL
import Database.Persist
import Database.Persist.Sql (Migration, migrate, SqlBackend, PersistFieldSql)
-- import Database.Persist.Class
import Database.Persist.Quasi
import Language.Haskell.TH.Lib (
#if MIN_VERSION_template_haskell(2,11,0)
Expand All @@ -61,7 +66,7 @@ import Language.Haskell.TH.Lib (
import Language.Haskell.TH.Quote
import Language.Haskell.TH.Syntax
import Data.Char (toLower, toUpper)
import Control.Monad (forM, (<=<), mzero)
import Control.Monad (forM, unless, (<=<), mzero)
import qualified System.IO as SIO
import Data.Text (pack, Text, append, unpack, concat, uncons, cons, stripPrefix, stripSuffix)
import qualified Data.Text as T
Expand All @@ -87,6 +92,8 @@ import Web.HttpApiData (ToHttpApiData(..), FromHttpApiData(..))
import GHC.Generics (Generic)
import qualified Data.Text.Encoding as TE

import GHC.TypeLits

-- | This special-cases "type_" and strips out its underscore. When
-- used for JSON serialization and deserialization, it works around
-- <https://github.com/yesodweb/persistent/issues/412>
Expand Down Expand Up @@ -116,14 +123,14 @@ persistFileWith :: PersistSettings -> FilePath -> Q Exp
persistFileWith ps fp = persistManyFileWith ps [fp]

-- | Same as 'persistFileWith', but uses several external files instead of
-- one. Splitting your Persistent definitions into multiple modules can
-- one. Splitting your Persistent definitions into multiple modules can
-- potentially dramatically speed up compile times.
--
-- The recommended file extension is @.persistentmodels@.
--
-- ==== __Examples__
--
-- Split your Persistent definitions into multiple files (@models1@, @models2@),
-- Split your Persistent definitions into multiple files (@models1@, @models2@),
-- then create a new module for each new file and run 'mkPersist' there:
--
-- @
Expand All @@ -145,13 +152,13 @@ persistFileWith ps fp = persistManyFileWith ps [fp]
-- -- Migrate.hs
-- 'share'
-- ['mkMigrate' "migrateAll"]
-- $('persistManyFileWith' 'lowerCaseSettings' ["models1.persistentmodels","models2.persistentmodels"])
-- $('persistManyFileWith' 'lowerCaseSettings' ["models1.persistentmodels","models2.persistentmodels"])
-- @
--
-- Tip: To get the same import behavior as if you were declaring all your models in
-- one file, import your new files @as Name@ into another file, then export @module Name@.
--
-- This approach may be used in the future to reduce memory usage during compilation,
-- This approach may be used in the future to reduce memory usage during compilation,
-- but so far we've only seen mild reductions.
--
-- See <https://github.com/yesodweb/persistent/issues/778 persistent#778> and
Expand Down Expand Up @@ -367,7 +374,8 @@ mkPersist mps ents' = do
x <- fmap Data.Monoid.mconcat $ mapM (persistFieldFromEntity mps) ents
y <- fmap mconcat $ mapM (mkEntity entMap mps) ents
z <- fmap mconcat $ mapM (mkJSON mps) ents
return $ mconcat [x, y, z]
uniqueKeyInstances <- fmap mconcat $ mapM (mkUniqueKeyInstances mps) ents
return $ mconcat [x, y, z, uniqueKeyInstances]
where
ents = map fixEntityDef ents'
entMap = M.fromList $ map (\ent -> (entityHaskell ent, ent)) ents
Expand Down Expand Up @@ -1106,6 +1114,83 @@ mkEntity entMap mps t = do
genDataType = genericDataType mps entName backendT
entName = entityHaskell t

mkUniqueKeyInstances :: MkPersistSettings -> EntityDef -> Q [Dec]
mkUniqueKeyInstances mps t = do
undecidableInstancesEnabled <- isExtEnabled UndecidableInstances
unless undecidableInstancesEnabled . fail
$ "Generating Persistent entities now requires the 'UndecidableInstances' "
<> "language extension. Please enable it in your file by copy/pasting "
<> "this line into the top of your file: \n\n"
<> "{-# LANGUAGE UndecidableInstances #-}"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gives a pretty pleasant error message. The alternative is a bit nasty.

case entityUniques t of
[] -> mappend <$> typeErrorSingle <*> typeErrorAtLeastOne
[_] -> mappend <$> singleUniqueKey <*> atLeastOneKey
(_:_) -> mappend <$> typeErrorMultiple <*> atLeastOneKey
where
requireUniquesPName = mkName "requireUniquesP"
onlyUniquePName = mkName "onlyUniqueP"
typeErrorSingle = mkOnlyUniqueError typeErrorNoneCtx
typeErrorMultiple = mkOnlyUniqueError typeErrorMultipleCtx

withPersistStoreWriteCxt =
if mpsGeneric mps
then do
write <- [t|PersistStoreWrite $(pure (VarT $ mkName "backend")) |]
pure [write]
else do
pure []

typeErrorNoneCtx = do
tyErr <- [t|TypeError (NoUniqueKeysError $(pure genDataType))|]
(tyErr :) <$> withPersistStoreWriteCxt

typeErrorMultipleCtx = do
tyErr <- [t|TypeError (MultipleUniqueKeysError $(pure genDataType))|]
(tyErr :) <$> withPersistStoreWriteCxt

mkOnlyUniqueError :: Q Cxt -> Q [Dec]
mkOnlyUniqueError mkCtx = do
ctx <- mkCtx
let impl = mkImpossible onlyUniquePName
pure [instanceD ctx onlyOneUniqueKeyClass impl]

mkImpossible name =
[ FunD name
[ Clause
[ WildP ]
(NormalB
(VarE (mkName "error") `AppE` LitE (StringL "impossible"))
)
[]
]
]

typeErrorAtLeastOne :: Q [Dec]
typeErrorAtLeastOne = do
let impl = mkImpossible requireUniquesPName
cxt <- typeErrorMultipleCtx
pure [instanceD cxt atLeastOneUniqueKeyClass impl]

singleUniqueKey :: Q [Dec]
singleUniqueKey = do
expr <- [e|\p -> head (persistUniqueKeysP p)|]
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is safe because we have already guaranteed that there are multiple unique keys

let impl = [FunD onlyUniquePName [Clause [] (NormalB expr) []]]
cxt <- withPersistStoreWriteCxt
pure [instanceD cxt onlyOneUniqueKeyClass impl]

atLeastOneUniqueKeyClass = ConT ''AtLeastOneUniqueKey `AppT` genDataType
onlyOneUniqueKeyClass = ConT ''OnlyOneUniqueKey `AppT` genDataType

atLeastOneKey :: Q [Dec]
atLeastOneKey = do
expr <- [e|\p -> NEL.fromList (persistUniqueKeysP p)|]
let impl = [FunD requireUniquesPName [Clause [] (NormalB expr) []]]
cxt <- withPersistStoreWriteCxt
pure [instanceD cxt atLeastOneUniqueKeyClass impl]

genDataType = genericDataType mps (entityHaskell t) backendT


entityText :: EntityDef -> Text
entityText = unHaskellName . entityHaskell

Expand Down
6 changes: 3 additions & 3 deletions persistent-template/persistent-template.cabal
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: persistent-template
version: 2.6.0
version: 2.7.0
license: MIT
license-file: LICENSE
author: Michael Snoyman <michael@snoyman.com>
Expand All @@ -15,9 +15,9 @@ bug-reports: https://github.com/yesodweb/persistent/issues
extra-source-files: test/main.hs ChangeLog.md README.md

library
build-depends: base >= 4.6 && < 5
build-depends: base >= 4.6 && < 5
, template-haskell
, persistent >= 2.5 && < 3
, persistent >= 2.10 && < 3
, monad-control >= 0.2 && < 1.1
, bytestring >= 0.9
, text >= 0.5
Expand Down
1 change: 1 addition & 0 deletions persistent-template/test/main.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# LANGUAGE OverloadedStrings, QuasiQuotes, TemplateHaskell, TypeFamilies, GADTs #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
Expand Down
2 changes: 1 addition & 1 deletion persistent-test/src/CustomPersistFieldTest.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# OPTIONS_GHC -fno-warn-unused-binds #-}
{-# LANGUAGE UndecidableInstances #-}
{-# language RankNTypes #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
Expand All @@ -15,7 +16,6 @@ module CustomPersistFieldTest (specsWith, customFieldMigrate) where

import Init
import CustomPersistField
import Control.Monad.Fail

share [mkPersist sqlSettings { mpsGeneric = True }, mkMigrate "customFieldMigrate"] [persistLowerCase|
BlogPost
Expand Down
1 change: 1 addition & 0 deletions persistent-test/src/CustomPrimaryKeyReferenceTest.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# LANGUAGE GADTs #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
Expand Down
1 change: 1 addition & 0 deletions persistent-test/src/DataTypeTest.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-unused-binds #-}
{-# OPTIONS_GHC -fno-warn-unused-imports #-}
{-# LANGUAGE RankNTypes #-}
Expand Down
1 change: 1 addition & 0 deletions persistent-test/src/EmbedOrderTest.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# OPTIONS_GHC -fno-warn-unused-binds #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ScopedTypeVariables, FlexibleInstances #-}
{-# LANGUAGE QuasiQuotes, TypeFamilies, GeneralizedNewtypeDeriving, TemplateHaskell,
OverloadedStrings, GADTs, FlexibleContexts, EmptyDataDecls, MultiParamTypeClasses #-}
Expand Down
1 change: 1 addition & 0 deletions persistent-test/src/EmbedTest.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# OPTIONS_GHC -fno-warn-unused-binds -fno-warn-orphans -O0 #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
Expand Down
1 change: 1 addition & 0 deletions persistent-test/src/EmptyEntityTest.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# OPTIONS_GHC -fno-warn-unused-binds #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
Expand Down
1 change: 1 addition & 0 deletions persistent-test/src/EntityEmbedTest.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# OPTIONS_GHC -fno-warn-unused-binds #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE EmptyDataDecls #-}
Expand Down