packages feed

persistent-postgresql 2.13.7.0 → 2.14.0.0

raw patch · 15 files changed

+1529/−1342 lines, 15 filesdep +postgresql-simple-interval

Dependencies added: postgresql-simple-interval

Files

ChangeLog.md view
@@ -1,5 +1,14 @@ # Changelog for persistent-postgresql +## 2.14.0.0++* [#1604](https://github.com/yesodweb/persistent/pull/1604)+    * Changed the representation of intervals to use the `Interval` type from [the `postgresql-simple-interval` package](https://hackage.haskell.org/package/postgresql-simple-interval).+      This changes the behavior of `PgInterval` for very small and very large values.+    * Previously `PgInterval 0.000_000_9` would be rounded to `0.000_001` seconds, but now it is truncated to 0 seconds.+    * Previously `PgInterval 9_223_372_036_854.775_808` would overflow and throw a SQL error, but now it saturates to `9_223_372_036_854.775_807` seconds.+    * The SQL representation of `PgInterval` now always includes the `interval` prefix, like `interval '1 second'`.+ ## 2.13.7.0  * [#1600](https://github.com/yesodweb/persistent/pull/1600)
Database/Persist/Postgresql/Internal.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-} {-# LANGUAGE ViewPatterns #-}  module Database.Persist.Postgresql.Internal@@ -35,6 +36,7 @@ import qualified Database.PostgreSQL.Simple as PG import qualified Database.PostgreSQL.Simple.FromField as PGFF import qualified Database.PostgreSQL.Simple.Internal as PG+import qualified Database.PostgreSQL.Simple.Interval as Interval import qualified Database.PostgreSQL.Simple.ToField as PGTF import qualified Database.PostgreSQL.Simple.TypeInfo.Static as PS import qualified Database.PostgreSQL.Simple.Types as PG@@ -46,29 +48,30 @@ import Control.Monad.IO.Unlift (MonadIO (..)) import Control.Monad.Trans.Class (lift) import Data.Acquire (with)-import qualified Data.Attoparsec.ByteString.Char8 as P-import Data.Bits ((.&.))+import Data.Bits (toIntegralSized) import Data.ByteString (ByteString) import qualified Data.ByteString.Builder as BB-import qualified Data.ByteString.Char8 as B8-import Data.Char (ord) import Data.Conduit import qualified Data.Conduit.List as CL import Data.Data (Typeable) import Data.Either (partitionEithers)-import Data.Fixed (Fixed (..), Pico)+import Data.Fixed (Fixed (..), Micro, Pico) import Data.Function (on)-import Data.Int (Int64) import qualified Data.IntMap as I import Data.List as List (find, foldl', groupBy, sort) import qualified Data.List.NonEmpty as NEL import qualified Data.Map as Map import Data.Maybe-import Data.String.Conversions.Monomorphic (toStrictByteString) import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Encoding as T-import Data.Time (NominalDiffTime, localTimeToUTC, utc)+import Data.Time+    ( NominalDiffTime+    , localTimeToUTC+    , nominalDiffTimeToSeconds+    , secondsToNominalDiffTime+    , utc+    ) import Database.Persist.Sql import qualified Database.Persist.Sql.Util as Util @@ -165,7 +168,7 @@         , (k PS.time, convertPV PersistTimeOfDay)         , (k PS.timestamp, convertPV (PersistUTCTime . localTimeToUTC utc))         , (k PS.timestamptz, convertPV PersistUTCTime)-        , (k PS.interval, convertPV (PersistLiteralEscaped . pgIntervalToBs))+        , (k PS.interval, convertPV $ toPersistValue @Interval.Interval)         , (k PS.bit, convertPV PersistInt64)         , (k PS.varbit, convertPV PersistInt64)         , (k PS.numeric, convertPV PersistRational)@@ -195,7 +198,7 @@         , (1183, listOf PersistTimeOfDay)         , (1115, listOf PersistUTCTime)         , (1185, listOf PersistUTCTime)-        , (1187, listOf (PersistLiteralEscaped . pgIntervalToBs))+        , (1187, listOf $ toPersistValue @Interval.Interval)         , (1561, listOf PersistInt64)         , (1563, listOf PersistInt64)         , (1231, listOf PersistRational)@@ -233,113 +236,49 @@  -- | Represent Postgres interval using NominalDiffTime --+-- Note that this type cannot be losslessly round tripped through PostgreSQL.+-- For example the value @'PgInterval' 0.0000009@ will truncate extra+-- precision. And the value @'PgInterval'  9223372036854.775808@ will overflow.+-- Use the 'Interval.Interval' type if that is a problem for you.+-- -- @since 2.11.0.0 newtype PgInterval = PgInterval {getPgInterval :: NominalDiffTime}     deriving (Eq, Show) -pgIntervalToBs :: PgInterval -> ByteString-pgIntervalToBs = toStrictByteString . show . getPgInterval- instance PGTF.ToField PgInterval where-    toField (PgInterval t) = PGTF.toField t+    toField = PGTF.toField . pgIntervalToInterval  instance PGFF.FromField PgInterval where-    fromField f mdata =-        if PGFF.typeOid f /= PS.typoid PS.interval-            then PGFF.returnError PGFF.Incompatible f ""-            else case mdata of-                Nothing -> PGFF.returnError PGFF.UnexpectedNull f ""-                Just dat -> case P.parseOnly (nominalDiffTime <* P.endOfInput) dat of-                    Left msg -> PGFF.returnError PGFF.ConversionFailed f msg-                    Right t -> return $ PgInterval t-      where-        toPico :: Integer -> Pico-        toPico = MkFixed--        -- Taken from Database.PostgreSQL.Simple.Time.Internal.Parser-        twoDigits :: P.Parser Int-        twoDigits = do-            a <- P.digit-            b <- P.digit-            let-                c2d c = ord c .&. 15-            return $! c2d a * 10 + c2d b--        -- Taken from Database.PostgreSQL.Simple.Time.Internal.Parser-        seconds :: P.Parser Pico-        seconds = do-            real <- twoDigits-            mc <- P.peekChar-            case mc of-                Just '.' -> do-                    t <- P.anyChar *> P.takeWhile1 P.isDigit-                    return $! parsePicos (fromIntegral real) t-                _ -> return $! fromIntegral real-          where-            parsePicos :: Int64 -> B8.ByteString -> Pico-            parsePicos a0 t = toPico (fromIntegral (t' * 10 ^ n))-              where-                n = max 0 (12 - B8.length t)-                t' =-                    B8.foldl'-                        (\a c -> 10 * a + fromIntegral (ord c .&. 15))-                        a0-                        (B8.take 12 t)--        parseSign :: P.Parser Bool-        parseSign = P.choice [P.char '-' >> return True, return False]--        -- Db stores it in [-]HHH:MM:SS.[SSSS]-        -- For example, nominalDay is stored as 24:00:00-        interval :: P.Parser (Bool, Int, Int, Pico)-        interval = do-            s <- parseSign-            h <- P.decimal <* P.char ':'-            m <- twoDigits <* P.char ':'-            ss <- seconds-            if m < 60 && ss <= 60-                then return (s, h, m, ss)-                else fail "Invalid interval"--        nominalDiffTime :: P.Parser NominalDiffTime-        nominalDiffTime = do-            (s, h, m, ss) <- interval-            let-                pico = ss + 60 * (fromIntegral m) + 60 * 60 * (fromIntegral (abs h))-            return . fromRational . toRational $ if s then (-pico) else pico--fromPersistValueError-    :: Text-    -- ^ Haskell type, should match Haskell name exactly, e.g. "Int64"-    -> Text-    -- ^ Database type(s), should appear different from Haskell name, e.g. "integer" or "INT", not "Int".-    -> PersistValue-    -- ^ Incorrect value-    -> Text-    -- ^ Error message-fromPersistValueError haskellType databaseType received =-    T.concat-        [ "Failed to parse Haskell type `"-        , haskellType-        , "`; expected "-        , databaseType-        , " from database, but received: "-        , T.pack (show received)-        , ". Potential solution: Check that your database schema matches your Persistent model definitions."-        ]+    fromField f =+        maybe (PGFF.returnError PGFF.ConversionFailed f "invalid interval") pure+            . intervalToPgInterval+            <=< PGFF.fromField f  instance PersistField PgInterval where-    toPersistValue = PersistLiteralEscaped . pgIntervalToBs-    fromPersistValue (PersistLiteral_ DbSpecific bs) =-        fromPersistValue (PersistLiteralEscaped bs)-    fromPersistValue x@(PersistLiteral_ Escaped bs) =-        case P.parseOnly (P.signed P.rational <* P.char 's' <* P.endOfInput) bs of-            Left _ -> Left $ fromPersistValueError "PgInterval" "Interval" x-            Right i -> Right $ PgInterval i-    fromPersistValue x = Left $ fromPersistValueError "PgInterval" "Interval" x+    toPersistValue =+        toPersistValue+            . pgIntervalToInterval+    fromPersistValue =+        maybe (Left "invalid interval") pure+            . intervalToPgInterval+            <=< fromPersistValue  instance PersistFieldSql PgInterval where     sqlType _ = SqlOther "interval"++pgIntervalToInterval :: PgInterval -> Interval.Interval+pgIntervalToInterval =+    Interval.fromTimeSaturating mempty+        . getPgInterval++intervalToPgInterval :: Interval.Interval -> Maybe PgInterval+intervalToPgInterval interval =+    let+        (calendarDiffDays, nominalDiffTime) = Interval.intoTime interval+     in+        if calendarDiffDays == mempty+            then Just $ PgInterval nominalDiffTime+            else Nothing  -- | Indicates whether a Postgres Column is safe to drop. --
Database/Persist/Postgresql/JSON.hs view
@@ -3,25 +3,30 @@  -- | Filter operators for JSON values added to PostgreSQL 9.4 module Database.Persist.Postgresql.JSON-  ( (@>.)-  , (<@.)-  , (?.)-  , (?|.)-  , (?&.)-  , Value()-  ) where+    ( (@>.)+    , (<@.)+    , (?.)+    , (?|.)+    , (?&.)+    , Value ()+    ) where -import Data.Aeson (FromJSON, ToJSON, Value, encode, eitherDecodeStrict)+import Data.Aeson (FromJSON, ToJSON, Value, eitherDecodeStrict, encode) import qualified Data.ByteString.Lazy as BSL import Data.Proxy (Proxy) import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding as TE (encodeUtf8) -import Database.Persist (EntityField, Filter(..), PersistValue(..), PersistField(..), PersistFilter(..))-import Database.Persist.Sql (PersistFieldSql(..), SqlType(..))-import Database.Persist.Types (FilterValue(..))-+import Database.Persist+    ( EntityField+    , Filter (..)+    , PersistField (..)+    , PersistFilter (..)+    , PersistValue (..)+    )+import Database.Persist.Sql (PersistFieldSql (..), SqlType (..))+import Database.Persist.Types (FilterValue (..))  infix 4 @>., <@., ?., ?|., ?&. @@ -314,36 +319,36 @@ (?&.) :: EntityField record Value -> [Text] -> Filter record (?&.) field = jsonFilter " ??& " field . PostgresArray -jsonFilter :: PersistField a => Text -> EntityField record Value -> a -> Filter record+jsonFilter+    :: (PersistField a) => Text -> EntityField record Value -> a -> Filter record jsonFilter op field a = Filter field (UnsafeValue a) $ BackendSpecificFilter op - ----------------- -- AESON VALUE -- -----------------  instance PersistField Value where-  toPersistValue = toPersistValueJsonB-  fromPersistValue = fromPersistValueJsonB+    toPersistValue = toPersistValueJsonB+    fromPersistValue = fromPersistValueJsonB  instance PersistFieldSql Value where-  sqlType = sqlTypeJsonB+    sqlType = sqlTypeJsonB  -- FIXME: PersistText might be a bit more efficient, -- but needs testing/profiling before changing it. -- (When entering into the DB the type isn't as important as fromPersistValue)-toPersistValueJsonB :: ToJSON a => a -> PersistValue+toPersistValueJsonB :: (ToJSON a) => a -> PersistValue toPersistValueJsonB = PersistLiteralEscaped . BSL.toStrict . encode -fromPersistValueJsonB :: FromJSON a => PersistValue -> Either Text a+fromPersistValueJsonB :: (FromJSON a) => PersistValue -> Either Text a fromPersistValueJsonB (PersistText t) =     case eitherDecodeStrict $ TE.encodeUtf8 t of-      Left str -> Left $ fromPersistValueParseError "FromJSON" t $ T.pack str-      Right v -> Right v+        Left str -> Left $ fromPersistValueParseError "FromJSON" t $ T.pack str+        Right v -> Right v fromPersistValueJsonB (PersistByteString bs) =     case eitherDecodeStrict bs of-      Left str -> Left $ fromPersistValueParseError "FromJSON" bs $ T.pack str-      Right v -> Right v+        Left str -> Left $ fromPersistValueParseError "FromJSON" bs $ T.pack str+        Right v -> Right v fromPersistValueJsonB x = Left $ fromPersistValueError "FromJSON" "string or bytea" x  -- Constraints on the type might not be necessary,@@ -351,38 +356,49 @@ sqlTypeJsonB :: (ToJSON a, FromJSON a) => Proxy a -> SqlType sqlTypeJsonB _ = SqlOther "JSONB" --fromPersistValueError :: Text -- ^ Haskell type, should match Haskell name exactly, e.g. "Int64"-                      -> Text -- ^ Database type(s), should appear different from Haskell name, e.g. "integer" or "INT", not "Int".-                      -> PersistValue -- ^ Incorrect value-                      -> Text -- ^ Error message-fromPersistValueError haskellType databaseType received = T.concat-    [ "Failed to parse Haskell type `"-    , haskellType-    , "`; expected "-    , databaseType-    , " from database, but received: "-    , T.pack (show received)-    , ". Potential solution: Check that your database schema matches your Persistent model definitions."-    ]+fromPersistValueError+    :: Text+    -- ^ Haskell type, should match Haskell name exactly, e.g. "Int64"+    -> Text+    -- ^ Database type(s), should appear different from Haskell name, e.g. "integer" or "INT", not "Int".+    -> PersistValue+    -- ^ Incorrect value+    -> Text+    -- ^ Error message+fromPersistValueError haskellType databaseType received =+    T.concat+        [ "Failed to parse Haskell type `"+        , haskellType+        , "`; expected "+        , databaseType+        , " from database, but received: "+        , T.pack (show received)+        , ". Potential solution: Check that your database schema matches your Persistent model definitions."+        ] -fromPersistValueParseError :: (Show a)-                           => Text -- ^ Haskell type, should match Haskell name exactly, e.g. "Int64"-                           -> a -- ^ Received value-                           -> Text -- ^ Additional error-                           -> Text -- ^ Error message-fromPersistValueParseError haskellType received err = T.concat-    [ "Failed to parse Haskell type `"-    , haskellType-    , "`, but received "-    , T.pack (show received)-    , " | with error: "-    , err-    ]+fromPersistValueParseError+    :: (Show a)+    => Text+    -- ^ Haskell type, should match Haskell name exactly, e.g. "Int64"+    -> a+    -- ^ Received value+    -> Text+    -- ^ Additional error+    -> Text+    -- ^ Error message+fromPersistValueParseError haskellType received err =+    T.concat+        [ "Failed to parse Haskell type `"+        , haskellType+        , "`, but received "+        , T.pack (show received)+        , " | with error: "+        , err+        ]  newtype PostgresArray a = PostgresArray [a] -instance PersistField a => PersistField (PostgresArray a) where-  toPersistValue (PostgresArray ts) = PersistArray $ toPersistValue <$> ts-  fromPersistValue (PersistArray as) = PostgresArray <$> traverse fromPersistValue as-  fromPersistValue wat = Left $ fromPersistValueError "PostgresArray" "array" wat+instance (PersistField a) => PersistField (PostgresArray a) where+    toPersistValue (PostgresArray ts) = PersistArray $ toPersistValue <$> ts+    fromPersistValue (PersistArray as) = PostgresArray <$> traverse fromPersistValue as+    fromPersistValue wat = Left $ fromPersistValueError "PostgresArray" "array" wat
conn-killed/Main.hs view
@@ -1,7 +1,10 @@-{-# LANGUAGE ScopedTypeVariables, StandaloneDeriving, GeneralizedNewtypeDeriving, DerivingStrategies #-}-{-# LANGUAGE OverloadedStrings, QuantifiedConstraints #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuantifiedConstraints #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeApplications #-}-{-# language OverloadedStrings #-}  -- | This executable is a test of the issue raised in #1199. module Main where@@ -9,90 +12,104 @@ import Prelude hiding (show) import qualified Prelude -import qualified Data.Text as Text-import  Control.Monad.IO.Class-import  qualified Control.Monad as Monad-import qualified UnliftIO.Concurrent as Concurrent-import qualified UnliftIO.Exception as Exception-import qualified Database.Persist as Persist-import qualified Database.Persist.Sql as Persist-import qualified Database.Persist.Postgresql as Persist-import qualified Control.Monad.Logger as Logger+import qualified Control.Monad as Monad+import Control.Monad.IO.Class import Control.Monad.Logger+import qualified Control.Monad.Logger as Logger+import Control.Monad.Trans+import Control.Monad.Trans.Reader import qualified Data.ByteString as BS+import Data.Coerce import qualified Data.Pool as Pool+import qualified Data.Text as Text import Data.Time+import qualified Database.Persist as Persist+import qualified Database.Persist.Postgresql as Persist+import qualified Database.Persist.Sql as Persist import UnliftIO-import Data.Coerce-import Control.Monad.Trans.Reader-import Control.Monad.Trans+import qualified UnliftIO.Concurrent as Concurrent+import qualified UnliftIO.Exception as Exception -newtype LogPrefixT m a = LogPrefixT { runLogPrefixT :: ReaderT LogStr m a }+newtype LogPrefixT m a = LogPrefixT {runLogPrefixT :: ReaderT LogStr m a}     deriving newtype         (Functor, Applicative, Monad, MonadIO, MonadTrans) -instance MonadLogger m => MonadLogger (LogPrefixT m) where+instance (MonadLogger m) => MonadLogger (LogPrefixT m) where     monadLoggerLog loc src lvl msg = LogPrefixT $ ReaderT $ \prefix ->         monadLoggerLog loc src lvl (toLogStr prefix <> toLogStr msg) -deriving newtype instance (forall a b. Coercible a b => Coercible (m a) (m b), MonadUnliftIO m) => MonadUnliftIO (LogPrefixT m)+deriving newtype instance+    (forall a b. (Coercible a b) => Coercible (m a) (m b), MonadUnliftIO m)+    => MonadUnliftIO (LogPrefixT m)  prefixLogs :: Text.Text -> LogPrefixT m a -> m a prefixLogs prefix =     flip runReaderT (toLogStr $! mconcat ["[", prefix, "] "]) . runLogPrefixT  infixr 5 `prefixLogs`-show :: Show a => a -> Text.Text+show :: (Show a) => a -> Text.Text show = Text.pack . Prelude.show  main :: IO ()-main = runStdoutLoggingT $ Concurrent.myThreadId >>= \tid -> prefixLogs (show tid) $ do--  -- I started a postgres server with:-  -- docker run --rm --name some-postgres -p 5432:5432 -e POSTGRES_PASSWORD=secret postgres-  pool <- Logger.runNoLoggingT $ Persist.createPostgresqlPool "postgresql://postgres:secret@localhost:5433/postgres" 1+main =+    runStdoutLoggingT $+        Concurrent.myThreadId >>= \tid -> prefixLogs (show tid) $ do+            -- I started a postgres server with:+            -- docker run --rm --name some-postgres -p 5432:5432 -e POSTGRES_PASSWORD=secret postgres+            pool <-+                Logger.runNoLoggingT $+                    Persist.createPostgresqlPool+                        "postgresql://postgres:secret@localhost:5433/postgres"+                        1 -  logInfoN "creating table..."-  Monad.void $ liftIO $ createTableFoo pool+            logInfoN "creating table..."+            Monad.void $ liftIO $ createTableFoo pool -  liftIO getCurrentTime >>= \now ->-    simulateFailedLongRunningPostgresCall pool+            liftIO getCurrentTime >>= \now ->+                simulateFailedLongRunningPostgresCall pool -  -- logInfoN "destroying resources"-  -- liftIO $ Pool.destroyAllResources pool+            -- logInfoN "destroying resources"+            -- liftIO $ Pool.destroyAllResources pool -  logInfoN "pg_sleep"-  result :: Either Exception.SomeException [Persist.Single (Maybe String)] <--    Exception.try . (liftIO . (flip Persist.runSqlPersistMPool) pool) $ do-        Persist.rawSql @(Persist.Single (Maybe String)) "select pg_sleep(2)" []+            logInfoN "pg_sleep"+            result :: Either Exception.SomeException [Persist.Single (Maybe String)] <-+                Exception.try . (liftIO . (flip Persist.runSqlPersistMPool) pool) $ do+                    Persist.rawSql @(Persist.Single (Maybe String)) "select pg_sleep(2)" [] -  -- when we try the above we get back:-  -- 'result: Left libpq: failed (another command is already in progress'-  -- this is because the connection went back into the pool before it was ready-  -- or perhaps it should have been destroyed and a new connection created and put into the pool?-  logInfoN $ "result: " <> show result+            -- when we try the above we get back:+            -- 'result: Left libpq: failed (another command is already in progress'+            -- this is because the connection went back into the pool before it was ready+            -- or perhaps it should have been destroyed and a new connection created and put into the pool?+            logInfoN $ "result: " <> show result  createTableFoo :: Pool.Pool Persist.SqlBackend -> IO () createTableFoo pool = (flip Persist.runSqlPersistMPool) pool $ do-  Persist.rawExecute "CREATE table if not exists foo(id int);" []+    Persist.rawExecute "CREATE table if not exists foo(id int);" []  simulateFailedLongRunningPostgresCall-    :: (MonadLogger m, MonadUnliftIO m, forall a b. Coercible a b => Coercible (m a) (m b)) => Pool.Pool Persist.SqlBackend -> m ()+    :: ( MonadLogger m+       , MonadUnliftIO m+       , forall a b. (Coercible a b) => Coercible (m a) (m b)+       )+    => Pool.Pool Persist.SqlBackend -> m () simulateFailedLongRunningPostgresCall pool = do-  threadId <- Concurrent.forkIO-    $ (do-        me <- Concurrent.myThreadId-        prefixLogs (show me) $ do-            let numThings :: Int = 100000000-            logInfoN $ "start inserting " <> show numThings <> " things"+    threadId <-+        Concurrent.forkIO $+            ( do+                me <- Concurrent.myThreadId+                prefixLogs (show me) $ do+                    let+                        numThings :: Int = 100000000+                    logInfoN $ "start inserting " <> show numThings <> " things" -            (`Persist.runSqlPool` pool) $ do-                logInfoN "inside of thing"-                Monad.forM_ [1 .. numThings] $ \i -> do-                    Monad.when (i `mod` 1000 == 0) $-                        logInfoN $ "Thing #: " <> show i-                    Persist.rawExecute "insert into foo values(1);" []-      )-  Concurrent.threadDelay 1000000-  Monad.void $ Concurrent.killThread threadId-  logInfoN "killed thread"+                    (`Persist.runSqlPool` pool) $ do+                        logInfoN "inside of thing"+                        Monad.forM_ [1 .. numThings] $ \i -> do+                            Monad.when (i `mod` 1000 == 0) $+                                logInfoN $+                                    "Thing #: " <> show i+                            Persist.rawExecute "insert into foo values(1);" []+            )+    Concurrent.threadDelay 1000000+    Monad.void $ Concurrent.killThread threadId+    logInfoN "killed thread"
persistent-postgresql.cabal view
@@ -1,5 +1,5 @@ name:               persistent-postgresql-version:            2.13.7.0+version:            2.14.0.0 license:            MIT license-file:       LICENSE author:             Felipe Lessa, Michael Snoyman <michael@snoyman.com>@@ -28,6 +28,7 @@     , persistent          >=2.13.3  && <3     , postgresql-libpq    >=0.9.4.2 && <0.12     , postgresql-simple   >=0.6.1   && <0.8+    , postgresql-simple-interval >=1 && < 1.1     , resource-pool     , resourcet           >=1.1.9     , string-conversions@@ -82,6 +83,7 @@     , persistent-postgresql     , persistent-qq     , persistent-test+    , postgresql-simple-interval     , QuickCheck     , quickcheck-instances     , resourcet
test/ArrayAggTest.hs view
@@ -1,16 +1,18 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE DataKinds, FlexibleContexts #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-}-{-# LANGUAGE UndecidableInstances #-} -- FIXME-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}+-- FIXME+{-# LANGUAGE UndecidableInstances #-}  module ArrayAggTest where @@ -23,12 +25,16 @@ import PersistentTestModels import PgInit -share [mkPersist persistSettings,  mkMigrate "jsonTestMigrate"] [persistLowerCase|+share+    [mkPersist persistSettings, mkMigrate "jsonTestMigrate"]+    [persistLowerCase|   TestValue     json Value |] -cleanDB :: (BaseBackend backend ~ SqlBackend, PersistQueryWrite backend, MonadIO m) => ReaderT backend m ()+cleanDB+    :: (BaseBackend backend ~ SqlBackend, PersistQueryWrite backend, MonadIO m)+    => ReaderT backend m () cleanDB = deleteWhere ([] :: [Filter TestValue])  emptyArr :: Value@@ -36,22 +42,31 @@  specs :: Spec specs = do-  describe "rawSql/array_agg" $ do-    let runArrayAggTest :: (PersistField [a], Ord a, Show a) => Text -> [a] -> Assertion-        runArrayAggTest dbField expected = runConnAssert $ do-          void $ insertMany-            [ UserPT "a" $ Just "b"-            , UserPT "c" $ Just "d"-            , UserPT "e"   Nothing-            , UserPT "g" $ Just "h" ]-          escape <- getEscapeRawNameFunction-          let query = T.concat [ "SELECT array_agg(", escape dbField, ") "-                               , "FROM ", escape "UserPT"-                               ]-          [Single xs] <- rawSql query []-          liftIO $ sort xs @?= expected+    describe "rawSql/array_agg" $ do+        let+            runArrayAggTest :: (PersistField [a], Ord a, Show a) => Text -> [a] -> Assertion+            runArrayAggTest dbField expected = runConnAssert $ do+                void $+                    insertMany+                        [ UserPT "a" $ Just "b"+                        , UserPT "c" $ Just "d"+                        , UserPT "e" Nothing+                        , UserPT "g" $ Just "h"+                        ]+                escape <- getEscapeRawNameFunction+                let+                    query =+                        T.concat+                            [ "SELECT array_agg("+                            , escape dbField+                            , ") "+                            , "FROM "+                            , escape "UserPT"+                            ]+                [Single xs] <- rawSql query []+                liftIO $ sort xs @?= expected -    it "works for [Text]"       $ do-        runArrayAggTest "ident"    ["a", "c", "e", "g" :: Text]-    it "works for [Maybe Text]" $ do-        runArrayAggTest "password" [Nothing, Just "b", Just "d", Just "h" :: Maybe Text]+        it "works for [Text]" $ do+            runArrayAggTest "ident" ["a", "c", "e", "g" :: Text]+        it "works for [Maybe Text]" $ do+            runArrayAggTest "password" [Nothing, Just "b", Just "d", Just "h" :: Maybe Text]
test/CustomConstraintTest.hs view
@@ -1,21 +1,26 @@-{-# LANGUAGE EmptyDataDecls             #-}-{-# LANGUAGE FlexibleContexts           #-}-{-# LANGUAGE GADTs, DataKinds, FlexibleInstances                      #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE EmptyDataDecls #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE MultiParamTypeClasses      #-}-{-# LANGUAGE OverloadedStrings          #-}-{-# LANGUAGE QuasiQuotes                #-}-{-# LANGUAGE TemplateHaskell            #-}-{-# LANGUAGE TypeFamilies               #-}-{-# LANGUAGE UndecidableInstances       #-}-{-# LANGUAGE DerivingStrategies         #-}-{-# LANGUAGE StandaloneDeriving         #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+ module CustomConstraintTest where -import PgInit import qualified Data.Text as T+import PgInit -share [mkPersist sqlSettings, mkMigrate "customConstraintMigrate"] [persistLowerCase|+share+    [mkPersist sqlSettings, mkMigrate "customConstraintMigrate"]+    [persistLowerCase| CustomConstraint1     some_field Text     deriving Show@@ -33,34 +38,44 @@  specs :: Spec specs = do-  describe "custom constraint used in migration" $ do-    it "custom constraint is actually created" $ runConnAssert $ do-      void $ runMigrationSilent customConstraintMigrate-      void $ runMigrationSilent customConstraintMigrate -- run a second time to ensure the constraint isn't dropped-      let query = T.concat ["SELECT DISTINCT COUNT(*) "-                           ,"FROM information_schema.constraint_column_usage ccu, "-                           ,"information_schema.key_column_usage kcu, "-                           ,"information_schema.table_constraints tc "-                           ,"WHERE tc.constraint_type='FOREIGN KEY' "-                           ,"AND kcu.constraint_name=tc.constraint_name "-                           ,"AND ccu.constraint_name=kcu.constraint_name "-                           ,"AND kcu.ordinal_position=1 "-                           ,"AND ccu.table_name=? "-                           ,"AND ccu.column_name=? "-                           ,"AND kcu.table_name=? "-                           ,"AND kcu.column_name=? "-                           ,"AND tc.constraint_name=?"]-      [Single exists_] <- rawSql query [PersistText "custom_constraint1"-                                      ,PersistText "id"-                                      ,PersistText "custom_constraint2"-                                      ,PersistText "cc_id"-                                      ,PersistText "custom_constraint"]-      liftIO $ 1 @?= (exists_ :: Int)+    describe "custom constraint used in migration" $ do+        it "custom constraint is actually created" $ runConnAssert $ do+            void $ runMigrationSilent customConstraintMigrate+            void $ runMigrationSilent customConstraintMigrate -- run a second time to ensure the constraint isn't dropped+            let+                query =+                    T.concat+                        [ "SELECT DISTINCT COUNT(*) "+                        , "FROM information_schema.constraint_column_usage ccu, "+                        , "information_schema.key_column_usage kcu, "+                        , "information_schema.table_constraints tc "+                        , "WHERE tc.constraint_type='FOREIGN KEY' "+                        , "AND kcu.constraint_name=tc.constraint_name "+                        , "AND ccu.constraint_name=kcu.constraint_name "+                        , "AND kcu.ordinal_position=1 "+                        , "AND ccu.table_name=? "+                        , "AND ccu.column_name=? "+                        , "AND kcu.table_name=? "+                        , "AND kcu.column_name=? "+                        , "AND tc.constraint_name=?"+                        ]+            [Single exists_] <-+                rawSql+                    query+                    [ PersistText "custom_constraint1"+                    , PersistText "id"+                    , PersistText "custom_constraint2"+                    , PersistText "cc_id"+                    , PersistText "custom_constraint"+                    ]+            liftIO $ 1 @?= (exists_ :: Int) -    it "allows multiple constraints on a single column" $ runConnAssert $ do-      void $ runMigrationSilent customConstraintMigrate-      -- | Here we add another foreign key on the same column where the default one already exists. In practice, this could be a compound key with another field.-      rawExecute "ALTER TABLE \"custom_constraint3\" ADD CONSTRAINT \"extra_constraint\" FOREIGN KEY(\"cc_id1\") REFERENCES \"custom_constraint1\"(\"id\")" []-      -- | This is where the error is thrown in `getColumn`-      void $ getMigration customConstraintMigrate-      pure ()+        it "allows multiple constraints on a single column" $ runConnAssert $ do+            void $ runMigrationSilent customConstraintMigrate+            -- \| Here we add another foreign key on the same column where the default one already exists. In practice, this could be a compound key with another field.+            rawExecute+                "ALTER TABLE \"custom_constraint3\" ADD CONSTRAINT \"extra_constraint\" FOREIGN KEY(\"cc_id1\") REFERENCES \"custom_constraint1\"(\"id\")"+                []+            -- \| This is where the error is thrown in `getColumn`+            void $ getMigration customConstraintMigrate+            pure ()
test/EquivalentTypeTestPostgres.hs view
@@ -1,15 +1,16 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE DataKinds, FlexibleInstances #-}+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -Wno-unused-top-binds #-}  module EquivalentTypeTestPostgres (specs) where@@ -20,7 +21,9 @@ import Database.Persist.TH import PgInit -share [mkPersist sqlSettings, mkMigrate "migrateAll1"] [persistLowerCase|+share+    [mkPersist sqlSettings, mkMigrate "migrateAll1"]+    [persistLowerCase| EquivalentType sql=equivalent_types     field1 Int    sqltype=bigint     field2 T.Text sqltype=text@@ -28,7 +31,9 @@     deriving Eq Show |] -share [mkPersist sqlSettings, mkMigrate "migrateAll2"] [persistLowerCase|+share+    [mkPersist sqlSettings, mkMigrate "migrateAll2"]+    [persistLowerCase| EquivalentType2 sql=equivalent_types     field1 Int    sqltype=int8     field2 T.Text@@ -39,9 +44,9 @@ specs :: Spec specs = describe "doesn't migrate equivalent types" $ do     it "works" $ asIO $ runResourceT $ runConn $ do-         _ <- rawExecute "DROP DOMAIN IF EXISTS us_postal_code CASCADE" []-        _ <- rawExecute "CREATE DOMAIN us_postal_code AS TEXT CHECK(VALUE ~ '^\\d{5}$')" []+        _ <-+            rawExecute "CREATE DOMAIN us_postal_code AS TEXT CHECK(VALUE ~ '^\\d{5}$')" []          _ <- runMigrationSilent migrateAll1         xs <- getMigration migrateAll2
test/ImplicitUuidSpec.hs view
@@ -46,7 +46,8 @@     rawExecute "DROP TABLE with_def_uuid;" []     runMigration implicitUuidMigrate -itDb :: String -> SqlPersistT (LoggingT (ResourceT IO)) a -> SpecWith (Arg (IO ()))+itDb+    :: String -> SqlPersistT (LoggingT (ResourceT IO)) a -> SpecWith (Arg (IO ())) itDb msg action = it msg $ runConnAssert $ void action  pass :: IO ()@@ -56,10 +57,12 @@ spec = describe "ImplicitUuidSpec" $ before_ wipe $ do     describe "WithDefUuidKey" $ do         it "works on UUIDs" $ do-            let withDefUuidKey = WithDefUuidKey (UUID "Hello")+            let+                withDefUuidKey = WithDefUuidKey (UUID "Hello")             pass     describe "getEntityId" $ do-        let Just idField = getEntityIdField (entityDef (Proxy @WithDefUuid))+        let+            Just idField = getEntityIdField (entityDef (Proxy @WithDefUuid))         it "has a UUID SqlType" $ asIO $ do             fieldSqlType idField `shouldBe` SqlOther "UUID"         it "is an implicit ID column" $ asIO $ do@@ -67,10 +70,12 @@      describe "insert" $ do         itDb "successfully has a default" $ do-            let matt = WithDefUuid-                    { withDefUuidName =-                        "Matt"-                    }+            let+                matt =+                    WithDefUuid+                        { withDefUuidName =+                            "Matt"+                        }             k <- insert matt             mrec <- get k             mrec `shouldBe` Just matt
test/JSONTest.hs view
@@ -1,680 +1,760 @@-{-# LANGUAGE ExistentialQuantification #-}-{-# language DataKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--module JSONTest where--import Control.Monad.IO.Class (MonadIO)-import Data.Aeson hiding (Key)-import qualified Data.Vector as V (fromList)-import Test.HUnit (assertBool)-import Test.Hspec.Expectations ()--import Database.Persist-import Database.Persist.Postgresql.JSON--import PgInit---share [mkPersist persistSettings,  mkMigrate "jsonTestMigrate"] [persistLowerCase|-  TestValue-    json Value-    deriving Show-|]--cleanDB :: (BaseBackend backend ~ SqlBackend, PersistQueryWrite backend, MonadIO m)-        => ReaderT backend m ()-cleanDB = deleteWhere ([] :: [Filter TestValue])--emptyArr :: Value-emptyArr = toJSON ([] :: [Value])--insert' :: (MonadIO m, PersistStoreWrite backend, BaseBackend backend ~ SqlBackend)-        => Value -> ReaderT backend m (Key TestValue)-insert' = insert . TestValue---matchKeys :: (Show record, Show (Key record), MonadIO m, Eq (Key record))-          => [Key record] -> [Entity record] -> m ()-matchKeys ys xs = do-  msg1 `assertBoolIO` (xLen == yLen)-  forM_ ys $ \y -> msg2 y `assertBoolIO` (y `elem` ks)-    where ks = entityKey <$> xs-          xLen = length xs-          yLen = length ys-          msg1 = mconcat-              [ "\nexpected: ", show yLen-              , "\n but got: ", show xLen-              , "\n[xs: ", show xs, "]"-              , "\n[ys: ", show ys, "]"-              ]-          msg2 y = mconcat-              [ "key \"", show y-              , "\" not in result:\n  ", show ks-              ]--setup :: IO TestKeys-setup = asIO $ runConn_ $ do-  void $ runMigrationSilent jsonTestMigrate-  testKeys--teardown :: IO ()-teardown = asIO $ runConn_ $ do-    cleanDB--shouldBeIO :: (Show a, Eq a, MonadIO m) => a -> a -> m ()-shouldBeIO x y = liftIO $ shouldBe x y--assertBoolIO :: MonadIO m => String -> Bool -> m ()-assertBoolIO s b = liftIO $ assertBool s b--testKeys :: (Monad m, MonadIO m) => ReaderT SqlBackend m TestKeys-testKeys = do-    nullK <- insert' Null--    boolTK <- insert' $ Bool True-    boolFK <- insert' $ toJSON False--    num0K <- insert' $ Number 0-    num1K <- insert' $ Number 1-    numBigK <- insert' $ toJSON (1234567890 :: Int)-    numFloatK <- insert' $ Number 0.0-    numSmallK <- insert' $ Number 0.0000000000000000123-    numFloat2K <- insert' $ Number 1.5-    -- numBigFloatK will turn into 9876543210.123457 because JSON-    numBigFloatK <- insert' $ toJSON (9876543210.123456789 :: Double)--    strNullK <- insert' $ String ""-    strObjK <- insert' $ String "{}"-    strArrK <- insert' $ String "[]"-    strAK <- insert' $ String "a"-    strTestK <- insert' $ toJSON ("testing" :: Text)-    str2K <- insert' $ String "2"-    strFloatK <- insert' $ String "0.45876"--    arrNullK <- insert' $ Array $ V.fromList []-    arrListK <- insert' $ toJSON [emptyArr,emptyArr,toJSON [emptyArr,emptyArr]]-    arrList2K <- insert' $ toJSON [emptyArr,toJSON [Number 3,Bool False]-                                  ,toJSON [emptyArr,toJSON [Object mempty]]-                                  ]-    arrFilledK <- insert' $ toJSON [Null, Number 4, String "b"-                                   ,Object mempty, emptyArr-                                   ,object [ "test" .= [Null], "test2" .= String "yes"]-                                   ]-    arrList3K <- insert' $ toJSON [toJSON [String "a"], Number 1]-    arrList4K <- insert' $ toJSON [String "a", String "b", String "c", String "d"]--    objNullK <- insert' $ Object mempty-    objTestK <- insert' $ object ["test" .= Null, "test1" .= String "no"]-    objDeepK <- insert' $ object ["c" .= Number 24.986, "foo" .= object ["deep1" .= Bool True]]-    objEmptyK <- insert' $ object ["" .= Number 9001]-    objFullK  <- insert' $ object ["a" .= Number 1, "b" .= Number 2-                                  ,"c" .= Number 3, "d" .= Number 4-                                  ]-    return TestKeys{..}--data TestKeys =-  TestKeys { nullK :: Key TestValue-             , boolTK :: Key TestValue-             , boolFK :: Key TestValue-             , num0K :: Key TestValue-             , num1K :: Key TestValue-             , numBigK :: Key TestValue-             , numFloatK :: Key TestValue-             , numSmallK :: Key TestValue-             , numFloat2K :: Key TestValue-             , numBigFloatK :: Key TestValue-             , strNullK :: Key TestValue-             , strObjK :: Key TestValue-             , strArrK :: Key TestValue-             , strAK :: Key TestValue-             , strTestK :: Key TestValue-             , str2K :: Key TestValue-             , strFloatK :: Key TestValue-             , arrNullK :: Key TestValue-             , arrListK :: Key TestValue-             , arrList2K :: Key TestValue-             , arrFilledK :: Key TestValue-             , objNullK :: Key TestValue-             , objTestK :: Key TestValue-             , objDeepK :: Key TestValue-             , arrList3K  :: Key TestValue-             , arrList4K  :: Key TestValue-             , objEmptyK  :: Key TestValue-             , objFullK   :: Key TestValue-             } deriving (Eq, Ord, Show)--specs :: Spec-specs = afterAll_ teardown $ do-  beforeAll setup $ do-    describe "Testing JSON operators" $ do-      describe "@>. object queries" $ do-        it "matches an empty Object with any object" $-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. Object mempty] []-            [objNullK, objTestK, objDeepK, objEmptyK, objFullK] `matchKeys`  vals--        it "matches a subset of object properties" $-            -- {test: null, test1: no} @>. {test: null} == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. object ["test" .= Null]] []-            [objTestK] `matchKeys`  vals--        it "matches a nested object against an empty object at the same key" $-            -- {c: 24.986, foo: {deep1: true}} @>. {foo: {}} == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. object ["foo" .= object []]] []-            [objDeepK] `matchKeys`  vals--        it "doesn't match a nested object against a string at the same key" $-            -- {c: 24.986, foo: {deep1: true}} @>. {foo: nope} == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. object ["foo" .= String "nope"]] []-            [] `matchKeys`  vals--        it "matches a nested object when the query object is identical" $-            -- {c: 24.986, foo: {deep1: true}} @>. {foo: {deep1: true}} == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. (object ["foo" .= object ["deep1" .= True]])] []-            [objDeepK] `matchKeys`  vals--        it "doesn't match a nested object when queried with that exact object" $-            -- {c: 24.986, foo: {deep1: true}} @>. {deep1: true} == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. object ["deep1" .= True]] []-            [] `matchKeys`  vals--      describe "@>. array queries" $ do-        it "matches an empty Array with any list" $-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. emptyArr] []-            [arrNullK, arrListK, arrList2K, arrFilledK, arrList3K, arrList4K] `matchKeys`  vals--        it "matches list when queried with subset (1 item)" $-            -- [null, 4, 'b', {}, [], {test: [null], test2: 'yes'}] @>. [4] == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. toJSON [4 :: Int]] []-            [arrFilledK] `matchKeys` vals--        it "matches list when queried with subset (2 items)" $-            -- [null, 4, 'b', {}, [], {test: [null], test2: 'yes'}] @>. [null,'b'] == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. toJSON [Null, String "b"]] []-            [arrFilledK] `matchKeys` vals--        it "doesn't match list when queried with intersecting list (1 match, 1 diff)" $-            -- [null, 4, 'b', {}, [], {test: [null], test2: 'yes'}] @>. [null,'d'] == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. toJSON [emptyArr, String "d"]] []-            [] `matchKeys` vals--        it "matches list when queried with same list in different order" $-            -- [null, 4, 'b', {}, [], {test: [null], test2: 'yes'}] @>.-            -- [[],'b',{test: [null],test2: 'yes'},4,null,{}] == True-          \TestKeys {..} -> runConnAssert $ do-            let queryList =-                  toJSON [ emptyArr, String "b"-                         , object [ "test" .= [Null], "test2" .= String "yes"]-                         , Number 4, Null, Object mempty ]--            vals <- selectList [TestValueJson @>. queryList ] []-            [arrFilledK] `matchKeys` vals--        it "doesn't match list when queried with same list + 1 item" $-            -- [null,4,'b',{},[],{test:[null],test2:'yes'}] @>.-            -- [null,4,'b',{},[],{test:[null],test2: 'yes'}, false] == False-          \TestKeys {..} -> runConnAssert $ do-            let testList =-                  toJSON [ Null, Number 4, String "b", Object mempty, emptyArr-                         , object [ "test" .= [Null], "test2" .= String "yes"]-                         , Bool False ]--            vals <- selectList [TestValueJson @>. testList]  []-            [] `matchKeys` vals--        it "matches list when it shares an empty object with the query list" $-            -- [null,4,'b',{},[],{test: [null],test2: 'yes'}] @>. [{}] == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. toJSON [Object mempty]] []-            [arrFilledK] `matchKeys` vals--        it "matches list with nested list, when queried with an empty nested list" $-            -- [null,4,'b',{},[],{test:[null],test2:'yes'}] @>. [{test:[]}] == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. toJSON [object ["test" .= emptyArr]]] []-            [arrFilledK] `matchKeys` vals--        it "doesn't match list with nested list, when queried with a diff. nested list" $-            -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @>.-            -- [{"test1":[null]}]  == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. toJSON [object ["test1" .= [Null]]]] []-            [] `matchKeys` vals--        it "matches many nested lists when queried with empty nested list" $-            -- [[],[],[[],[]]]                                  @>. [[]] == True-            -- [[],[3,false],[[],[{}]]]                         @>. [[]] == True-            -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @>. [[]] == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. toJSON [emptyArr]] []-            [arrListK,arrList2K,arrFilledK, arrList3K] `matchKeys` vals--        it "matches nested list when queried with a subset of that list" $-            -- [[],[3,false],[[],[{}]]] @>. [[3]] == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. toJSON [[3 :: Int]]] []-            [arrList2K] `matchKeys` vals--        it "doesn't match nested list againts a partial intersection of that list" $-            -- [[],[3,false],[[],[{}]]] @>. [[true,3]] == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. toJSON [[Bool True, Number 3]]] []-            [] `matchKeys` vals--        it "matches list when queried with raw number contained in the list" $-            -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @>. 4 == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. Number 4] []-            [arrFilledK] `matchKeys` vals--        it "doesn't match list when queried with raw value not contained in the list" $-            -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @>. 99 == False-          \TestKeys {..} -> runConnAssert $ do-          vals <- selectList [TestValueJson @>. Number 99] []-          [] `matchKeys` vals--        it "matches list when queried with raw string contained in the list" $-            -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @>. "b" == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. String "b"] []-            [arrFilledK, arrList4K] `matchKeys` vals--        it "doesn't match list with empty object when queried with \"{}\" " $-            -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @>. "{}" == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. String "{}"] []-            [strObjK] `matchKeys` vals--        it "doesnt match list with nested object when queried with object (not in list)" $-            -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @>.-            -- {"test":[null],"test2":"yes"} == False-          \TestKeys {..} -> runConnAssert $ do-            let queryObject = object [ "test" .= [Null], "test2" .= String "yes"]-            vals <- selectList [TestValueJson @>. queryObject ] []-            [] `matchKeys` vals--      describe "@>. string queries" $ do-        it "matches identical strings" $-            -- "testing" @>. "testing" == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. String "testing"] []-            [strTestK] `matchKeys` vals--        it "doesnt match case insensitive" $-            -- "testing" @>. "Testing" == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. String "Testing"] []-            [] `matchKeys` vals--        it "doesn't match substrings" $-            -- "testing" @>. "test" == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. String "test"] []-            [] `matchKeys` vals--        it "doesn't match strings with object keys" $-            -- "testing" @>. {"testing":1} == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. object ["testing" .= Number 1]] []-            [] `matchKeys` vals--      describe "@>. number queries" $ do-        it "matches identical numbers" $-            -- 1   @>. 1 == True-            -- [1] @>. 1 == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. toJSON (1 :: Int)] []-            [num1K, arrList3K] `matchKeys` vals--        it "matches numbers when queried with float" $-            -- 0 @>. 0.0 == True-            -- 0.0 @>. 0.0 == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. toJSON (0.0 :: Double)] []-            [num0K,numFloatK] `matchKeys` vals--        it "does not match numbers when queried with a substring of that number" $-            -- 1234567890 @>. 123456789 == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. toJSON (123456789 :: Int)] []-            [] `matchKeys` vals--        it "does not match number when queried with different number" $-            -- 1234567890 @>. 234567890 == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. toJSON (234567890 :: Int)] []-            [] `matchKeys` vals--        it "does not match number when queried with string of that number" $-            -- 1 @>. "1" == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. String "1"] []-            [] `matchKeys` vals--        it "does not match number when queried with list of digits" $-            -- 1234567890 @>. [1,2,3,4,5,6,7,8,9,0] == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. toJSON ([1,2,3,4,5,6,7,8,9,0] :: [Int])] []-            [] `matchKeys` vals--      describe "@>. boolean queries" $ do-        it "matches identical booleans (True)" $-            -- true @>. true == True-            -- false @>. true == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. toJSON True] []-            [boolTK] `matchKeys` vals--        it "matches identical booleans (False)" $-            -- false @>. false == True-            -- true @>. false == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. Bool False] []-            [boolFK] `matchKeys` vals--        it "does not match boolean with string of boolean" $-            -- true @>. "true" == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. String "true"] []-            [] `matchKeys` vals--      describe "@>. null queries" $ do-        it "matches nulls" $-            -- null @>. null == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. Null] []-            [nullK,arrFilledK] `matchKeys` vals--        it "does not match null with string of null" $-            -- null @>. "null" == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson @>. String "null"] []-            [] `matchKeys` vals---      describe "<@. queries" $ do-        it "matches subobject when queried with superobject" $-            -- {}                         <@. {"test":null,"test1":"no","blabla":[]} == True-            -- {"test":null,"test1":"no"} <@. {"test":null,"test1":"no","blabla":[]} == True-          \TestKeys {..} -> runConnAssert $ do-            let queryObject = object ["test" .= Null-                                     , "test1" .= String "no"-                                     , "blabla" .= emptyArr-                                     ]-            vals <- selectList [TestValueJson <@. queryObject] []-            [objNullK,objTestK] `matchKeys` vals--        it "matches raw values and sublists when queried with superlist" $-            -- []    <@. [null,4,"b",{},[],{"test":[null],"test2":"yes"},false] == True-            -- null  <@. [null,4,"b",{},[],{"test":[null],"test2":"yes"},false] == True-            -- false <@. [null,4,"b",{},[],{"test":[null],"test2":"yes"},false] == True-            -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] <@.-            -- [null,4,"b",{},[],{"test":[null],"test2":"yes"},false] == True-          \TestKeys {..} -> runConnAssert $ do-            let queryList =-                  toJSON [ Null, Number 4, String "b", Object mempty, emptyArr-                         , object [ "test" .= [Null], "test2" .= String "yes"]-                         , Bool False ]--            vals <- selectList [TestValueJson <@. queryList ] []-            [arrNullK,arrFilledK,boolFK,nullK] `matchKeys` vals--        it "matches identical strings" $-            -- "a" <@. "a" == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson <@. String "a"] []-            [strAK] `matchKeys` vals--        it "matches identical big floats" $-            -- 9876543210.123457 <@ 9876543210.123457 == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson <@. Number 9876543210.123457] []-            [numBigFloatK] `matchKeys` vals--        it "doesn't match different big floats" $-            -- 9876543210.123457 <@. 9876543210.123456789 == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson <@. Number 9876543210.123456789] []-            [] `matchKeys` vals--        it "matches nulls" $-            -- null <@. null == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson <@. Null] []-            [nullK] `matchKeys` vals--      describe "?. queries" $ do-        it "matches top level keys and not the keys of nested objects" $-            -- {"test":null,"test1":"no"}                       ?. "test" == True-            -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] ?. "test" == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?. "test"] []-            [objTestK] `matchKeys` vals--        it "doesn't match nested key" $-            -- {"c":24.986,"foo":{"deep1":true"}} ?. "deep1" == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?. "deep1"] []-            [] `matchKeys` vals--        it "matches \"{}\" but not empty object when queried with \"{}\"" $-            -- "{}" ?. "{}" == True-            -- {}   ?. "{}" == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?. "{}"] []-            [strObjK] `matchKeys` vals--        it "matches raw empty str and empty str key when queried with \"\"" $-            ---- {}        ?. "" == False-            ---- ""        ?. "" == True-            ---- {"":9001} ?. "" == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?. ""] []-            [strNullK,objEmptyK] `matchKeys` vals--        it "matches lists containing string value when queried with raw string value" $-            -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] ?. "b" == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?. "b"] []-            [arrFilledK,arrList4K,objFullK] `matchKeys` vals--        it "matches lists, objects, and raw values correctly when queried with string" $-            -- [["a"]]                   ?. "a" == False-            -- "a"                       ?. "a" == True-            -- ["a","b","c","d"]         ?. "a" == True-            -- {"a":1,"b":2,"c":3,"d":4} ?. "a" == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?. "a"] []-            [strAK,arrList4K,objFullK] `matchKeys` vals--        it "matches string list but not real list when queried with \"[]\"" $-            -- "[]" ?. "[]" == True-            -- []   ?. "[]" == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?. "[]"] []-            [strArrK] `matchKeys` vals--        it "does not match null when queried with string null" $-            -- null ?. "null" == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?. "null"] []-            [] `matchKeys` vals--        it "does not match bool whe nqueried with string bool" $-            -- true ?. "true" == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?. "true"] []-            [] `matchKeys` vals---      describe "?|. queries" $ do-        it "matches raw vals, lists, objects, and nested objects" $-            -- "a"                                              ?|. ["a","b","c"] == True-            -- [["a"],1]                                        ?|. ["a","b","c"] == False-            -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] ?|. ["a","b","c"] == True-            -- ["a","b","c","d"]                                ?|. ["a","b","c"] == True-            -- {"a":1,"b":2,"c":3,"d":4}                        ?|. ["a","b","c"] == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?|. ["a","b","c"]] []-            [strAK,arrFilledK,objDeepK,arrList4K,objFullK] `matchKeys` vals--        it "matches str object but not object when queried with \"{}\"" $-            -- "{}"  ?|. ["{}"] == True-            -- {}    ?|. ["{}"] == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?|. ["{}"]] []-            [strObjK] `matchKeys` vals--        it "doesn't match superstrings when queried with substring" $-            -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] ?|. ["test"] == False-            -- "testing"                                        ?|. ["test"] == False-            -- {"test":null,"test1":"no"}                       ?|. ["test"] == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?|. ["test"]] []-            [objTestK] `matchKeys` vals--        it "doesn't match nested keys" $-            -- {"c":24.986,"foo":{"deep1":true"}} ?|. ["deep1"] == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?|. ["deep1"]] []-            [] `matchKeys` vals--        it "doesn't match anything when queried with empty list" $-            -- ANYTHING ?|. [] == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?|. []] []-            [] `matchKeys` vals--        it "doesn't match raw, non-string, values when queried with strings" $-            -- true ?|. ["true","null","1"] == False-            -- null ?|. ["true","null","1"] == False-            -- 1    ?|. ["true","null","1"] == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?|. ["true","null","1"]] []-            [] `matchKeys` vals--        it "matches string array when queried with \"[]\"" $-            -- []   ?|. ["[]"] == False-            -- "[]" ?|. ["[]"] == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?|. ["[]"]] []-            [strArrK] `matchKeys` vals--      describe "?&. queries" $ do-        it "matches anything when queried with an empty list" $-            -- ANYTHING ?&. [] == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?&. []] []-            flip matchKeys vals [ nullK-                                , boolTK, boolFK-                                , num0K, num1K, numBigK, numFloatK-                                , numSmallK, numFloat2K, numBigFloatK-                                , strNullK, strObjK, strArrK, strAK-                                , strTestK, str2K, strFloatK-                                , arrNullK, arrListK, arrList2K-                                , arrFilledK, arrList3K, arrList4K-                                , objNullK, objTestK, objDeepK-                                , objEmptyK, objFullK-                                ]--        it "matches raw values, lists, and objects when queried with string" $-            -- "a"                       ?&. ["a"] == True-            -- [["a"],1]                 ?&. ["a"] == False-            -- ["a","b","c","d"]         ?&. ["a"] == True-            -- {"a":1,"b":2,"c":3,"d":4} ?&. ["a"] == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?&. ["a"]] []-            [strAK,arrList4K,objFullK] `matchKeys` vals--        it "matches raw values, lists, and objects when queried with multiple string" $-            -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] ?&. ["b","c"] == False-            -- {"c":24.986,"foo":{"deep1":true"}}               ?&. ["b","c"] == False-            -- ["a","b","c","d"]                                ?&. ["b","c"] == True-            -- {"a":1,"b":2,"c":3,"d":4}                        ?&. ["b","c"] == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?&. ["b","c"]] []-            [arrList4K,objFullK] `matchKeys` vals--        it "matches object string when queried with \"{}\"" $-            -- {}   ?&. ["{}"] == False-            -- "{}" ?&. ["{}"] == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?&. ["{}"]] []-            [strObjK] `matchKeys` vals--        it "doesn't match superstrings when queried with substring" $-            -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] ?&. ["test"] == False-            -- "testing"                                        ?&. ["test"] == False-            -- {"test":null,"test1":"no"}                       ?&. ["test"] == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?&. ["test"]] []-            [objTestK] `matchKeys` vals--        it "doesn't match nested keys" $-            -- {"c":24.986,"foo":{"deep1":true"}} ?&. ["deep1"] == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?&. ["deep1"]] []-            [] `matchKeys` vals--        it "doesn't match anything when there is a partial match" $-            -- "a"                       ?&. ["a","e"] == False-            -- ["a","b","c","d"]         ?&. ["a","e"] == False-            -- {"a":1,"b":2,"c":3,"d":4} ?&. ["a","e"] == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?&. ["a","e"]] []-            [] `matchKeys` vals--        it "matches string array when queried with \"[]\"" $-            -- []   ?&. ["[]"] == False-            -- "[]" ?&. ["[]"] == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?&. ["[]"]] []-            [strArrK] `matchKeys` vals--        it "doesn't match null when queried with string null" $-            -- THIS WILL FAIL IF THE IMPLEMENTATION USES-            -- @ '{null}' @-            -- INSTEAD OF-            -- @ ARRAY['null'] @-            -- null ?&. ["null"] == False-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?&. ["null"]] []-            [] `matchKeys` vals--        it "doesn't match number when queried with str of that number" $-            -- [["a"],1] ?&. ["1"] == False-            -- "1"       ?&. ["1"] == True-          \TestKeys {..} -> runConnAssert $ do-          str1 <- insert' $ toJSON $ String "1"-          vals <- selectList [TestValueJson ?&. ["1"]] []-          [str1] `matchKeys` vals--        it "doesn't match empty objs or list when queried with empty string" $-            -- {}        ?&. [""] == False-            -- []        ?&. [""] == False-            -- ""        ?&. [""] == True-            -- {"":9001} ?&. [""] == True-          \TestKeys {..} -> runConnAssert $ do-            vals <- selectList [TestValueJson ?&. [""]] []-            [strNullK,objEmptyK] `matchKeys` vals+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++module JSONTest where++import Control.Monad.IO.Class (MonadIO)+import Data.Aeson hiding (Key)+import qualified Data.Vector as V (fromList)+import Test.HUnit (assertBool)+import Test.Hspec.Expectations ()++import Database.Persist+import Database.Persist.Postgresql.JSON++import PgInit++share+    [mkPersist persistSettings, mkMigrate "jsonTestMigrate"]+    [persistLowerCase|+  TestValue+    json Value+    deriving Show+|]++cleanDB+    :: (BaseBackend backend ~ SqlBackend, PersistQueryWrite backend, MonadIO m)+    => ReaderT backend m ()+cleanDB = deleteWhere ([] :: [Filter TestValue])++emptyArr :: Value+emptyArr = toJSON ([] :: [Value])++insert'+    :: (MonadIO m, PersistStoreWrite backend, BaseBackend backend ~ SqlBackend)+    => Value -> ReaderT backend m (Key TestValue)+insert' = insert . TestValue++matchKeys+    :: (Show record, Show (Key record), MonadIO m, Eq (Key record))+    => [Key record] -> [Entity record] -> m ()+matchKeys ys xs = do+    msg1 `assertBoolIO` (xLen == yLen)+    forM_ ys $ \y -> msg2 y `assertBoolIO` (y `elem` ks)+  where+    ks = entityKey <$> xs+    xLen = length xs+    yLen = length ys+    msg1 =+        mconcat+            [ "\nexpected: "+            , show yLen+            , "\n but got: "+            , show xLen+            , "\n[xs: "+            , show xs+            , "]"+            , "\n[ys: "+            , show ys+            , "]"+            ]+    msg2 y =+        mconcat+            [ "key \""+            , show y+            , "\" not in result:\n  "+            , show ks+            ]++setup :: IO TestKeys+setup = asIO $ runConn_ $ do+    void $ runMigrationSilent jsonTestMigrate+    testKeys++teardown :: IO ()+teardown = asIO $ runConn_ $ do+    cleanDB++shouldBeIO :: (Show a, Eq a, MonadIO m) => a -> a -> m ()+shouldBeIO x y = liftIO $ shouldBe x y++assertBoolIO :: (MonadIO m) => String -> Bool -> m ()+assertBoolIO s b = liftIO $ assertBool s b++testKeys :: (Monad m, MonadIO m) => ReaderT SqlBackend m TestKeys+testKeys = do+    nullK <- insert' Null++    boolTK <- insert' $ Bool True+    boolFK <- insert' $ toJSON False++    num0K <- insert' $ Number 0+    num1K <- insert' $ Number 1+    numBigK <- insert' $ toJSON (1234567890 :: Int)+    numFloatK <- insert' $ Number 0.0+    numSmallK <- insert' $ Number 0.0000000000000000123+    numFloat2K <- insert' $ Number 1.5+    -- numBigFloatK will turn into 9876543210.123457 because JSON+    numBigFloatK <- insert' $ toJSON (9876543210.123456789 :: Double)++    strNullK <- insert' $ String ""+    strObjK <- insert' $ String "{}"+    strArrK <- insert' $ String "[]"+    strAK <- insert' $ String "a"+    strTestK <- insert' $ toJSON ("testing" :: Text)+    str2K <- insert' $ String "2"+    strFloatK <- insert' $ String "0.45876"++    arrNullK <- insert' $ Array $ V.fromList []+    arrListK <- insert' $ toJSON [emptyArr, emptyArr, toJSON [emptyArr, emptyArr]]+    arrList2K <-+        insert' $+            toJSON+                [ emptyArr+                , toJSON [Number 3, Bool False]+                , toJSON [emptyArr, toJSON [Object mempty]]+                ]+    arrFilledK <-+        insert' $+            toJSON+                [ Null+                , Number 4+                , String "b"+                , Object mempty+                , emptyArr+                , object ["test" .= [Null], "test2" .= String "yes"]+                ]+    arrList3K <- insert' $ toJSON [toJSON [String "a"], Number 1]+    arrList4K <- insert' $ toJSON [String "a", String "b", String "c", String "d"]++    objNullK <- insert' $ Object mempty+    objTestK <- insert' $ object ["test" .= Null, "test1" .= String "no"]+    objDeepK <-+        insert' $ object ["c" .= Number 24.986, "foo" .= object ["deep1" .= Bool True]]+    objEmptyK <- insert' $ object ["" .= Number 9001]+    objFullK <-+        insert' $+            object+                [ "a" .= Number 1+                , "b" .= Number 2+                , "c" .= Number 3+                , "d" .= Number 4+                ]+    return TestKeys{..}++data TestKeys+    = TestKeys+    { nullK :: Key TestValue+    , boolTK :: Key TestValue+    , boolFK :: Key TestValue+    , num0K :: Key TestValue+    , num1K :: Key TestValue+    , numBigK :: Key TestValue+    , numFloatK :: Key TestValue+    , numSmallK :: Key TestValue+    , numFloat2K :: Key TestValue+    , numBigFloatK :: Key TestValue+    , strNullK :: Key TestValue+    , strObjK :: Key TestValue+    , strArrK :: Key TestValue+    , strAK :: Key TestValue+    , strTestK :: Key TestValue+    , str2K :: Key TestValue+    , strFloatK :: Key TestValue+    , arrNullK :: Key TestValue+    , arrListK :: Key TestValue+    , arrList2K :: Key TestValue+    , arrFilledK :: Key TestValue+    , objNullK :: Key TestValue+    , objTestK :: Key TestValue+    , objDeepK :: Key TestValue+    , arrList3K :: Key TestValue+    , arrList4K :: Key TestValue+    , objEmptyK :: Key TestValue+    , objFullK :: Key TestValue+    }+    deriving (Eq, Ord, Show)++specs :: Spec+specs = afterAll_ teardown $ do+    beforeAll setup $ do+        describe "Testing JSON operators" $ do+            describe "@>. object queries" $ do+                it "matches an empty Object with any object" $+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. Object mempty] []+                        [objNullK, objTestK, objDeepK, objEmptyK, objFullK] `matchKeys` vals++                it "matches a subset of object properties" $+                    -- {test: null, test1: no} @>. {test: null} == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. object ["test" .= Null]] []+                        [objTestK] `matchKeys` vals++                it "matches a nested object against an empty object at the same key" $+                    -- {c: 24.986, foo: {deep1: true}} @>. {foo: {}} == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. object ["foo" .= object []]] []+                        [objDeepK] `matchKeys` vals++                it "doesn't match a nested object against a string at the same key" $+                    -- {c: 24.986, foo: {deep1: true}} @>. {foo: nope} == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. object ["foo" .= String "nope"]] []+                        [] `matchKeys` vals++                it "matches a nested object when the query object is identical" $+                    -- {c: 24.986, foo: {deep1: true}} @>. {foo: {deep1: true}} == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <-+                            selectList [TestValueJson @>. (object ["foo" .= object ["deep1" .= True]])] []+                        [objDeepK] `matchKeys` vals++                it "doesn't match a nested object when queried with that exact object" $+                    -- {c: 24.986, foo: {deep1: true}} @>. {deep1: true} == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. object ["deep1" .= True]] []+                        [] `matchKeys` vals++            describe "@>. array queries" $ do+                it "matches an empty Array with any list" $+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. emptyArr] []+                        [arrNullK, arrListK, arrList2K, arrFilledK, arrList3K, arrList4K]+                            `matchKeys` vals++                it "matches list when queried with subset (1 item)" $+                    -- [null, 4, 'b', {}, [], {test: [null], test2: 'yes'}] @>. [4] == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. toJSON [4 :: Int]] []+                        [arrFilledK] `matchKeys` vals++                it "matches list when queried with subset (2 items)" $+                    -- [null, 4, 'b', {}, [], {test: [null], test2: 'yes'}] @>. [null,'b'] == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. toJSON [Null, String "b"]] []+                        [arrFilledK] `matchKeys` vals++                it "doesn't match list when queried with intersecting list (1 match, 1 diff)" $+                    -- [null, 4, 'b', {}, [], {test: [null], test2: 'yes'}] @>. [null,'d'] == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. toJSON [emptyArr, String "d"]] []+                        [] `matchKeys` vals++                it "matches list when queried with same list in different order" $+                    -- [null, 4, 'b', {}, [], {test: [null], test2: 'yes'}] @>.+                    -- [[],'b',{test: [null],test2: 'yes'},4,null,{}] == True+                    \TestKeys{..} -> runConnAssert $ do+                        let+                            queryList =+                                toJSON+                                    [ emptyArr+                                    , String "b"+                                    , object ["test" .= [Null], "test2" .= String "yes"]+                                    , Number 4+                                    , Null+                                    , Object mempty+                                    ]++                        vals <- selectList [TestValueJson @>. queryList] []+                        [arrFilledK] `matchKeys` vals++                it "doesn't match list when queried with same list + 1 item" $+                    -- [null,4,'b',{},[],{test:[null],test2:'yes'}] @>.+                    -- [null,4,'b',{},[],{test:[null],test2: 'yes'}, false] == False+                    \TestKeys{..} -> runConnAssert $ do+                        let+                            testList =+                                toJSON+                                    [ Null+                                    , Number 4+                                    , String "b"+                                    , Object mempty+                                    , emptyArr+                                    , object ["test" .= [Null], "test2" .= String "yes"]+                                    , Bool False+                                    ]++                        vals <- selectList [TestValueJson @>. testList] []+                        [] `matchKeys` vals++                it "matches list when it shares an empty object with the query list" $+                    -- [null,4,'b',{},[],{test: [null],test2: 'yes'}] @>. [{}] == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. toJSON [Object mempty]] []+                        [arrFilledK] `matchKeys` vals++                it "matches list with nested list, when queried with an empty nested list" $+                    -- [null,4,'b',{},[],{test:[null],test2:'yes'}] @>. [{test:[]}] == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. toJSON [object ["test" .= emptyArr]]] []+                        [arrFilledK] `matchKeys` vals++                it "doesn't match list with nested list, when queried with a diff. nested list" $+                    -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @>.+                    -- [{"test1":[null]}]  == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. toJSON [object ["test1" .= [Null]]]] []+                        [] `matchKeys` vals++                it "matches many nested lists when queried with empty nested list" $+                    -- [[],[],[[],[]]]                                  @>. [[]] == True+                    -- [[],[3,false],[[],[{}]]]                         @>. [[]] == True+                    -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @>. [[]] == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. toJSON [emptyArr]] []+                        [arrListK, arrList2K, arrFilledK, arrList3K] `matchKeys` vals++                it "matches nested list when queried with a subset of that list" $+                    -- [[],[3,false],[[],[{}]]] @>. [[3]] == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. toJSON [[3 :: Int]]] []+                        [arrList2K] `matchKeys` vals++                it "doesn't match nested list againts a partial intersection of that list" $+                    -- [[],[3,false],[[],[{}]]] @>. [[true,3]] == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. toJSON [[Bool True, Number 3]]] []+                        [] `matchKeys` vals++                it "matches list when queried with raw number contained in the list" $+                    -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @>. 4 == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. Number 4] []+                        [arrFilledK] `matchKeys` vals++                it "doesn't match list when queried with raw value not contained in the list" $+                    -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @>. 99 == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. Number 99] []+                        [] `matchKeys` vals++                it "matches list when queried with raw string contained in the list" $+                    -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @>. "b" == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. String "b"] []+                        [arrFilledK, arrList4K] `matchKeys` vals++                it "doesn't match list with empty object when queried with \"{}\" " $+                    -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @>. "{}" == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. String "{}"] []+                        [strObjK] `matchKeys` vals++                it "doesnt match list with nested object when queried with object (not in list)" $+                    -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @>.+                    -- {"test":[null],"test2":"yes"} == False+                    \TestKeys{..} -> runConnAssert $ do+                        let+                            queryObject = object ["test" .= [Null], "test2" .= String "yes"]+                        vals <- selectList [TestValueJson @>. queryObject] []+                        [] `matchKeys` vals++            describe "@>. string queries" $ do+                it "matches identical strings" $+                    -- "testing" @>. "testing" == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. String "testing"] []+                        [strTestK] `matchKeys` vals++                it "doesnt match case insensitive" $+                    -- "testing" @>. "Testing" == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. String "Testing"] []+                        [] `matchKeys` vals++                it "doesn't match substrings" $+                    -- "testing" @>. "test" == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. String "test"] []+                        [] `matchKeys` vals++                it "doesn't match strings with object keys" $+                    -- "testing" @>. {"testing":1} == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. object ["testing" .= Number 1]] []+                        [] `matchKeys` vals++            describe "@>. number queries" $ do+                it "matches identical numbers" $+                    -- 1   @>. 1 == True+                    -- [1] @>. 1 == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. toJSON (1 :: Int)] []+                        [num1K, arrList3K] `matchKeys` vals++                it "matches numbers when queried with float" $+                    -- 0 @>. 0.0 == True+                    -- 0.0 @>. 0.0 == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. toJSON (0.0 :: Double)] []+                        [num0K, numFloatK] `matchKeys` vals++                it "does not match numbers when queried with a substring of that number" $+                    -- 1234567890 @>. 123456789 == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. toJSON (123456789 :: Int)] []+                        [] `matchKeys` vals++                it "does not match number when queried with different number" $+                    -- 1234567890 @>. 234567890 == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. toJSON (234567890 :: Int)] []+                        [] `matchKeys` vals++                it "does not match number when queried with string of that number" $+                    -- 1 @>. "1" == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. String "1"] []+                        [] `matchKeys` vals++                it "does not match number when queried with list of digits" $+                    -- 1234567890 @>. [1,2,3,4,5,6,7,8,9,0] == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <-+                            selectList+                                [TestValueJson @>. toJSON ([1, 2, 3, 4, 5, 6, 7, 8, 9, 0] :: [Int])]+                                []+                        [] `matchKeys` vals++            describe "@>. boolean queries" $ do+                it "matches identical booleans (True)" $+                    -- true @>. true == True+                    -- false @>. true == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. toJSON True] []+                        [boolTK] `matchKeys` vals++                it "matches identical booleans (False)" $+                    -- false @>. false == True+                    -- true @>. false == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. Bool False] []+                        [boolFK] `matchKeys` vals++                it "does not match boolean with string of boolean" $+                    -- true @>. "true" == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. String "true"] []+                        [] `matchKeys` vals++            describe "@>. null queries" $ do+                it "matches nulls" $+                    -- null @>. null == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. Null] []+                        [nullK, arrFilledK] `matchKeys` vals++                it "does not match null with string of null" $+                    -- null @>. "null" == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson @>. String "null"] []+                        [] `matchKeys` vals++            describe "<@. queries" $ do+                it "matches subobject when queried with superobject" $+                    -- {}                         <@. {"test":null,"test1":"no","blabla":[]} == True+                    -- {"test":null,"test1":"no"} <@. {"test":null,"test1":"no","blabla":[]} == True+                    \TestKeys{..} -> runConnAssert $ do+                        let+                            queryObject =+                                object+                                    [ "test" .= Null+                                    , "test1" .= String "no"+                                    , "blabla" .= emptyArr+                                    ]+                        vals <- selectList [TestValueJson <@. queryObject] []+                        [objNullK, objTestK] `matchKeys` vals++                it "matches raw values and sublists when queried with superlist" $+                    -- []    <@. [null,4,"b",{},[],{"test":[null],"test2":"yes"},false] == True+                    -- null  <@. [null,4,"b",{},[],{"test":[null],"test2":"yes"},false] == True+                    -- false <@. [null,4,"b",{},[],{"test":[null],"test2":"yes"},false] == True+                    -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] <@.+                    -- [null,4,"b",{},[],{"test":[null],"test2":"yes"},false] == True+                    \TestKeys{..} -> runConnAssert $ do+                        let+                            queryList =+                                toJSON+                                    [ Null+                                    , Number 4+                                    , String "b"+                                    , Object mempty+                                    , emptyArr+                                    , object ["test" .= [Null], "test2" .= String "yes"]+                                    , Bool False+                                    ]++                        vals <- selectList [TestValueJson <@. queryList] []+                        [arrNullK, arrFilledK, boolFK, nullK] `matchKeys` vals++                it "matches identical strings" $+                    -- "a" <@. "a" == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson <@. String "a"] []+                        [strAK] `matchKeys` vals++                it "matches identical big floats" $+                    -- 9876543210.123457 <@ 9876543210.123457 == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson <@. Number 9876543210.123457] []+                        [numBigFloatK] `matchKeys` vals++                it "doesn't match different big floats" $+                    -- 9876543210.123457 <@. 9876543210.123456789 == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson <@. Number 9876543210.123456789] []+                        [] `matchKeys` vals++                it "matches nulls" $+                    -- null <@. null == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson <@. Null] []+                        [nullK] `matchKeys` vals++            describe "?. queries" $ do+                it "matches top level keys and not the keys of nested objects" $+                    -- {"test":null,"test1":"no"}                       ?. "test" == True+                    -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] ?. "test" == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?. "test"] []+                        [objTestK] `matchKeys` vals++                it "doesn't match nested key" $+                    -- {"c":24.986,"foo":{"deep1":true"}} ?. "deep1" == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?. "deep1"] []+                        [] `matchKeys` vals++                it "matches \"{}\" but not empty object when queried with \"{}\"" $+                    -- "{}" ?. "{}" == True+                    -- {}   ?. "{}" == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?. "{}"] []+                        [strObjK] `matchKeys` vals++                it "matches raw empty str and empty str key when queried with \"\"" $+                    ---- {}        ?. "" == False+                    ---- ""        ?. "" == True+                    ---- {"":9001} ?. "" == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?. ""] []+                        [strNullK, objEmptyK] `matchKeys` vals++                it "matches lists containing string value when queried with raw string value" $+                    -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] ?. "b" == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?. "b"] []+                        [arrFilledK, arrList4K, objFullK] `matchKeys` vals++                it "matches lists, objects, and raw values correctly when queried with string" $+                    -- [["a"]]                   ?. "a" == False+                    -- "a"                       ?. "a" == True+                    -- ["a","b","c","d"]         ?. "a" == True+                    -- {"a":1,"b":2,"c":3,"d":4} ?. "a" == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?. "a"] []+                        [strAK, arrList4K, objFullK] `matchKeys` vals++                it "matches string list but not real list when queried with \"[]\"" $+                    -- "[]" ?. "[]" == True+                    -- []   ?. "[]" == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?. "[]"] []+                        [strArrK] `matchKeys` vals++                it "does not match null when queried with string null" $+                    -- null ?. "null" == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?. "null"] []+                        [] `matchKeys` vals++                it "does not match bool whe nqueried with string bool" $+                    -- true ?. "true" == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?. "true"] []+                        [] `matchKeys` vals++            describe "?|. queries" $ do+                it "matches raw vals, lists, objects, and nested objects" $+                    -- "a"                                              ?|. ["a","b","c"] == True+                    -- [["a"],1]                                        ?|. ["a","b","c"] == False+                    -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] ?|. ["a","b","c"] == True+                    -- ["a","b","c","d"]                                ?|. ["a","b","c"] == True+                    -- {"a":1,"b":2,"c":3,"d":4}                        ?|. ["a","b","c"] == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?|. ["a", "b", "c"]] []+                        [strAK, arrFilledK, objDeepK, arrList4K, objFullK] `matchKeys` vals++                it "matches str object but not object when queried with \"{}\"" $+                    -- "{}"  ?|. ["{}"] == True+                    -- {}    ?|. ["{}"] == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?|. ["{}"]] []+                        [strObjK] `matchKeys` vals++                it "doesn't match superstrings when queried with substring" $+                    -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] ?|. ["test"] == False+                    -- "testing"                                        ?|. ["test"] == False+                    -- {"test":null,"test1":"no"}                       ?|. ["test"] == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?|. ["test"]] []+                        [objTestK] `matchKeys` vals++                it "doesn't match nested keys" $+                    -- {"c":24.986,"foo":{"deep1":true"}} ?|. ["deep1"] == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?|. ["deep1"]] []+                        [] `matchKeys` vals++                it "doesn't match anything when queried with empty list" $+                    -- ANYTHING ?|. [] == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?|. []] []+                        [] `matchKeys` vals++                it "doesn't match raw, non-string, values when queried with strings" $+                    -- true ?|. ["true","null","1"] == False+                    -- null ?|. ["true","null","1"] == False+                    -- 1    ?|. ["true","null","1"] == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?|. ["true", "null", "1"]] []+                        [] `matchKeys` vals++                it "matches string array when queried with \"[]\"" $+                    -- []   ?|. ["[]"] == False+                    -- "[]" ?|. ["[]"] == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?|. ["[]"]] []+                        [strArrK] `matchKeys` vals++            describe "?&. queries" $ do+                it "matches anything when queried with an empty list" $+                    -- ANYTHING ?&. [] == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?&. []] []+                        flip+                            matchKeys+                            vals+                            [ nullK+                            , boolTK+                            , boolFK+                            , num0K+                            , num1K+                            , numBigK+                            , numFloatK+                            , numSmallK+                            , numFloat2K+                            , numBigFloatK+                            , strNullK+                            , strObjK+                            , strArrK+                            , strAK+                            , strTestK+                            , str2K+                            , strFloatK+                            , arrNullK+                            , arrListK+                            , arrList2K+                            , arrFilledK+                            , arrList3K+                            , arrList4K+                            , objNullK+                            , objTestK+                            , objDeepK+                            , objEmptyK+                            , objFullK+                            ]++                it "matches raw values, lists, and objects when queried with string" $+                    -- "a"                       ?&. ["a"] == True+                    -- [["a"],1]                 ?&. ["a"] == False+                    -- ["a","b","c","d"]         ?&. ["a"] == True+                    -- {"a":1,"b":2,"c":3,"d":4} ?&. ["a"] == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?&. ["a"]] []+                        [strAK, arrList4K, objFullK] `matchKeys` vals++                it "matches raw values, lists, and objects when queried with multiple string" $+                    -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] ?&. ["b","c"] == False+                    -- {"c":24.986,"foo":{"deep1":true"}}               ?&. ["b","c"] == False+                    -- ["a","b","c","d"]                                ?&. ["b","c"] == True+                    -- {"a":1,"b":2,"c":3,"d":4}                        ?&. ["b","c"] == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?&. ["b", "c"]] []+                        [arrList4K, objFullK] `matchKeys` vals++                it "matches object string when queried with \"{}\"" $+                    -- {}   ?&. ["{}"] == False+                    -- "{}" ?&. ["{}"] == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?&. ["{}"]] []+                        [strObjK] `matchKeys` vals++                it "doesn't match superstrings when queried with substring" $+                    -- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] ?&. ["test"] == False+                    -- "testing"                                        ?&. ["test"] == False+                    -- {"test":null,"test1":"no"}                       ?&. ["test"] == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?&. ["test"]] []+                        [objTestK] `matchKeys` vals++                it "doesn't match nested keys" $+                    -- {"c":24.986,"foo":{"deep1":true"}} ?&. ["deep1"] == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?&. ["deep1"]] []+                        [] `matchKeys` vals++                it "doesn't match anything when there is a partial match" $+                    -- "a"                       ?&. ["a","e"] == False+                    -- ["a","b","c","d"]         ?&. ["a","e"] == False+                    -- {"a":1,"b":2,"c":3,"d":4} ?&. ["a","e"] == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?&. ["a", "e"]] []+                        [] `matchKeys` vals++                it "matches string array when queried with \"[]\"" $+                    -- []   ?&. ["[]"] == False+                    -- "[]" ?&. ["[]"] == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?&. ["[]"]] []+                        [strArrK] `matchKeys` vals++                it "doesn't match null when queried with string null" $+                    -- THIS WILL FAIL IF THE IMPLEMENTATION USES+                    -- @ '{null}' @+                    -- INSTEAD OF+                    -- @ ARRAY['null'] @+                    -- null ?&. ["null"] == False+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?&. ["null"]] []+                        [] `matchKeys` vals++                it "doesn't match number when queried with str of that number" $+                    -- [["a"],1] ?&. ["1"] == False+                    -- "1"       ?&. ["1"] == True+                    \TestKeys{..} -> runConnAssert $ do+                        str1 <- insert' $ toJSON $ String "1"+                        vals <- selectList [TestValueJson ?&. ["1"]] []+                        [str1] `matchKeys` vals++                it "doesn't match empty objs or list when queried with empty string" $+                    -- {}        ?&. [""] == False+                    -- []        ?&. [""] == False+                    -- ""        ?&. [""] == True+                    -- {"":9001} ?&. [""] == True+                    \TestKeys{..} -> runConnAssert $ do+                        vals <- selectList [TestValueJson ?&. [""]] []+                        [strNullK, objEmptyK] `matchKeys` vals
test/MigrationReferenceSpec.hs view
@@ -1,13 +1,15 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE OverloadedStrings, DataKinds, FlexibleInstances #-}-{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# OPTIONS_GHC -Wno-unused-top-binds #-}  module MigrationReferenceSpec where@@ -17,7 +19,9 @@ import Control.Monad.Trans.Writer (censor, mapWriterT) import Data.Text (Text, isInfixOf) -share [mkPersist sqlSettings, mkMigrate "referenceMigrate"] [persistLowerCase|+share+    [mkPersist sqlSettings, mkMigrate "referenceMigrate"]+    [persistLowerCase|  LocationCapabilities     Id Text@@ -47,10 +51,10 @@             isReference :: Text -> Bool             isReference migration = "REFERENCES" `isInfixOf` migration -        runMigration-            $ mapWriterT (censor noForeignKeys)-            $ referenceMigrate+        runMigration $+            mapWriterT (censor noForeignKeys) $+                referenceMigrate -        runMigration-            $ mapWriterT (censor onlyForeignKeys)-            $ referenceMigrate+        runMigration $+            mapWriterT (censor onlyForeignKeys) $+                referenceMigrate
test/PgInit.hs view
@@ -9,14 +9,12 @@     , runConn_     , runConnAssert     , runConnAssertUseConf-     , MonadIO     , persistSettings     , MkPersistSettings (..)-    , BackendKey(..)-    , GenerateKey(..)--     -- re-exports+    , BackendKey (..)+    , GenerateKey (..)+    -- re-exports     , module Control.Monad.Trans.Reader     , module Control.Monad     , module Database.Persist.Sql@@ -29,77 +27,84 @@     , module Test.HUnit     , AValue (..)     , BS.ByteString-    , Int32, Int64+    , Int32+    , Int64     , liftIO-    , mkPersist, migrateModels, mkMigrate, share, sqlSettings, persistLowerCase, persistUpperCase+    , mkPersist+    , migrateModels+    , mkMigrate+    , share+    , sqlSettings+    , persistLowerCase+    , persistUpperCase     , mkEntityDefList     , setImplicitIdDef     , SomeException     , Text-    , TestFn(..)+    , TestFn (..)     , LoggingT     , ResourceT-    , UUID(..)+    , UUID (..)     , sqlSettingsUuid     ) where  import Init-       ( GenerateKey(..)-       , MonadFail-       , RunDb-       , TestFn(..)-       , UUID(..)-       , arbText-       , asIO-       , assertEmpty-       , assertNotEmpty-       , assertNotEqual-       , isTravis-       , liftA2-       , sqlSettingsUuid-       , truncateTimeOfDay-       , truncateToMicro-       , truncateUTCTime-       , (==@)-       , (@/=)-       , (@==)-       )+    ( GenerateKey (..)+    , MonadFail+    , RunDb+    , TestFn (..)+    , UUID (..)+    , arbText+    , asIO+    , assertEmpty+    , assertNotEmpty+    , assertNotEqual+    , isTravis+    , liftA2+    , sqlSettingsUuid+    , truncateTimeOfDay+    , truncateToMicro+    , truncateUTCTime+    , (==@)+    , (@/=)+    , (@==)+    )  -- re-exports import Control.Exception (SomeException) import Control.Monad (forM_, liftM, replicateM, void, when) import Control.Monad.Trans.Reader-import Data.Aeson (FromJSON, ToJSON, Value(..), object)+import Data.Aeson (FromJSON, ToJSON, Value (..), object) import qualified Data.Text.Encoding as TE import Database.Persist.Postgresql.JSON () import Database.Persist.Sql.Raw.QQ import Database.Persist.SqlBackend import Database.Persist.TH-       ( MkPersistSettings(..)-       , migrateModels-       , mkEntityDefList-       , mkMigrate-       , mkPersist-       , persistLowerCase-       , persistUpperCase-       , setImplicitIdDef-       , share-       , sqlSettings-       )+    ( MkPersistSettings (..)+    , migrateModels+    , mkEntityDefList+    , mkMigrate+    , mkPersist+    , persistLowerCase+    , persistUpperCase+    , setImplicitIdDef+    , share+    , sqlSettings+    ) import Test.Hspec-       ( Arg-       , Spec-       , SpecWith-       , afterAll_-       , before-       , beforeAll-       , before_-       , describe-       , fdescribe-       , fit-       , hspec-       , it-       )+    ( Arg+    , Spec+    , SpecWith+    , afterAll_+    , before+    , beforeAll+    , before_+    , describe+    , fdescribe+    , fit+    , hspec+    , it+    ) import Test.Hspec.Expectations.Lifted import Test.QuickCheck.Instances () import UnliftIO@@ -132,98 +137,111 @@  dockerPg :: IO (Maybe BS.ByteString) dockerPg = do-  env <- liftIO getEnvironment-  return $ case lookup "POSTGRES_NAME" env of-    Just _name -> Just "postgres" -- /persistent/postgres-    _ -> Nothing+    env <- liftIO getEnvironment+    return $ case lookup "POSTGRES_NAME" env of+        Just _name -> Just "postgres" -- /persistent/postgres+        _ -> Nothing  persistSettings :: MkPersistSettings-persistSettings = sqlSettings { mpsGeneric = True }+persistSettings = sqlSettings{mpsGeneric = True} -runConn :: MonadUnliftIO m => SqlPersistT (LoggingT m) t -> m ()+runConn :: (MonadUnliftIO m) => SqlPersistT (LoggingT m) t -> m () runConn f = runConn_ f >>= const (return ()) -runConn_ :: MonadUnliftIO m => SqlPersistT (LoggingT m) t -> m t+runConn_ :: (MonadUnliftIO m) => SqlPersistT (LoggingT m) t -> m t runConn_ f = runConnInternal RunConnBasic f  -- | Data type to switch between pool creation functions, to ease testing both.-data RunConnType =-    RunConnBasic -- ^ Use 'withPostgresqlPool'-  | RunConnConf -- ^ Use 'withPostgresqlPoolWithConf'-  deriving (Show, Eq)+data RunConnType+    = -- | Use 'withPostgresqlPool'+      RunConnBasic+    | -- | Use 'withPostgresqlPoolWithConf'+      RunConnConf+    deriving (Show, Eq) -runConnInternal :: MonadUnliftIO m => RunConnType -> SqlPersistT (LoggingT m) t -> m t+runConnInternal+    :: (MonadUnliftIO m) => RunConnType -> SqlPersistT (LoggingT m) t -> m t runConnInternal connType f = do-  travis <- liftIO isTravis-  let debugPrint = not travis && _debugOn-      printDebug = if debugPrint then print . fromLogStr else void . return-      poolSize = 1-  connString <- if travis-    then do-      pure "host=localhost port=5432 user=perstest password=perstest dbname=persistent"-    else do-      host <- fromMaybe "localhost" <$> liftIO dockerPg-      pure ("host=" <> host <> " port=5432 user=postgres dbname=test")+    travis <- liftIO isTravis+    let+        debugPrint = not travis && _debugOn+        printDebug = if debugPrint then print . fromLogStr else void . return+        poolSize = 1+    connString <-+        if travis+            then do+                pure+                    "host=localhost port=5432 user=perstest password=perstest dbname=persistent"+            else do+                host <- fromMaybe "localhost" <$> liftIO dockerPg+                pure ("host=" <> host <> " port=5432 user=postgres dbname=test") -  flip runLoggingT (\_ _ _ s -> printDebug s) $ do-    logInfoN (if travis then "Running in CI" else "CI not detected")-    let go =-            case connType of-                RunConnBasic ->-                    withPostgresqlPool connString poolSize $ runSqlPool f-                RunConnConf -> do-                    let conf = PostgresConf-                          { pgConnStr = connString-                          , pgPoolStripes = 1-                          , pgPoolIdleTimeout = 60-                          , pgPoolSize = poolSize-                          }-                        hooks = defaultPostgresConfHooks-                    withPostgresqlPoolWithConf conf hooks (runSqlPool f)-    -- horrifying hack :( postgresql is having weird connection failures in-    -- CI, for no reason that i can determine. see this PR for notes:-                    -- https://github.com/yesodweb/persistent/pull/1197-    eres <- try go-    case eres of-        Left (err :: SomeException) -> do-            eres' <- try go-            case eres' of-                Left (err' :: SomeException) ->-                    if show err == show err'-                    then throwIO err-                    else throwIO err'-                Right a ->-                    pure a-        Right a ->-            pure a+    flip runLoggingT (\_ _ _ s -> printDebug s) $ do+        logInfoN (if travis then "Running in CI" else "CI not detected")+        let+            go =+                case connType of+                    RunConnBasic ->+                        withPostgresqlPool connString poolSize $ runSqlPool f+                    RunConnConf -> do+                        let+                            conf =+                                PostgresConf+                                    { pgConnStr = connString+                                    , pgPoolStripes = 1+                                    , pgPoolIdleTimeout = 60+                                    , pgPoolSize = poolSize+                                    }+                            hooks = defaultPostgresConfHooks+                        withPostgresqlPoolWithConf conf hooks (runSqlPool f)+        -- horrifying hack :( postgresql is having weird connection failures in+        -- CI, for no reason that i can determine. see this PR for notes:+        -- https://github.com/yesodweb/persistent/pull/1197+        eres <- try go+        case eres of+            Left (err :: SomeException) -> do+                eres' <- try go+                case eres' of+                    Left (err' :: SomeException) ->+                        if show err == show err'+                            then throwIO err+                            else throwIO err'+                    Right a ->+                        pure a+            Right a ->+                pure a  runConnAssert :: SqlPersistT (LoggingT (ResourceT IO)) () -> Assertion runConnAssert actions = do-  runResourceT $ runConn $ actions >> transactionUndo+    runResourceT $ runConn $ actions >> transactionUndo  -- | Like runConnAssert, but uses the "conf" flavor of functions to test that code path. runConnAssertUseConf :: SqlPersistT (LoggingT (ResourceT IO)) () -> Assertion runConnAssertUseConf actions = do-  runResourceT $ runConnInternal RunConnConf (actions >> transactionUndo)+    runResourceT $ runConnInternal RunConnConf (actions >> transactionUndo) -newtype AValue = AValue { getValue :: Value }+newtype AValue = AValue {getValue :: Value}  -- Need a specialized Arbitrary instance instance Arbitrary AValue where-  arbitrary = AValue <$>-              frequency [ (1, pure Null)-                        , (1, Bool <$> arbitrary)-                        , (2, Number <$> arbitrary)-                        , (2, String <$> arbText)-                        , (3, Array <$> limitIt 4 (fmap (fmap getValue) arbitrary))-                        , (3, object <$> arbObject)-                        ]-    where-      limitIt :: Int -> Gen a -> Gen a-      limitIt i x = sized $ \n -> do-          let m = if n > i then i else n-          resize m x-      arbObject = limitIt 4 -- Recursion can make execution divergent+    arbitrary =+        AValue+            <$> frequency+                [ (1, pure Null)+                , (1, Bool <$> arbitrary)+                , (2, Number <$> arbitrary)+                , (2, String <$> arbText)+                , (3, Array <$> limitIt 4 (fmap (fmap getValue) arbitrary))+                , (3, object <$> arbObject)+                ]+      where+        limitIt :: Int -> Gen a -> Gen a+        limitIt i x = sized $ \n -> do+            let+                m = if n > i then i else n+            resize m x+        arbObject =+            limitIt 4 -- Recursion can make execution divergent                 $ listOf -- [(,)] -> (,)-                . liftA2 (,) arbText -- (,) -> Text and Value+                    . liftA2 (,) arbText -- (,) -> Text and Value                 $ limitIt 4 (fmap getValue arbitrary) -- Again, precaution against divergent recursion.
test/PgIntervalTest.hs view
@@ -1,43 +1,74 @@-{-# LANGUAGE EmptyDataDecls             #-}-{-# LANGUAGE FlexibleContexts           #-}-{-# LANGUAGE GADTs, DataKinds, FlexibleInstances                      #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE EmptyDataDecls #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE MultiParamTypeClasses      #-}-{-# LANGUAGE OverloadedStrings          #-}-{-# LANGUAGE QuasiQuotes                #-}-{-# LANGUAGE TemplateHaskell            #-}-{-# LANGUAGE TypeFamilies               #-}-{-# LANGUAGE UndecidableInstances       #-}-{-# LANGUAGE DerivingStrategies         #-}-{-# LANGUAGE StandaloneDeriving         #-}-{-# LANGUAGE DeriveAnyClass             #-}-{-# LANGUAGE TypeOperators              #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}  module PgIntervalTest where +import Data.Fixed (Fixed (MkFixed), Micro, Pico)+import Data.Time.Clock (secondsToNominalDiffTime)+import Database.Persist.Postgresql (PgInterval (..))+import qualified Database.PostgreSQL.Simple.Interval as Interval import PgInit-import Data.Time.Clock (NominalDiffTime)-import Database.Persist.Postgresql (PgInterval(..)) import Test.Hspec.QuickCheck -share [mkPersist sqlSettings, mkMigrate "pgIntervalMigrate"] [persistLowerCase|+share+    [mkPersist sqlSettings, mkMigrate "pgIntervalMigrate"]+    [persistLowerCase| PgIntervalDb     interval_field PgInterval     deriving Eq     deriving Show++IntervalDb+    interval_field Interval.Interval+    deriving Eq Show |] --- Postgres Interval has a 1 microsecond resolution, while NominalDiffTime has--- picosecond resolution. Round to the nearest microsecond so that we can be--- fine in the tests.-truncate' :: NominalDiffTime -> NominalDiffTime-truncate' x = (fromIntegral (round (x * 10^6))) / 10^6+clamp :: (Ord a) => a -> a -> a -> a+clamp lo hi = max lo . min hi +-- Before version 15, PostgreSQL can't parse all possible intervals.+-- Each component is limited to the range of Int32.+-- So anything beyond 2,147,483,647 hours will fail to parse.++microsecondLimit :: Int64+microsecondLimit = 2147483647 * 60 * 60 * 1000000+ specs :: Spec specs = do     describe "Postgres Interval Property tests" $ do-        prop "Round trips" $ \time -> runConnAssert $ do-            let eg = PgIntervalDb $ PgInterval (truncate' time)+        prop "Round trips" $ \int64 -> runConnAssert $ do+            let+                eg =+                    PgIntervalDb+                        . PgInterval+                        . secondsToNominalDiffTime+                        . (realToFrac :: Micro -> Pico)+                        . MkFixed+                        . toInteger+                        $ clamp (-microsecondLimit) microsecondLimit int64             rid <- insert eg             r <- getJust rid             liftIO $ r `shouldBe` eg++        prop "interval round trips" $ \(m, d, u) -> runConnAssert $ do+            let+                expected =+                    IntervalDb . Interval.MkInterval m d $+                        clamp (-microsecondLimit) microsecondLimit u+            key <- insert expected+            actual <- getJust key+            liftIO $ actual `shouldBe` expected
test/UpsertWhere.hs view
@@ -20,7 +20,9 @@ import Data.Time import Database.Persist.Postgresql -share [mkPersist sqlSettings, mkMigrate "upsertWhereMigrate"] [persistLowerCase|+share+    [mkPersist sqlSettings, mkMigrate "upsertWhereMigrate"]+    [persistLowerCase|  Item     name        Text sqltype=varchar(80)@@ -47,12 +49,14 @@     deleteWhere ([] :: [Filter Item])     deleteWhere ([] :: [Filter ItemMigOnly]) -itDb :: String -> SqlPersistT (LoggingT (ResourceT IO)) a -> SpecWith (Arg (IO ()))+itDb+    :: String -> SqlPersistT (LoggingT (ResourceT IO)) a -> SpecWith (Arg (IO ())) itDb msg action = it msg $ runConnAssert $ void action  specs :: Spec specs = describe "UpsertWhere" $ do-    let item1 = Item "item1" "" (Just 3) Nothing+    let+        item1 = Item "item1" "" (Just 3) Nothing         item2 = Item "item2" "hello world" Nothing (Just 2)         items = [item1, item2] @@ -62,14 +66,15 @@             Just item <- fmap entityVal <$> getBy (UniqueName "item1")             item `shouldBe` item1         itDb "performs only updates given if record already exists" $ do-            let newDescription = "I am a new description"+            let+                newDescription = "I am a new description"             insert_ item1             upsertWhere                 (Item "item1" "i am an inserted description" (Just 1) (Just 2))                 [ItemDescription =. newDescription]                 []             Just item <- fmap entityVal <$> getBy (UniqueName "item1")-            item `shouldBe` item1 { itemDescription = newDescription }+            item `shouldBe` item1{itemDescription = newDescription}          itDb "inserts with MigrationOnly fields (#1330)" $ do             upsertWhere@@ -80,7 +85,8 @@     describe "upsertManyWhere" $ do         itDb "inserts fresh records" $ do             insertMany_ items-            let newItem = Item "item3" "fresh" Nothing Nothing+            let+                newItem = Item "item3" "fresh" Nothing Nothing             upsertManyWhere                 (newItem : items)                 [copyField ItemDescription]@@ -91,7 +97,7 @@         itDb "updates existing records" $ do             let                 postUpdate =-                    map (\i -> i { itemQuantity = fmap (+1) (itemQuantity i) }) items+                    map (\i -> i{itemQuantity = fmap (+ 1) (itemQuantity i)}) items             insertMany_ items             upsertManyWhere                 items@@ -102,8 +108,12 @@             dbItems `shouldMatchList` postUpdate         itDb "only copies passing values" $ do             insertMany_ items-            let newItems = map (\i -> i { itemQuantity = Just 0, itemPrice = fmap (*2) (itemPrice i) }) items-                postUpdate = map (\i -> i { itemPrice = fmap (*2) (itemPrice i) }) items+            let+                newItems =+                    map+                        (\i -> i{itemQuantity = Just 0, itemPrice = fmap (* 2) (itemPrice i)})+                        items+                postUpdate = map (\i -> i{itemPrice = fmap (* 2) (itemPrice i)}) items             upsertManyWhere                 newItems                 [ copyUnlessEq ItemQuantity (Just 0)@@ -114,7 +124,8 @@             dbItems <- fmap entityVal <$> selectList [] []             dbItems `shouldMatchList` postUpdate         itDb "inserts without modifying existing records if no updates specified" $ do-            let newItem = Item "item3" "hi friends!" Nothing Nothing+            let+                newItem = Item "item3" "hi friends!" Nothing Nothing             insertMany_ items             upsertManyWhere                 (newItem : items)@@ -123,74 +134,85 @@                 []             dbItems <- fmap entityVal <$> selectList [] []             dbItems `shouldMatchList` (newItem : items)-        itDb "inserts without modifying existing records if no updates specified and there's a filter with True condition" $-          do-            let newItem = Item "item3" "hi friends!" Nothing Nothing-            insertMany_ items-            upsertManyWhere-              (newItem : items)-              []-              []-              [ItemDescription ==. "hi friends!"]-            dbItems <- fmap entityVal <$> selectList [] []-            dbItems `shouldMatchList` (newItem : items)-        itDb "inserts without updating existing records if there are updates specified but there's a filter with a False condition" $-          do-            let newItem = Item "item3" "hi friends!" Nothing Nothing-            insertMany_ items-            upsertManyWhere-              (newItem : items)-              []-              [ItemQuantity +=. Just 1]-              [ItemDescription ==. "hi friends!"]-            dbItems <- fmap entityVal <$> selectList [] []-            dbItems `shouldMatchList` (newItem : items)-        itDb "inserts new records but does not update existing records if there are updates specified but the modification condition is False" $-          do-            let newItem = Item "item3" "hi friends!" Nothing Nothing-            insertMany_ items-            upsertManyWhere-              (newItem : items)-              []-              [ItemQuantity +=. Just 1]-              [excludeNotEqualToOriginal ItemDescription]-            dbItems <- fmap entityVal <$> selectList [] []-            dbItems `shouldMatchList` (newItem : items)-        itDb "inserts new records and updates existing records if there are updates specified and the modification condition is True (because it's empty)" $-          do-            let newItem = Item "item3" "hello world" Nothing Nothing-                postUpdate = map (\i -> i {itemQuantity = fmap (+ 1) (itemQuantity i)}) items-            insertMany_ items-            upsertManyWhere-              (newItem : items)-              []-              [ItemQuantity +=. Just 1]-              []-            dbItems <- fmap entityVal <$> selectList [] []-            dbItems `shouldMatchList` (newItem : postUpdate)-        itDb "inserts new records and updates existing records if there are updates specified and the modification filter condition is triggered" $-           do-            let newItem = Item "item3" "hi friends!" Nothing Nothing-                postUpdate = map (\i -> i {itemQuantity = fmap (+1) (itemQuantity i)}) items-            insertMany_ items-            upsertManyWhere-              (newItem : items)-              [-                copyUnlessEq ItemDescription "hi friends!"-              , copyField ItemPrice-              ]-              [ItemQuantity +=. Just 1]-              [ItemDescription !=. "bye friends!"]-            dbItems <- fmap entityVal <$> selectList [] []-            dbItems `shouldMatchList` (newItem : postUpdate)-        itDb "inserts an item and doesn't apply the update if the filter condition is triggered" $-          do-            let newItem = Item "item3" "hello world" Nothing Nothing-            insertMany_ items-            upsertManyWhere-              (newItem : items)-              []-              [ItemQuantity +=. Just 1]-              [excludeNotEqualToOriginal ItemDescription]-            dbItems <- fmap entityVal <$> selectList [] []-            dbItems `shouldMatchList` (newItem : items)+        itDb+            "inserts without modifying existing records if no updates specified and there's a filter with True condition"+            $ do+                let+                    newItem = Item "item3" "hi friends!" Nothing Nothing+                insertMany_ items+                upsertManyWhere+                    (newItem : items)+                    []+                    []+                    [ItemDescription ==. "hi friends!"]+                dbItems <- fmap entityVal <$> selectList [] []+                dbItems `shouldMatchList` (newItem : items)+        itDb+            "inserts without updating existing records if there are updates specified but there's a filter with a False condition"+            $ do+                let+                    newItem = Item "item3" "hi friends!" Nothing Nothing+                insertMany_ items+                upsertManyWhere+                    (newItem : items)+                    []+                    [ItemQuantity +=. Just 1]+                    [ItemDescription ==. "hi friends!"]+                dbItems <- fmap entityVal <$> selectList [] []+                dbItems `shouldMatchList` (newItem : items)+        itDb+            "inserts new records but does not update existing records if there are updates specified but the modification condition is False"+            $ do+                let+                    newItem = Item "item3" "hi friends!" Nothing Nothing+                insertMany_ items+                upsertManyWhere+                    (newItem : items)+                    []+                    [ItemQuantity +=. Just 1]+                    [excludeNotEqualToOriginal ItemDescription]+                dbItems <- fmap entityVal <$> selectList [] []+                dbItems `shouldMatchList` (newItem : items)+        itDb+            "inserts new records and updates existing records if there are updates specified and the modification condition is True (because it's empty)"+            $ do+                let+                    newItem = Item "item3" "hello world" Nothing Nothing+                    postUpdate = map (\i -> i{itemQuantity = fmap (+ 1) (itemQuantity i)}) items+                insertMany_ items+                upsertManyWhere+                    (newItem : items)+                    []+                    [ItemQuantity +=. Just 1]+                    []+                dbItems <- fmap entityVal <$> selectList [] []+                dbItems `shouldMatchList` (newItem : postUpdate)+        itDb+            "inserts new records and updates existing records if there are updates specified and the modification filter condition is triggered"+            $ do+                let+                    newItem = Item "item3" "hi friends!" Nothing Nothing+                    postUpdate = map (\i -> i{itemQuantity = fmap (+ 1) (itemQuantity i)}) items+                insertMany_ items+                upsertManyWhere+                    (newItem : items)+                    [ copyUnlessEq ItemDescription "hi friends!"+                    , copyField ItemPrice+                    ]+                    [ItemQuantity +=. Just 1]+                    [ItemDescription !=. "bye friends!"]+                dbItems <- fmap entityVal <$> selectList [] []+                dbItems `shouldMatchList` (newItem : postUpdate)+        itDb+            "inserts an item and doesn't apply the update if the filter condition is triggered"+            $ do+                let+                    newItem = Item "item3" "hello world" Nothing Nothing+                insertMany_ items+                upsertManyWhere+                    (newItem : items)+                    []+                    [ItemQuantity +=. Just 1]+                    [excludeNotEqualToOriginal ItemDescription]+                dbItems <- fmap entityVal <$> selectList [] []+                dbItems `shouldMatchList` (newItem : items)
test/main.hs view
@@ -67,7 +67,9 @@ type Tuple = (,)  -- Test lower case names-share [mkPersist persistSettings, mkMigrate "dataTypeMigrate"] [persistLowerCase|+share+    [mkPersist persistSettings, mkMigrate "dataTypeMigrate"]+    [persistLowerCase| DataTypeTable no-json     text Text     textMaxLen Text maxlen=100@@ -87,131 +89,138 @@ |]  instance Arbitrary DataTypeTable where-  arbitrary = DataTypeTable-     <$> arbText                -- text-     <*> (T.take 100 <$> arbText)          -- textManLen-     <*> arbitrary              -- bytes-     <*> liftA2 (,) arbitrary arbText      -- bytesTextTuple-     <*> (BS.take 100 <$> arbitrary)       -- bytesMaxLen-     <*> arbitrary              -- int-     <*> arbitrary              -- intList-     <*> arbitrary              -- intMap-     <*> arbitrary              -- double-     <*> arbitrary              -- bool-     <*> arbitrary              -- day-     <*> arbitrary              -- pico-     <*> (arbitrary) -- utc-     <*> (truncateUTCTime   =<< arbitrary) -- utc-     <*> fmap getValue arbitrary -- value+    arbitrary =+        DataTypeTable+            <$> arbText -- text+            <*> (T.take 100 <$> arbText) -- textManLen+            <*> arbitrary -- bytes+            <*> liftA2 (,) arbitrary arbText -- bytesTextTuple+            <*> (BS.take 100 <$> arbitrary) -- bytesMaxLen+            <*> arbitrary -- int+            <*> arbitrary -- intList+            <*> arbitrary -- intMap+            <*> arbitrary -- double+            <*> arbitrary -- bool+            <*> arbitrary -- day+            <*> arbitrary -- pico+            <*> (arbitrary) -- utc+            <*> (truncateUTCTime =<< arbitrary) -- utc+            <*> fmap getValue arbitrary -- value -setup :: MonadIO m => Migration -> ReaderT SqlBackend m ()+setup :: (MonadIO m) => Migration -> ReaderT SqlBackend m () setup migration = do-  printMigration migration-  runMigrationUnsafe migration+    printMigration migration+    runMigrationUnsafe migration  main :: IO () main = do-  runConn $ do-    mapM_ setup-      [ PersistentTest.testMigrate-      , PersistentTest.noPrefixMigrate-      , PersistentTest.customPrefixMigrate-      , PersistentTest.treeMigrate-      , EmbedTest.embedMigrate-      , EmbedOrderTest.embedOrderMigrate-      , LargeNumberTest.numberMigrate-      , UniqueTest.uniqueMigrate-      , MaxLenTest.maxlenMigrate-      , MaybeFieldDefsTest.maybeFieldDefMigrate-      , TypeLitFieldDefsTest.typeLitFieldDefsMigrate-      , Recursive.recursiveMigrate-      , CompositeTest.compositeMigrate-      , TreeTest.treeMigrate-      , PersistUniqueTest.migration-      , RenameTest.migration-      , CustomPersistFieldTest.customFieldMigrate-      , PrimaryTest.migration-      , CustomPrimaryKeyReferenceTest.migration-      , MigrationColumnLengthTest.migration-      , TransactionLevelTest.migration-      , LongIdentifierTest.migration-      , ForeignKey.compositeMigrate-      , MigrationTest.migrationMigrate-      , PgIntervalTest.pgIntervalMigrate-      , UpsertWhere.upsertWhereMigrate-      , ImplicitUuidSpec.implicitUuidMigrate-      ]-    PersistentTest.cleanDB-    ForeignKey.cleanDB--  hspec $ do-      ImplicitUuidSpec.spec-      MigrationReferenceSpec.spec-      RenameTest.specsWith runConnAssert-      DataTypeTest.specsWith runConnAssert-          (Just (runMigrationSilent dataTypeMigrate))-          [ TestFn "text" dataTypeTableText-          , TestFn "textMaxLen" dataTypeTableTextMaxLen-          , TestFn "bytes" dataTypeTableBytes-          , TestFn "bytesTextTuple" dataTypeTableBytesTextTuple-          , TestFn "bytesMaxLen" dataTypeTableBytesMaxLen-          , TestFn "int" dataTypeTableInt-          , TestFn "intList" dataTypeTableIntList-          , TestFn "intMap" dataTypeTableIntMap-          , TestFn "bool" dataTypeTableBool-          , TestFn "day" dataTypeTableDay-          , TestFn "time" (DataTypeTest.roundTime . dataTypeTableTime)-          , TestFn "utc" (DataTypeTest.roundUTCTime . dataTypeTableUtc)-          , TestFn "jsonb" dataTypeTableJsonb-          ]-          [ ("pico", dataTypeTablePico) ]-          dataTypeTableDouble-      HtmlTest.specsWith-          runConnAssert-          (Just (runMigrationSilent HtmlTest.htmlMigrate))+    runConn $ do+        mapM_+            setup+            [ PersistentTest.testMigrate+            , PersistentTest.noPrefixMigrate+            , PersistentTest.customPrefixMigrate+            , PersistentTest.treeMigrate+            , EmbedTest.embedMigrate+            , EmbedOrderTest.embedOrderMigrate+            , LargeNumberTest.numberMigrate+            , UniqueTest.uniqueMigrate+            , MaxLenTest.maxlenMigrate+            , MaybeFieldDefsTest.maybeFieldDefMigrate+            , TypeLitFieldDefsTest.typeLitFieldDefsMigrate+            , Recursive.recursiveMigrate+            , CompositeTest.compositeMigrate+            , TreeTest.treeMigrate+            , PersistUniqueTest.migration+            , RenameTest.migration+            , CustomPersistFieldTest.customFieldMigrate+            , PrimaryTest.migration+            , CustomPrimaryKeyReferenceTest.migration+            , MigrationColumnLengthTest.migration+            , TransactionLevelTest.migration+            , LongIdentifierTest.migration+            , ForeignKey.compositeMigrate+            , MigrationTest.migrationMigrate+            , PgIntervalTest.pgIntervalMigrate+            , UpsertWhere.upsertWhereMigrate+            , ImplicitUuidSpec.implicitUuidMigrate+            ]+        PersistentTest.cleanDB+        ForeignKey.cleanDB -      EmbedTest.specsWith runConnAssert-      EmbedOrderTest.specsWith runConnAssert-      LargeNumberTest.specsWith runConnAssert-      ForeignKey.specsWith runConnAssert-      UniqueTest.specsWith runConnAssert-      MaxLenTest.specsWith runConnAssert-      MaybeFieldDefsTest.specsWith runConnAssert-      TypeLitFieldDefsTest.specsWith runConnAssert-      Recursive.specsWith runConnAssert-      SumTypeTest.specsWith runConnAssert (Just (runMigrationSilent SumTypeTest.sumTypeMigrate))-      MigrationTest.specsWith runConnAssert-      MigrationOnlyTest.specsWith runConnAssert+    hspec $ do+        ImplicitUuidSpec.spec+        MigrationReferenceSpec.spec+        RenameTest.specsWith runConnAssert+        DataTypeTest.specsWith+            runConnAssert+            (Just (runMigrationSilent dataTypeMigrate))+            [ TestFn "text" dataTypeTableText+            , TestFn "textMaxLen" dataTypeTableTextMaxLen+            , TestFn "bytes" dataTypeTableBytes+            , TestFn "bytesTextTuple" dataTypeTableBytesTextTuple+            , TestFn "bytesMaxLen" dataTypeTableBytesMaxLen+            , TestFn "int" dataTypeTableInt+            , TestFn "intList" dataTypeTableIntList+            , TestFn "intMap" dataTypeTableIntMap+            , TestFn "bool" dataTypeTableBool+            , TestFn "day" dataTypeTableDay+            , TestFn "time" (DataTypeTest.roundTime . dataTypeTableTime)+            , TestFn "utc" (DataTypeTest.roundUTCTime . dataTypeTableUtc)+            , TestFn "jsonb" dataTypeTableJsonb+            ]+            [("pico", dataTypeTablePico)]+            dataTypeTableDouble+        HtmlTest.specsWith+            runConnAssert+            (Just (runMigrationSilent HtmlTest.htmlMigrate)) -          (Just-              $ runMigrationSilent MigrationOnlyTest.migrateAll1-              >> runMigrationSilent MigrationOnlyTest.migrateAll2-          )-      PersistentTest.specsWith runConnAssert-      ReadWriteTest.specsWith runConnAssert-      PersistentTest.filterOrSpecs runConnAssert-      RawSqlTest.specsWith runConnAssert-      UpsertTest.specsWith-          runConnAssert-          UpsertTest.Don'tUpdateNull-          UpsertTest.UpsertPreserveOldKey+        EmbedTest.specsWith runConnAssert+        EmbedOrderTest.specsWith runConnAssert+        LargeNumberTest.specsWith runConnAssert+        ForeignKey.specsWith runConnAssert+        UniqueTest.specsWith runConnAssert+        MaxLenTest.specsWith runConnAssert+        MaybeFieldDefsTest.specsWith runConnAssert+        TypeLitFieldDefsTest.specsWith runConnAssert+        Recursive.specsWith runConnAssert+        SumTypeTest.specsWith+            runConnAssert+            (Just (runMigrationSilent SumTypeTest.sumTypeMigrate))+        MigrationTest.specsWith runConnAssert+        MigrationOnlyTest.specsWith+            runConnAssert+            ( Just $+                runMigrationSilent MigrationOnlyTest.migrateAll1+                    >> runMigrationSilent MigrationOnlyTest.migrateAll2+            )+        PersistentTest.specsWith runConnAssert+        ReadWriteTest.specsWith runConnAssert+        PersistentTest.filterOrSpecs runConnAssert+        RawSqlTest.specsWith runConnAssert+        UpsertTest.specsWith+            runConnAssert+            UpsertTest.Don'tUpdateNull+            UpsertTest.UpsertPreserveOldKey -      MpsNoPrefixTest.specsWith runConnAssert-      MpsCustomPrefixTest.specsWith runConnAssert-      EmptyEntityTest.specsWith runConnAssert (Just (runMigrationSilent EmptyEntityTest.migration))-      CompositeTest.specsWith runConnAssert-      TreeTest.specsWith runConnAssert-      PersistUniqueTest.specsWith runConnAssert-      PrimaryTest.specsWith runConnAssert-      CustomPersistFieldTest.specsWith runConnAssert-      CustomPrimaryKeyReferenceTest.specsWith runConnAssert-      MigrationColumnLengthTest.specsWith runConnAssert-      EquivalentTypeTestPostgres.specs-      TransactionLevelTest.specsWith runConnAssert-      LongIdentifierTest.specsWith runConnAssertUseConf -- Have at least one test use the conf variant of connecting to Postgres, to improve test coverage.-      JSONTest.specs-      CustomConstraintTest.specs-      UpsertWhere.specs-      PgIntervalTest.specs-      ArrayAggTest.specs-      GeneratedColumnTestSQL.specsWith runConnAssert+        MpsNoPrefixTest.specsWith runConnAssert+        MpsCustomPrefixTest.specsWith runConnAssert+        EmptyEntityTest.specsWith+            runConnAssert+            (Just (runMigrationSilent EmptyEntityTest.migration))+        CompositeTest.specsWith runConnAssert+        TreeTest.specsWith runConnAssert+        PersistUniqueTest.specsWith runConnAssert+        PrimaryTest.specsWith runConnAssert+        CustomPersistFieldTest.specsWith runConnAssert+        CustomPrimaryKeyReferenceTest.specsWith runConnAssert+        MigrationColumnLengthTest.specsWith runConnAssert+        EquivalentTypeTestPostgres.specs+        TransactionLevelTest.specsWith runConnAssert+        LongIdentifierTest.specsWith runConnAssertUseConf -- Have at least one test use the conf variant of connecting to Postgres, to improve test coverage.+        JSONTest.specs+        CustomConstraintTest.specs+        UpsertWhere.specs+        PgIntervalTest.specs+        ArrayAggTest.specs+        GeneratedColumnTestSQL.specsWith runConnAssert