Compare commits

...

21 Commits

Author SHA1 Message Date
parsonsmatt
303c65f187 i hate this 2021-03-29 14:39:37 -06:00
parsonsmatt
6a6a63cb97 stylish haskell 2021-03-29 14:04:51 -06:00
parsonsmatt
7bc4fdd3d4 come on pls 2021-03-29 13:39:27 -06:00
parsonsmatt
8748923faa sigh 2021-03-29 13:20:03 -06:00
parsonsmatt
086dfb1f1e i miss file-watch 2021-03-29 13:14:50 -06:00
parsonsmatt
2ed58e3659 ok no really 2021-03-29 13:14:27 -06:00
parsonsmatt
d2d52566bb i think that should do it 2021-03-29 13:12:29 -06:00
parsonsmatt
cf7a6e50ae uh 2021-03-29 12:54:48 -06:00
parsonsmatt
776d15a8fb ci nonsense 2021-03-29 12:54:25 -06:00
parsonsmatt
56e0d7afe7 it passed? 2021-03-29 09:46:20 -06:00
parsonsmatt
4388cccbce merge 2021-03-29 09:25:21 -06:00
parsonsmatt
c8bfd619e9 no persistent-template dependency please 2021-03-29 09:25:02 -06:00
Matt Parsons
651380fc80
Merge branch 'master' into matt/persistent-2.12 2021-03-29 09:16:15 -06:00
parsonsmatt
3fdf631404 k 2021-03-26 17:28:57 -06:00
parsonsmatt
053420d3de fix gha 2021-03-26 17:23:27 -06:00
parsonsmatt
3292b7a7a0 minor bump 2021-03-26 16:58:11 -06:00
parsonsmatt
7bd4a524fd make the example work 2021-03-26 16:42:06 -06:00
parsonsmatt
2b5da6ab6f tests pass locally 2021-03-26 16:38:32 -06:00
parsonsmatt
4a546d2698 stuff 2021-03-26 16:03:24 -06:00
parsonsmatt
9ac73d93dc uhhh why are you like this 2021-03-26 14:55:00 -06:00
parsonsmatt
c8916cb493 run mysql tests 2021-03-26 14:40:57 -06:00
17 changed files with 457 additions and 333 deletions

View File

@ -487,5 +487,6 @@ user which can access it:
``` ```
mysql> CREATE DATABASE esqutest; mysql> CREATE DATABASE esqutest;
mysql> CREATE USER 'travis'@'localhost'; mysql> CREATE USER 'travis'@'localhost';
mysql> ALTER USER 'travis'@'localhost' IDENTIFIED BY 'esqutest';
mysql> GRANT ALL ON esqutest.* TO 'travis'; mysql> GRANT ALL ON esqutest.* TO 'travis';
``` ```

View File

@ -1 +1,25 @@
packages: . packages: .
source-repository-package
type: git
location: https://github.com/yesodweb/persistent
tag: 03e794f618b9b8daa5ae9c3a4edfe792642df55c
subdir: persistent
source-repository-package
type: git
location: https://github.com/yesodweb/persistent
tag: 03e794f618b9b8daa5ae9c3a4edfe792642df55c
subdir: persistent-postgresql
source-repository-package
type: git
location: https://github.com/yesodweb/persistent
tag: 03e794f618b9b8daa5ae9c3a4edfe792642df55c
subdir: persistent-mysql
source-repository-package
type: git
location: https://github.com/yesodweb/persistent
tag: 03e794f618b9b8daa5ae9c3a4edfe792642df55c
subdir: persistent-sqlite

View File

@ -1,3 +1,9 @@
3.4.2.0
=======
- @parsonsmatt
- [#243](https://github.com/bitemyapp/esqueleto/pull/243)
- Support `persistent-2.12`
3.4.1.1 3.4.1.1
======= =======
- @MaxGabriel - @MaxGabriel

View File

@ -1,7 +1,7 @@
cabal-version: 1.12 cabal-version: 1.12
name: esqueleto name: esqueleto
version: 3.4.1.1 version: 3.4.2.0
synopsis: Type-safe EDSL for SQL queries on persistent backends. synopsis: Type-safe EDSL for SQL queries on persistent backends.
description: @esqueleto@ is a bare bones, type-safe EDSL for SQL queries that works with unmodified @persistent@ SQL backends. Its language closely resembles SQL, so you don't have to learn new concepts, just new syntax, and it's fairly easy to predict the generated SQL and optimize it for your backend. Most kinds of errors committed when writing SQL are caught as compile-time errors---although it is possible to write type-checked @esqueleto@ queries that fail at runtime. description: @esqueleto@ is a bare bones, type-safe EDSL for SQL queries that works with unmodified @persistent@ SQL backends. Its language closely resembles SQL, so you don't have to learn new concepts, just new syntax, and it's fairly easy to predict the generated SQL and optimize it for your backend. Most kinds of errors committed when writing SQL are caught as compile-time errors---although it is possible to write type-checked @esqueleto@ queries that fail at runtime.
. .
@ -53,7 +53,7 @@ library
, conduit >=1.3 , conduit >=1.3
, containers , containers
, monad-logger , monad-logger
, persistent >=2.10.0 && <2.12 , persistent >=2.12 && <2.13
, resourcet >=1.2 , resourcet >=1.2
, tagged >=0.2 , tagged >=0.2
, text >=0.11 && <1.3 , text >=0.11 && <1.3
@ -100,7 +100,6 @@ test-suite mysql
, mysql-simple , mysql-simple
, persistent , persistent
, persistent-mysql , persistent-mysql
, persistent-template
, resourcet >=1.2 , resourcet >=1.2
, tagged >=0.2 , tagged >=0.2
, text >=0.11 && <1.3 , text >=0.11 && <1.3
@ -135,7 +134,6 @@ test-suite postgresql
, mtl , mtl
, persistent , persistent
, persistent-postgresql , persistent-postgresql
, persistent-template
, postgresql-libpq , postgresql-libpq
, postgresql-simple , postgresql-simple
, resourcet >=1.2 , resourcet >=1.2
@ -171,7 +169,6 @@ test-suite sqlite
, mtl , mtl
, persistent , persistent
, persistent-sqlite , persistent-sqlite
, persistent-template
, resourcet >=1.2 , resourcet >=1.2
, tagged >=0.2 , tagged >=0.2
, text >=0.11 && <1.3 , text >=0.11 && <1.3

View File

@ -11,7 +11,7 @@ module Blog
import Control.Monad.Base (MonadBase (..)) import Control.Monad.Base (MonadBase (..))
import Control.Monad.IO.Unlift (MonadUnliftIO(..), wrappedWithRunInIO) import Control.Monad.IO.Unlift (MonadUnliftIO(..), wrappedWithRunInIO)
import Control.Monad.Logger (MonadLogger, NoLoggingT (..)) import Control.Monad.Logger (MonadLoggerIO, MonadLogger, NoLoggingT (..))
import Control.Monad.Reader import Control.Monad.Reader
import Control.Monad.Trans.Control (ComposeSt, MonadBaseControl (..), import Control.Monad.Trans.Control (ComposeSt, MonadBaseControl (..),
MonadTransControl (..), MonadTransControl (..),
@ -26,6 +26,7 @@ newtype BlogT m a = BlogT { unBlogT :: NoLoggingT (ReaderT ConnectionString m) a
, MonadLogger , MonadLogger
, MonadReader ConnectionString , MonadReader ConnectionString
, MonadIO , MonadIO
, MonadLoggerIO
) )
instance MonadUnliftIO m => MonadUnliftIO (BlogT m) where instance MonadUnliftIO m => MonadUnliftIO (BlogT m) where

View File

@ -23,7 +23,7 @@ import Control.Monad (void)
import Control.Monad (forM_) import Control.Monad (forM_)
import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.IO.Unlift (MonadUnliftIO) import Control.Monad.IO.Unlift (MonadUnliftIO)
import Control.Monad.Logger (MonadLogger) import Control.Monad.Logger (MonadLogger, MonadLoggerIO)
import Control.Monad.Reader (MonadReader(..), runReaderT) import Control.Monad.Reader (MonadReader(..), runReaderT)
import Control.Monad.Trans.Control (MonadBaseControl) import Control.Monad.Trans.Control (MonadBaseControl)
import Data.Monoid ((<>)) import Data.Monoid ((<>))
@ -165,6 +165,7 @@ runDB :: (MonadReader ConnectionString m,
MonadIO m, MonadIO m,
MonadBaseControl IO m, MonadBaseControl IO m,
MonadUnliftIO m, MonadUnliftIO m,
MonadLoggerIO m,
MonadLogger m) MonadLogger m)
=> SqlPersistT m a -> m a => SqlPersistT m a -> m a
runDB query = do runDB query = do

View File

@ -227,6 +227,7 @@ import qualified Data.Text.Lazy.Builder as TLB
import Database.Esqueleto.Internal.Internal hiding (From, from, on) import Database.Esqueleto.Internal.Internal hiding (From, from, on)
import Database.Esqueleto.Internal.PersistentImport import Database.Esqueleto.Internal.PersistentImport
import GHC.TypeLits import GHC.TypeLits
import Database.Persist (EntityNameDB(..))
-- $setup -- $setup
-- --
@ -1040,7 +1041,7 @@ from parts = do
runFrom :: From a -> SqlQuery (a, FromClause) runFrom :: From a -> SqlQuery (a, FromClause)
runFrom e@Table = do runFrom e@Table = do
let ed = entityDef $ getVal e let ed = entityDef $ getVal e
ident <- newIdentFor (entityDB ed) ident <- newIdentFor . DBName . unEntityNameDB $ entityDB ed
let entity = EEntity ident let entity = EEntity ident
pure $ (entity, FromStart ident ed) pure $ (entity, FromStart ident ed)
where where

View File

@ -43,7 +43,7 @@ parseOnExpr sqlBackend text = do
-- with postgresql, mysql, and sqlite backends. -- with postgresql, mysql, and sqlite backends.
mkEscapeChar :: SqlBackend -> Either String Char mkEscapeChar :: SqlBackend -> Either String Char
mkEscapeChar sqlBackend = mkEscapeChar sqlBackend =
case Text.uncons (connEscapeName sqlBackend (DBName "")) of case Text.uncons (connEscapeRawName sqlBackend "") of
Nothing -> Nothing ->
Left "Failed to get an escape character from the SQL backend." Left "Failed to get an escape character from the SQL backend."
Just (c, _) -> Just (c, _) ->

View File

@ -56,6 +56,7 @@ import Data.Typeable (Typeable)
import Database.Esqueleto.Internal.ExprParser (TableAccess(..), parseOnExpr) import Database.Esqueleto.Internal.ExprParser (TableAccess(..), parseOnExpr)
import Database.Esqueleto.Internal.PersistentImport import Database.Esqueleto.Internal.PersistentImport
import qualified Database.Persist import qualified Database.Persist
import Database.Persist (FieldNameDB(..), EntityNameDB(..))
import Database.Persist.Sql.Util import Database.Persist.Sql.Util
( entityColumnCount ( entityColumnCount
, entityColumnNames , entityColumnNames
@ -64,6 +65,7 @@ import Database.Persist.Sql.Util
, parseEntityValues , parseEntityValues
) )
import Text.Blaze.Html (Html) import Text.Blaze.Html (Html)
import Data.Coerce (coerce)
-- | (Internal) Start a 'from' query with an entity. 'from' -- | (Internal) Start a 'from' query with an entity. 'from'
-- does two kinds of magic using 'fromStart', 'fromJoin' and -- does two kinds of magic using 'fromStart', 'fromJoin' and
@ -86,11 +88,14 @@ fromStart
=> SqlQuery (SqlExpr (PreprocessedFrom (SqlExpr (Entity a)))) => SqlQuery (SqlExpr (PreprocessedFrom (SqlExpr (Entity a))))
fromStart = do fromStart = do
let ed = entityDef (Proxy :: Proxy a) let ed = entityDef (Proxy :: Proxy a)
ident <- newIdentFor (entityDB ed) ident <- newIdentFor (coerce $ entityDB ed)
let ret = EEntity ident let ret = EEntity ident
f' = FromStart ident ed f' = FromStart ident ed
return (EPreprocessedFrom ret f') return (EPreprocessedFrom ret f')
-- | Copied from @persistent@
newtype DBName = DBName { unDBName :: T.Text }
-- | (Internal) Same as 'fromStart', but entity may be missing. -- | (Internal) Same as 'fromStart', but entity may be missing.
fromStartMaybe fromStartMaybe
:: ( PersistEntity a :: ( PersistEntity a
@ -568,7 +573,7 @@ e ^. field
] ]
fieldIdent = fieldIdent =
case e of case e of
EEntity _ -> fromDBName info (fieldDB fieldDef) EEntity _ -> fromDBName info (coerce $ fieldDB fieldDef)
EAliasedEntity baseI _ -> useIdent info $ aliasedEntityColumnIdent baseI fieldDef EAliasedEntity baseI _ -> useIdent info $ aliasedEntityColumnIdent baseI fieldDef
EAliasedEntityReference a b -> EAliasedEntityReference a b ->
error $ unwords error $ unwords
@ -1805,7 +1810,7 @@ instance Show FromClause where
where where
dummy = SqlBackend dummy = SqlBackend
{ connEscapeName = \(DBName x) -> x { connEscapeRawName = id
} }
render' = T.unpack . renderExpr dummy render' = T.unpack . renderExpr dummy
@ -2124,7 +2129,7 @@ instance ToSomeValues (SqlExpr (Value a)) where
fieldName fieldName
:: (PersistEntity val, PersistField typ) :: (PersistEntity val, PersistField typ)
=> IdentInfo -> EntityField val typ -> TLB.Builder => IdentInfo -> EntityField val typ -> TLB.Builder
fieldName info = fromDBName info . fieldDB . persistFieldDef fieldName info = fromDBName info . coerce . fieldDB . persistFieldDef
-- FIXME: Composite/non-id pKS not supported on set -- FIXME: Composite/non-id pKS not supported on set
setAux setAux
@ -2140,7 +2145,7 @@ sub :: PersistField a => Mode -> SqlQuery (SqlExpr (Value a)) -> SqlExpr (Value
sub mode query = ERaw Parens $ \info -> toRawSql mode info query sub mode query = ERaw Parens $ \info -> toRawSql mode info query
fromDBName :: IdentInfo -> DBName -> TLB.Builder fromDBName :: IdentInfo -> DBName -> TLB.Builder
fromDBName (conn, _) = TLB.fromText . connEscapeName conn fromDBName (conn, _) = TLB.fromText . connEscapeRawName conn . unDBName
existsHelper :: SqlQuery () -> SqlExpr (Value Bool) existsHelper :: SqlQuery () -> SqlExpr (Value Bool)
existsHelper = sub SELECT . (>> return true) existsHelper = sub SELECT . (>> return true)
@ -2900,7 +2905,7 @@ makeFrom info mode fs = ret
(useIdent info ident, mempty) (useIdent info ident, mempty)
base ident@(I identText) def = base ident@(I identText) def =
let db@(DBName dbText) = entityDB def let db@(DBName dbText) = coerce $ entityDB def
in ( fromDBName info db <> in ( fromDBName info db <>
if dbText == identText if dbText == identText
then mempty then mempty
@ -3030,7 +3035,7 @@ valueReferenceToRawSql sourceIdent columnIdentF info =
aliasedEntityColumnIdent :: Ident -> FieldDef -> Ident aliasedEntityColumnIdent :: Ident -> FieldDef -> Ident
aliasedEntityColumnIdent (I baseIdent) field = aliasedEntityColumnIdent (I baseIdent) field =
I (baseIdent <> "_" <> (unDBName $ fieldDB field)) I (baseIdent <> "_" <> (unDBName $ coerce $ fieldDB field))
aliasedColumnName :: Ident -> IdentInfo -> T.Text -> TLB.Builder aliasedColumnName :: Ident -> IdentInfo -> T.Text -> TLB.Builder
aliasedColumnName (I baseIdent) info columnName = aliasedColumnName (I baseIdent) info columnName =
@ -3064,11 +3069,11 @@ instance SqlSelect (SqlExpr InsertFinal) InsertFinal where
sqlInsertInto info (EInsertFinal (EInsert p _)) = sqlInsertInto info (EInsertFinal (EInsert p _)) =
let fields = let fields =
uncommas $ uncommas $
map (fromDBName info . fieldDB) $ map (fromDBName info . coerce . fieldDB) $
entityFields $ entityFields $
entityDef p entityDef p
table = table =
fromDBName info . entityDB . entityDef $ p fromDBName info . DBName . coerce . entityDB . entityDef $ p
in in
("INSERT INTO " <> table <> parens fields <> "\n", []) ("INSERT INTO " <> table <> parens fields <> "\n", [])
sqlSelectCols info (EInsertFinal (EInsert _ f)) = f info sqlSelectCols info (EInsertFinal (EInsert _ f)) = f info
@ -3084,8 +3089,8 @@ instance SqlSelect () () where
unescapedColumnNames :: EntityDef -> [DBName] unescapedColumnNames :: EntityDef -> [DBName]
unescapedColumnNames ent = unescapedColumnNames ent =
(if hasCompositeKey ent then id else ( fieldDB (entityId ent) :)) (if hasCompositeKey ent then id else ( coerce (fieldDB (entityId ent)) :))
$ map fieldDB (entityFields ent) $ map (coerce . fieldDB) (entityFields ent)
-- | You may return an 'Entity' from a 'select' query. -- | You may return an 'Entity' from a 'select' query.
instance PersistEntity a => SqlSelect (SqlExpr (Entity a)) (Entity a) where instance PersistEntity a => SqlSelect (SqlExpr (Entity a)) (Entity a) where

View File

@ -53,7 +53,6 @@ module Database.Esqueleto.Internal.PersistentImport
rawQuery, rawQuery,
rawQueryRes, rawQueryRes,
rawSql, rawSql,
askLogFunc,
close', close',
createSqlPool, createSqlPool,
liftSqlPersistMPool, liftSqlPersistMPool,
@ -118,7 +117,6 @@ module Database.Esqueleto.Internal.PersistentImport
Attr, Attr,
Checkmark(..), Checkmark(..),
CompositeDef(..), CompositeDef(..),
DBName(..),
EmbedEntityDef(..), EmbedEntityDef(..),
EmbedFieldDef(..), EmbedFieldDef(..),
EntityDef(..), EntityDef(..),
@ -127,7 +125,6 @@ module Database.Esqueleto.Internal.PersistentImport
FieldType(..), FieldType(..),
ForeignDef(..), ForeignDef(..),
ForeignFieldDef, ForeignFieldDef,
HaskellName(..),
IsNullable(..), IsNullable(..),
OnlyUniqueException(..), OnlyUniqueException(..),
PersistException(..), PersistException(..),

View File

@ -47,6 +47,7 @@ import Data.Time.Clock (UTCTime)
import Database.Esqueleto.Internal.Internal hiding (random_) import Database.Esqueleto.Internal.Internal hiding (random_)
import Database.Esqueleto.Internal.PersistentImport hiding (upsert, upsertBy) import Database.Esqueleto.Internal.PersistentImport hiding (upsert, upsertBy)
import Database.Persist.Class (OnlyOneUniqueKey) import Database.Persist.Class (OnlyOneUniqueKey)
import Database.Persist (ConstraintNameDB(..), EntityNameDB(..))
-- | (@random()@) Split out into database specific modules -- | (@random()@) Split out into database specific modules
-- because MySQL uses `rand()`. -- because MySQL uses `rand()`.
@ -306,10 +307,10 @@ insertSelectWithConflictCount unique query conflictQuery = do
updates = conflictQuery entCurrent entExcluded updates = conflictQuery entCurrent entExcluded
combine (tlb1,vals1) (tlb2,vals2) = (builderToText (tlb1 `mappend` tlb2), vals1 ++ vals2) combine (tlb1,vals1) (tlb2,vals2) = (builderToText (tlb1 `mappend` tlb2), vals1 ++ vals2)
entExcluded = EEntity $ I "excluded" entExcluded = EEntity $ I "excluded"
tableName = unDBName . entityDB . entityDef tableName = unEntityNameDB . entityDB . entityDef
entCurrent = EEntity $ I (tableName proxy) entCurrent = EEntity $ I (tableName proxy)
uniqueDef = toUniqueDef unique uniqueDef = toUniqueDef unique
constraint = TLB.fromText . unDBName . uniqueDBName $ uniqueDef constraint = TLB.fromText . unConstraintNameDB . uniqueDBName $ uniqueDef
renderedUpdates :: (BackendCompatible SqlBackend backend) => backend -> (TLB.Builder, [PersistValue]) renderedUpdates :: (BackendCompatible SqlBackend backend) => backend -> (TLB.Builder, [PersistValue])
renderedUpdates conn = renderUpdates conn updates renderedUpdates conn = renderUpdates conn updates
conflict conn = (mconcat ([ conflict conn = (mconcat ([

View File

@ -87,7 +87,7 @@ instance IsString JSONAccessor where
-- | @since 3.1.0 -- | @since 3.1.0
instance (FromJSON a, ToJSON a) => PersistField (JSONB a) where instance (FromJSON a, ToJSON a) => PersistField (JSONB a) where
toPersistValue = PersistDbSpecific . BSL.toStrict . encode . unJSONB toPersistValue = PersistLiteralEscaped . BSL.toStrict . encode . unJSONB
fromPersistValue pVal = fmap JSONB $ case pVal of fromPersistValue pVal = fmap JSONB $ case pVal of
PersistByteString bs -> first (badParse $ TE.decodeUtf8 bs) $ eitherDecodeStrict bs PersistByteString bs -> first (badParse $ TE.decodeUtf8 bs) $ eitherDecodeStrict bs
PersistText t -> first (badParse t) $ eitherDecodeStrict (TE.encodeUtf8 t) PersistText t -> first (badParse t) $ eitherDecodeStrict (TE.encodeUtf8 t)

View File

@ -5,8 +5,11 @@ packages:
- 'examples' - 'examples'
extra-deps: extra-deps:
- persistent-2.11.0.0 - git: https://www.github.com/yesodweb/persistent
- persistent-template-2.9.1.0 commit: 03e794f618b9b8daa5ae9c3a4edfe792642df55c
- persistent-mysql-2.10.3 subdirs:
- persistent-postgresql-2.11.0.0 - persistent
- persistent-sqlite-2.11.0.0 - persistent-template
- persistent-mysql
- persistent-postgresql
- persistent-sqlite

View File

@ -71,7 +71,7 @@ import Data.Time
import Control.Monad.Fail (MonadFail) import Control.Monad.Fail (MonadFail)
#endif #endif
import Control.Monad.IO.Class (MonadIO(liftIO)) import Control.Monad.IO.Class (MonadIO(liftIO))
import Control.Monad.Logger (MonadLogger(..), NoLoggingT, runNoLoggingT) import Control.Monad.Logger (MonadLoggerIO(..), MonadLogger(..), NoLoggingT, runNoLoggingT)
import Control.Monad.Trans.Reader (ReaderT) import Control.Monad.Trans.Reader (ReaderT)
import qualified Data.Attoparsec.Text as AP import qualified Data.Attoparsec.Text as AP
import Data.Char (toLower, toUpper) import Data.Char (toLower, toUpper)
@ -2507,6 +2507,7 @@ insert' v = flip Entity v <$> insert v
type RunDbMonad m = ( MonadUnliftIO m type RunDbMonad m = ( MonadUnliftIO m
, MonadIO m , MonadIO m
, MonadLoggerIO m
, MonadLogger m , MonadLogger m
, MonadCatch m ) , MonadCatch m )

View File

@ -1,27 +1,30 @@
{-# LANGUAGE ScopedTypeVariables {-# LANGUAGE FlexibleContexts #-}
, FlexibleContexts {-# LANGUAGE RankNTypes #-}
, RankNTypes {-# LANGUAGE ScopedTypeVariables #-}
, TypeFamilies {-# LANGUAGE TypeApplications #-}
, TypeApplications {-# LANGUAGE TypeFamilies #-}
#-}
module Main (main) where module Main (main) where
import Control.Applicative
import Control.Monad (void) import Control.Monad (void)
import Control.Monad.IO.Class (MonadIO(liftIO)) import Control.Monad.IO.Class (MonadIO(liftIO))
import Control.Monad.Logger (runStderrLoggingT, runNoLoggingT) import Control.Monad.Logger (runNoLoggingT, runStderrLoggingT)
import Control.Monad.Trans.Reader (ReaderT) import Control.Monad.Trans.Reader (ReaderT)
import Database.Persist.MySQL ( withMySQLConn import qualified Control.Monad.Trans.Resource as R
, connectHost
, connectDatabase
, connectUser
, connectPassword
, connectPort
, defaultConnectInfo)
import Database.Esqueleto import Database.Esqueleto
import Database.Esqueleto.Experimental hiding (from, on) import Database.Esqueleto.Experimental hiding (from, on)
import qualified Database.Esqueleto.Experimental as Experimental import qualified Database.Esqueleto.Experimental as Experimental
import qualified Control.Monad.Trans.Resource as R import Database.Persist.MySQL
( connectDatabase
, connectHost
, connectPassword
, connectPort
, connectUser
, defaultConnectInfo
, withMySQLConn
)
import System.Environment
import Test.Hspec import Test.Hspec
import Common.Test import Common.Test
@ -237,12 +240,31 @@ migrateIt = do
withConn :: RunDbMonad m => (SqlBackend -> R.ResourceT m a) -> m a withConn :: RunDbMonad m => (SqlBackend -> R.ResourceT m a) -> m a
withConn = withConn f = do
R.runResourceT . ci <- liftIO isCI
withMySQLConn defaultConnectInfo let connInfo
{ connectHost = "127.0.0.1" | ci =
, connectUser = "travis" defaultConnectInfo
, connectPassword = "esqutest" { connectHost = "127.0.0.1"
, connectDatabase = "esqutest" , connectUser = "travis"
, connectPort = 33306 , connectPassword = "esqutest"
} , connectDatabase = "esqutest"
, connectPort = 33306
}
| otherwise =
defaultConnectInfo
{ connectHost = "localhost"
, connectUser = "travis"
, connectPassword = "esqutest"
, connectDatabase = "esqutest"
, connectPort = 3306
}
R.runResourceT $ withMySQLConn connInfo f
isCI :: IO Bool
isCI = do
env <- getEnvironment
return $ case lookup "TRAVIS" env <|> lookup "CI" env of
Just "true" -> True
_ -> False

View File

@ -30,6 +30,7 @@ import Common.Test (RunDbMonad)
share [mkPersist sqlSettings, mkMigrate "migrateJSON"] [persistUpperCase| share [mkPersist sqlSettings, mkMigrate "migrateJSON"] [persistUpperCase|
Json Json
value (JSONB Value) value (JSONB Value)
deriving Show
|] |]
cleanJSON cleanJSON

View File

@ -1,47 +1,47 @@
{-# OPTIONS_GHC -fno-warn-unused-binds #-} {-# OPTIONS_GHC -fno-warn-unused-binds #-}
{-# LANGUAGE FlexibleContexts {-# LANGUAGE FlexibleContexts #-}
, LambdaCase {-# LANGUAGE LambdaCase #-}
, NamedFieldPuns {-# LANGUAGE NamedFieldPuns #-}
, OverloadedStrings {-# LANGUAGE OverloadedStrings #-}
, RankNTypes {-# LANGUAGE PartialTypeSignatures #-}
, ScopedTypeVariables {-# LANGUAGE RankNTypes #-}
, TypeApplications {-# LANGUAGE ScopedTypeVariables #-}
, TypeFamilies {-# LANGUAGE TypeApplications #-}
, PartialTypeSignatures {-# LANGUAGE TypeFamilies #-}
#-}
module Main (main) where module Main (main) where
import Data.Coerce
import Data.Foldable
import qualified Data.Map.Strict as Map
import Data.Map (Map)
import Data.Time
import Control.Arrow ((&&&)) import Control.Arrow ((&&&))
import Control.Monad (void, when) import Control.Monad (void, when)
import Control.Monad.Catch (MonadCatch, catch) import Control.Monad.Catch
import Control.Monad.IO.Class (MonadIO(liftIO)) import Control.Monad.IO.Class (MonadIO(liftIO))
import Control.Monad.Logger (runStderrLoggingT, runNoLoggingT) import Control.Monad.Logger (runNoLoggingT, runStderrLoggingT)
import Control.Monad.Trans.Reader (ReaderT, ask) import Control.Monad.Trans.Reader (ReaderT, ask)
import qualified Control.Monad.Trans.Resource as R import qualified Control.Monad.Trans.Resource as R
import Data.Aeson hiding (Value) import Data.Aeson hiding (Value)
import qualified Data.Aeson as A (Value) import qualified Data.Aeson as A (Value)
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as BSL
import qualified Data.Char as Char import qualified Data.Char as Char
import Data.Coerce
import Data.Foldable
import qualified Data.List as L import qualified Data.List as L
import Data.Map (Map)
import qualified Data.Map.Strict as Map
import Data.Ord (comparing) import Data.Ord (comparing)
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Encoding as TE import qualified Data.Text.Encoding as TE
import Data.Time.Clock (getCurrentTime, diffUTCTime, UTCTime) import Data.Time
import Data.Time.Clock (UTCTime, diffUTCTime, getCurrentTime)
import Database.Esqueleto hiding (random_) import Database.Esqueleto hiding (random_)
import Database.Esqueleto.Experimental hiding (random_, from, on) import Database.Esqueleto.Experimental hiding (from, on, random_)
import qualified Database.Esqueleto.Experimental as Experimental import qualified Database.Esqueleto.Experimental as Experimental
import qualified Database.Esqueleto.Internal.Sql as ES import qualified Database.Esqueleto.Internal.Sql as ES
import Database.Esqueleto.PostgreSQL (random_) import Database.Esqueleto.PostgreSQL (random_)
import qualified Database.Esqueleto.PostgreSQL as EP import qualified Database.Esqueleto.PostgreSQL as EP
import Database.Esqueleto.PostgreSQL.JSON hiding ((?.), (-.), (||.)) import Database.Esqueleto.PostgreSQL.JSON hiding ((-.), (?.), (||.))
import qualified Database.Esqueleto.PostgreSQL.JSON as JSON import qualified Database.Esqueleto.PostgreSQL.JSON as JSON
import Database.Persist.Postgresql (withPostgresqlConn) import Database.Persist.Postgresql (withPostgresqlConn)
import Database.PostgreSQL.Simple (SqlError(..), ExecStatus(..)) import Database.PostgreSQL.Simple (ExecStatus(..), SqlError(..))
import System.Environment import System.Environment
import Test.Hspec import Test.Hspec
import Test.Hspec.QuickCheck import Test.Hspec.QuickCheck
@ -570,11 +570,6 @@ testPostgresModule = do
-- | Get the time diff and check it's less than a second -- | Get the time diff and check it's less than a second
liftIO $ diffUTCTime nowUtc now `shouldSatisfy` (< oneSecond) liftIO $ diffUTCTime nowUtc now `shouldSatisfy` (< oneSecond)
--------------- JSON --------------- JSON --------------- JSON ---------------
--------------- JSON --------------- JSON --------------- JSON ---------------
--------------- JSON --------------- JSON --------------- JSON ---------------
testJSONInsertions :: Spec testJSONInsertions :: Spec
testJSONInsertions = testJSONInsertions =
describe "JSON Insertions" $ do describe "JSON Insertions" $ do
@ -619,14 +614,14 @@ testArrowJSONB =
createSaneSQL @JSONValue createSaneSQL @JSONValue
(jsonbVal (object ["a" .= True]) ->. "a") (jsonbVal (object ["a" .= True]) ->. "a")
"SELECT (? -> ?)\nFROM \"Json\"\n" "SELECT (? -> ?)\nFROM \"Json\"\n"
[ PersistDbSpecific "{\"a\":true}" [ PersistLiteralEscaped "{\"a\":true}"
, PersistText "a" ] , PersistText "a" ]
it "creates sane SQL (chained)" $ do it "creates sane SQL (chained)" $ do
let obj = object ["a" .= [1 :: Int,2,3]] let obj = object ["a" .= [1 :: Int,2,3]]
createSaneSQL @JSONValue createSaneSQL @JSONValue
(jsonbVal obj ->. "a" ->. 1) (jsonbVal obj ->. "a" ->. 1)
"SELECT ((? -> ?) -> ?)\nFROM \"Json\"\n" "SELECT ((? -> ?) -> ?)\nFROM \"Json\"\n"
[ PersistDbSpecific "{\"a\":[1,2,3]}" [ PersistLiteralEscaped "{\"a\":[1,2,3]}"
, PersistText "a" , PersistText "a"
, PersistInt64 1 ] , PersistInt64 1 ]
it "works as expected" $ run $ do it "works as expected" $ run $ do
@ -644,14 +639,14 @@ testArrowText =
createSaneSQL createSaneSQL
(jsonbVal (object ["a" .= True]) ->>. "a") (jsonbVal (object ["a" .= True]) ->>. "a")
"SELECT (? ->> ?)\nFROM \"Json\"\n" "SELECT (? ->> ?)\nFROM \"Json\"\n"
[ PersistDbSpecific "{\"a\":true}" [ PersistLiteralEscaped "{\"a\":true}"
, PersistText "a" ] , PersistText "a" ]
it "creates sane SQL (chained)" $ do it "creates sane SQL (chained)" $ do
let obj = object ["a" .= [1 :: Int,2,3]] let obj = object ["a" .= [1 :: Int,2,3]]
createSaneSQL createSaneSQL
(jsonbVal obj ->. "a" ->>. 1) (jsonbVal obj ->. "a" ->>. 1)
"SELECT ((? -> ?) ->> ?)\nFROM \"Json\"\n" "SELECT ((? -> ?) ->> ?)\nFROM \"Json\"\n"
[ PersistDbSpecific "{\"a\":[1,2,3]}" [ PersistLiteralEscaped "{\"a\":[1,2,3]}"
, PersistText "a" , PersistText "a"
, PersistInt64 1 ] , PersistInt64 1 ]
it "works as expected" $ run $ do it "works as expected" $ run $ do
@ -670,14 +665,14 @@ testHashArrowJSONB =
createSaneSQL @JSONValue createSaneSQL @JSONValue
(jsonbVal (object ["a" .= True]) #>. list) (jsonbVal (object ["a" .= True]) #>. list)
"SELECT (? #> ?)\nFROM \"Json\"\n" "SELECT (? #> ?)\nFROM \"Json\"\n"
[ PersistDbSpecific "{\"a\":true}" [ PersistLiteralEscaped "{\"a\":true}"
, persistTextArray list ] , persistTextArray list ]
it "creates sane SQL (chained)" $ do it "creates sane SQL (chained)" $ do
let obj = object ["a" .= [object ["b" .= True]]] let obj = object ["a" .= [object ["b" .= True]]]
createSaneSQL @JSONValue createSaneSQL @JSONValue
(jsonbVal obj #>. ["a","1"] #>. ["b"]) (jsonbVal obj #>. ["a","1"] #>. ["b"])
"SELECT ((? #> ?) #> ?)\nFROM \"Json\"\n" "SELECT ((? #> ?) #> ?)\nFROM \"Json\"\n"
[ PersistDbSpecific "{\"a\":[{\"b\":true}]}" [ PersistLiteralEscaped "{\"a\":[{\"b\":true}]}"
, persistTextArray ["a","1"] , persistTextArray ["a","1"]
, persistTextArray ["b"] ] , persistTextArray ["b"] ]
it "works as expected" $ run $ do it "works as expected" $ run $ do
@ -696,14 +691,14 @@ testHashArrowText =
createSaneSQL createSaneSQL
(jsonbVal (object ["a" .= True]) #>>. list) (jsonbVal (object ["a" .= True]) #>>. list)
"SELECT (? #>> ?)\nFROM \"Json\"\n" "SELECT (? #>> ?)\nFROM \"Json\"\n"
[ PersistDbSpecific "{\"a\":true}" [ PersistLiteralEscaped "{\"a\":true}"
, persistTextArray list ] , persistTextArray list ]
it "creates sane SQL (chained)" $ do it "creates sane SQL (chained)" $ do
let obj = object ["a" .= [object ["b" .= True]]] let obj = object ["a" .= [object ["b" .= True]]]
createSaneSQL createSaneSQL
(jsonbVal obj #>. ["a","1"] #>>. ["b"]) (jsonbVal obj #>. ["a","1"] #>>. ["b"])
"SELECT ((? #> ?) #>> ?)\nFROM \"Json\"\n" "SELECT ((? #> ?) #>> ?)\nFROM \"Json\"\n"
[ PersistDbSpecific "{\"a\":[{\"b\":true}]}" [ PersistLiteralEscaped "{\"a\":[{\"b\":true}]}"
, persistTextArray ["a","1"] , persistTextArray ["a","1"]
, persistTextArray ["b"] ] , persistTextArray ["b"] ]
it "works as expected" $ run $ do it "works as expected" $ run $ do
@ -725,130 +720,155 @@ testFilterOperators =
testInclusion :: Spec testInclusion :: Spec
testInclusion = do testInclusion = do
describe "@>" $ do describe "@>" $ do
it "creates sane SQL" $ it "creates sane SQL" $ do
createSaneSQL let obj = object ["a" .= False, "b" .= True]
(jsonbVal (object ["a" .= False, "b" .= True]) @>. jsonbVal (object ["a" .= False])) encoded = BSL.toStrict $ encode obj
"SELECT (? @> ?)\nFROM \"Json\"\n" createSaneSQL
[ PersistDbSpecific "{\"a\":false,\"b\":true}" (jsonbVal obj @>. jsonbVal (object ["a" .= False]))
, PersistDbSpecific "{\"a\":false}" ] "SELECT (? @> ?)\nFROM \"Json\"\n"
it "creates sane SQL (chained)" $ do [ PersistLiteralEscaped encoded
let obj = object ["a" .= [object ["b" .= True]]] , PersistLiteralEscaped "{\"a\":false}"
createSaneSQL ]
(jsonbVal obj ->. "a" @>. jsonbVal (object ["b" .= True])) it "creates sane SQL (chained)" $ do
"SELECT ((? -> ?) @> ?)\nFROM \"Json\"\n" let obj = object ["a" .= [object ["b" .= True]]]
[ PersistDbSpecific "{\"a\":[{\"b\":true}]}" encoded = BSL.toStrict $ encode obj
, PersistText "a" createSaneSQL
, PersistDbSpecific "{\"b\":true}" ] (jsonbVal obj ->. "a" @>. jsonbVal (object ["b" .= True]))
it "works as expected" $ run $ do "SELECT ((? -> ?) @> ?)\nFROM \"Json\"\n"
x <- selectJSONwhere $ \v -> v @>. jsonbVal (Number 1) [ PersistLiteralEscaped encoded
y <- selectJSONwhere $ \v -> v @>. jsonbVal (toJSON [object ["a" .= Number 3.14]]) , PersistText "a"
z <- selectJSONwhere $ \v -> v ->. 1 @>. jsonbVal (object ["a" .= Number 3.14]) , PersistLiteralEscaped "{\"b\":true}"
liftIO $ length x `shouldBe` 2 ]
liftIO $ length y `shouldBe` 1 it "works as expected" $ run $ do
liftIO $ length z `shouldBe` 1 x <- selectJSONwhere $ \v -> v @>. jsonbVal (Number 1)
describe "<@" $ do y <- selectJSONwhere $ \v -> v @>. jsonbVal (toJSON [object ["a" .= Number 3.14]])
it "creates sane SQL" $ z <- selectJSONwhere $ \v -> v ->. 1 @>. jsonbVal (object ["a" .= Number 3.14])
createSaneSQL liftIO $ length x `shouldBe` 2
(jsonbVal (object ["a" .= False]) <@. jsonbVal (object ["a" .= False, "b" .= True])) liftIO $ length y `shouldBe` 1
"SELECT (? <@ ?)\nFROM \"Json\"\n" liftIO $ length z `shouldBe` 1
[ PersistDbSpecific "{\"a\":false}" describe "<@" $ do
, PersistDbSpecific "{\"a\":false,\"b\":true}" ] it "creates sane SQL" $ do
it "creates sane SQL (chained)" $ do let obj = object ["a" .= False, "b" .= True]
let obj = object ["a" .= [object ["b" .= True]]] encoded = BSL.toStrict $ encode obj
createSaneSQL createSaneSQL
(jsonbVal obj ->. "a" <@. jsonbVal (object ["b" .= True, "c" .= Null])) (jsonbVal (object ["a" .= False]) <@. jsonbVal obj )
"SELECT ((? -> ?) <@ ?)\nFROM \"Json\"\n" "SELECT (? <@ ?)\nFROM \"Json\"\n"
[ PersistDbSpecific "{\"a\":[{\"b\":true}]}" [ PersistLiteralEscaped "{\"a\":false}"
, PersistText "a" , PersistLiteralEscaped encoded
, PersistDbSpecific "{\"b\":true,\"c\":null}" ] ]
it "works as expected" $ run $ do it "creates sane SQL (chained)" $ do
x <- selectJSONwhere $ \v -> v <@. jsonbVal (toJSON [Number 1]) let obj = object ["a" .= [object ["b" .= True]]]
y <- selectJSONwhere $ \v -> v <@. jsonbVal (object ["a" .= (1 :: Int), "b" .= False, "c" .= Null]) obj' = object ["b" .= True, "c" .= Null]
z <- selectJSONwhere $ \v -> v #>. ["a","b"] <@. jsonbVal (object ["b" .= False, "c" .= String "message"]) encoded = BSL.toStrict $ encode obj'
liftIO $ length x `shouldBe` 2 createSaneSQL
liftIO $ length y `shouldBe` 1 (jsonbVal obj ->. "a" <@. jsonbVal obj')
liftIO $ length z `shouldBe` 1 "SELECT ((? -> ?) <@ ?)\nFROM \"Json\"\n"
[ PersistLiteralEscaped "{\"a\":[{\"b\":true}]}"
, PersistText "a"
, PersistLiteralEscaped encoded
]
it "works as expected" $ run $ do
x <- selectJSONwhere $ \v -> v <@. jsonbVal (toJSON [Number 1])
y <- selectJSONwhere $ \v -> v <@. jsonbVal (object ["a" .= (1 :: Int), "b" .= False, "c" .= Null])
z <- selectJSONwhere $ \v -> v #>. ["a","b"] <@. jsonbVal (object ["b" .= False, "c" .= String "message"])
liftIO $ length x `shouldBe` 2
liftIO $ length y `shouldBe` 1
liftIO $ length z `shouldBe` 1
testQMark :: Spec testQMark :: Spec
testQMark = testQMark = do
describe "Question Mark" $ do describe "Question Mark" $ do
it "creates sane SQL" $ it "creates sane SQL" $ do
createSaneSQL let obj = object ["a" .= False, "b" .= True]
(jsonbVal (object ["a" .= False, "b" .= True]) JSON.?. "a") encoded = BSL.toStrict $ encode obj
"SELECT (? ?? ?)\nFROM \"Json\"\n" createSaneSQL
[ PersistDbSpecific "{\"a\":false,\"b\":true}" (jsonbVal obj JSON.?. "a")
, PersistText "a" ] "SELECT (? ?? ?)\nFROM \"Json\"\n"
it "creates sane SQL (chained)" $ do [ PersistLiteralEscaped encoded
let obj = object ["a" .= [object ["b" .= True]]] , PersistText "a"
createSaneSQL ]
(jsonbVal obj #>. ["a","0"] JSON.?. "b") it "creates sane SQL (chained)" $ do
"SELECT ((? #> ?) ?? ?)\nFROM \"Json\"\n" let obj = object ["a" .= [object ["b" .= True]]]
[ PersistDbSpecific "{\"a\":[{\"b\":true}]}" encoded = BSL.toStrict $ encode obj
, persistTextArray ["a","0"] createSaneSQL
, PersistText "b" ] (jsonbVal obj #>. ["a","0"] JSON.?. "b")
it "works as expected" $ run $ do "SELECT ((? #> ?) ?? ?)\nFROM \"Json\"\n"
x <- selectJSONwhere (JSON.?. "a") [ PersistLiteralEscaped encoded
y <- selectJSONwhere (JSON.?. "test") , persistTextArray ["a","0"]
z <- selectJSONwhere $ \v -> v ->. "a" JSON.?. "b" , PersistText "b"
liftIO $ length x `shouldBe` 2 ]
liftIO $ length y `shouldBe` 2 it "works as expected" $ run $ do
liftIO $ length z `shouldBe` 1 x <- selectJSONwhere (JSON.?. "a")
y <- selectJSONwhere (JSON.?. "test")
z <- selectJSONwhere $ \v -> v ->. "a" JSON.?. "b"
liftIO $ length x `shouldBe` 2
liftIO $ length y `shouldBe` 2
liftIO $ length z `shouldBe` 1
testQMarkAny :: Spec testQMarkAny :: Spec
testQMarkAny = testQMarkAny = do
describe "Question Mark (Any)" $ do describe "Question Mark (Any)" $ do
it "creates sane SQL" $ it "creates sane SQL" $ do
createSaneSQL let obj = (object ["a" .= False, "b" .= True])
(jsonbVal (object ["a" .= False, "b" .= True]) ?|. ["a","c"]) encoded = BSL.toStrict $ encode obj
"SELECT (? ??| ?)\nFROM \"Json\"\n" createSaneSQL
[ PersistDbSpecific "{\"a\":false,\"b\":true}" (jsonbVal obj ?|. ["a","c"])
, persistTextArray ["a","c"] ] "SELECT (? ??| ?)\nFROM \"Json\"\n"
it "creates sane SQL (chained)" $ do [ PersistLiteralEscaped encoded
let obj = object ["a" .= [object ["b" .= True]]] , persistTextArray ["a","c"]
createSaneSQL ]
(jsonbVal obj #>. ["a","0"] ?|. ["b","c"]) it "creates sane SQL (chained)" $ do
"SELECT ((? #> ?) ??| ?)\nFROM \"Json\"\n" let obj = object ["a" .= [object ["b" .= True]]]
[ PersistDbSpecific "{\"a\":[{\"b\":true}]}" encoded = BSL.toStrict $ encode obj
, persistTextArray ["a","0"] createSaneSQL
, persistTextArray ["b","c"] ] (jsonbVal obj #>. ["a","0"] ?|. ["b","c"])
it "works as expected" $ run $ do "SELECT ((? #> ?) ??| ?)\nFROM \"Json\"\n"
x <- selectJSONwhere (?|. ["b","test"]) [ PersistLiteralEscaped encoded
y <- selectJSONwhere (?|. ["a"]) , persistTextArray ["a","0"]
z <- selectJSONwhere $ \v -> v ->. (-3) ?|. ["a"] , persistTextArray ["b","c"]
w <- selectJSONwhere (?|. []) ]
liftIO $ length x `shouldBe` 3 it "works as expected" $ run $ do
liftIO $ length y `shouldBe` 2 x <- selectJSONwhere (?|. ["b","test"])
liftIO $ length z `shouldBe` 1 y <- selectJSONwhere (?|. ["a"])
liftIO $ length w `shouldBe` 0 z <- selectJSONwhere $ \v -> v ->. (-3) ?|. ["a"]
w <- selectJSONwhere (?|. [])
liftIO $ length x `shouldBe` 3
liftIO $ length y `shouldBe` 2
liftIO $ length z `shouldBe` 1
liftIO $ length w `shouldBe` 0
testQMarkAll :: Spec testQMarkAll :: Spec
testQMarkAll = testQMarkAll = do
describe "Question Mark (All)" $ do describe "Question Mark (All)" $ do
it "creates sane SQL" $ it "creates sane SQL" $ do
createSaneSQL let obj = object ["a" .= False, "b" .= True]
(jsonbVal (object ["a" .= False, "b" .= True]) ?&. ["a","c"]) encoded = BSL.toStrict $ encode obj
"SELECT (? ??& ?)\nFROM \"Json\"\n" createSaneSQL
[ PersistDbSpecific "{\"a\":false,\"b\":true}" (jsonbVal obj ?&. ["a","c"])
, persistTextArray ["a","c"] ] "SELECT (? ??& ?)\nFROM \"Json\"\n"
it "creates sane SQL (chained)" $ do [ PersistLiteralEscaped encoded
let obj = object ["a" .= [object ["b" .= True]]] , persistTextArray ["a","c"]
createSaneSQL ]
(jsonbVal obj #>. ["a","0"] ?&. ["b","c"]) it "creates sane SQL (chained)" $ do
"SELECT ((? #> ?) ??& ?)\nFROM \"Json\"\n" let obj = object ["a" .= [object ["b" .= True]]]
[ PersistDbSpecific "{\"a\":[{\"b\":true}]}" encoded = BSL.toStrict $ encode obj
, persistTextArray ["a","0"] createSaneSQL
, persistTextArray ["b","c"] ] (jsonbVal obj #>. ["a","0"] ?&. ["b","c"])
it "works as expected" $ run $ do "SELECT ((? #> ?) ??& ?)\nFROM \"Json\"\n"
x <- selectJSONwhere (?&. ["test"]) [ PersistLiteralEscaped encoded
y <- selectJSONwhere (?&. ["a","b"]) , persistTextArray ["a","0"]
z <- selectJSONwhere $ \v -> v ->. "a" ?&. ["b"] , persistTextArray ["b","c"]
w <- selectJSONwhere (?&. []) ]
liftIO $ length x `shouldBe` 2 it "works as expected" $ run $ do
liftIO $ length y `shouldBe` 1 x <- selectJSONwhere (?&. ["test"])
liftIO $ length z `shouldBe` 1 y <- selectJSONwhere (?&. ["a","b"])
liftIO $ length w `shouldBe` 9 z <- selectJSONwhere $ \v -> v ->. "a" ?&. ["b"]
w <- selectJSONwhere (?&. [])
liftIO $ length x `shouldBe` 2
liftIO $ length y `shouldBe` 1
liftIO $ length z `shouldBe` 1
liftIO $ length w `shouldBe` 9
testConcatDeleteOperators :: Spec testConcatDeleteOperators :: Spec
testConcatDeleteOperators = do testConcatDeleteOperators = do
@ -859,120 +879,135 @@ testConcatDeleteOperators = do
testHashMinusOperator testHashMinusOperator
testConcatenationOperator :: Spec testConcatenationOperator :: Spec
testConcatenationOperator = testConcatenationOperator = do
describe "Concatenation" $ do describe "Concatenation" $ do
it "creates sane SQL" $ it "creates sane SQL" $ do
createSaneSQL @JSONValue let objAB = object ["a" .= False, "b" .= True]
(jsonbVal (object ["a" .= False, "b" .= True]) objC = object ["c" .= Null]
JSON.||. jsonbVal (object ["c" .= Null])) createSaneSQL @JSONValue
"SELECT (? || ?)\nFROM \"Json\"\n" (jsonbVal objAB
[ PersistDbSpecific "{\"a\":false,\"b\":true}" JSON.||. jsonbVal objC)
, PersistDbSpecific "{\"c\":null}" ] "SELECT (? || ?)\nFROM \"Json\"\n"
it "creates sane SQL (chained)" $ do [ PersistLiteralEscaped $ BSL.toStrict $ encode objAB
let obj = object ["a" .= [object ["b" .= True]]] , PersistLiteralEscaped $ BSL.toStrict $ encode objC
createSaneSQL @JSONValue ]
(jsonbVal obj ->. "a" JSON.||. jsonbVal (toJSON [Null])) it "creates sane SQL (chained)" $ do
"SELECT ((? -> ?) || ?)\nFROM \"Json\"\n" let obj = object ["a" .= [object ["b" .= True]]]
[ PersistDbSpecific "{\"a\":[{\"b\":true}]}" encoded = BSL.toStrict $ encode obj
, PersistText "a" createSaneSQL @JSONValue
, PersistDbSpecific "[null]" ] (jsonbVal obj ->. "a" JSON.||. jsonbVal (toJSON [Null]))
it "works as expected" $ run $ do "SELECT ((? -> ?) || ?)\nFROM \"Json\"\n"
x <- selectJSON $ \v -> do [ PersistLiteralEscaped encoded
where_ $ v @>. jsonbVal (object []) , PersistText "a"
where_ $ v JSON.||. jsonbVal (object ["x" .= True]) , PersistLiteralEscaped "[null]"
@>. jsonbVal (object ["x" .= True]) ]
y <- selectJSONwhere $ \v -> it "works as expected" $ run $ do
v JSON.||. jsonbVal (toJSON [String "a", String "b"]) x <- selectJSON $ \v -> do
->>. 4 ==. just (val "b") where_ $ v @>. jsonbVal (object [])
z <- selectJSONwhere $ \v -> where_ $ v JSON.||. jsonbVal (object ["x" .= True])
v JSON.||. jsonbVal (toJSON [Bool False]) @>. jsonbVal (object ["x" .= True])
->. 0 JSON.@>. jsonbVal (Number 1) y <- selectJSONwhere $ \v ->
w <- selectJSON $ \v -> do v JSON.||. jsonbVal (toJSON [String "a", String "b"])
where_ . not_ $ v @>. jsonbVal (object []) ->>. 4 ==. just (val "b")
where_ $ jsonbVal (String "test1") JSON.||. v ->>. 0 ==. just (val "test1") z <- selectJSONwhere $ \v ->
liftIO $ length x `shouldBe` 2 v JSON.||. jsonbVal (toJSON [Bool False])
liftIO $ length y `shouldBe` 1 ->. 0 JSON.@>. jsonbVal (Number 1)
liftIO $ length z `shouldBe` 2 w <- selectJSON $ \v -> do
liftIO $ length w `shouldBe` 7 where_ . not_ $ v @>. jsonbVal (object [])
sqlFailWith "22023" $ selectJSONwhere $ \v -> where_ $ jsonbVal (String "test1") JSON.||. v ->>. 0 ==. just (val "test1")
v JSON.||. jsonbVal (toJSON $ String "test") liftIO $ length x `shouldBe` 2
@>. jsonbVal (String "test") liftIO $ length y `shouldBe` 1
liftIO $ length z `shouldBe` 2
liftIO $ length w `shouldBe` 7
testMinusOperator :: Spec testMinusOperator :: Spec
testMinusOperator = testMinusOperator =
describe "Minus Operator" $ do describe "Minus Operator" $ do
it "creates sane SQL" $ it "creates sane SQL" $ do
createSaneSQL @JSONValue let obj = object ["a" .= False, "b" .= True]
(jsonbVal (object ["a" .= False, "b" .= True]) JSON.-. "a") encoded = BSL.toStrict $ encode obj
"SELECT (? - ?)\nFROM \"Json\"\n" createSaneSQL @JSONValue
[ PersistDbSpecific "{\"a\":false,\"b\":true}" (jsonbVal obj JSON.-. "a")
, PersistText "a" ] "SELECT (? - ?)\nFROM \"Json\"\n"
it "creates sane SQL (chained)" $ do [ PersistLiteralEscaped encoded
let obj = object ["a" .= [object ["b" .= True]]] , PersistText "a"
createSaneSQL @JSONValue ]
(jsonbVal obj ->. "a" JSON.-. 0) it "creates sane SQL (chained)" $ do
"SELECT ((? -> ?) - ?)\nFROM \"Json\"\n" let obj = object ["a" .= [object ["b" .= True]]]
[ PersistDbSpecific "{\"a\":[{\"b\":true}]}" encoded = BSL.toStrict $ encode obj
, PersistText "a" createSaneSQL @JSONValue
, PersistInt64 0 ] (jsonbVal obj ->. "a" JSON.-. 0)
it "works as expected" $ run $ do "SELECT ((? -> ?) - ?)\nFROM \"Json\"\n"
x <- selectJSON $ \v -> do [ PersistLiteralEscaped encoded
where_ $ v @>. jsonbVal (toJSON ([] :: [Int])) , PersistText "a"
where_ $ v JSON.-. 0 @>. jsonbVal (toJSON [Bool True]) , PersistInt64 0
y <- selectJSON $ \v -> do ]
where_ $ v @>. jsonbVal (toJSON ([] :: [Int])) it "works as expected" $ run $ do
where_ $ v JSON.-. (-1) @>. jsonbVal (toJSON [Null]) x <- selectJSON $ \v -> do
z <- selectJSON_ $ \v -> v JSON.-. "b" ?&. ["a", "b"] where_ $ v @>. jsonbVal (toJSON ([] :: [Int]))
w <- selectJSON_ $ \v -> do where_ $ v JSON.-. 0 @>. jsonbVal (toJSON [Bool True])
v JSON.-. "test" @>. jsonbVal (toJSON [String "test"]) y <- selectJSON $ \v -> do
liftIO $ length x `shouldBe` 2 where_ $ v @>. jsonbVal (toJSON ([] :: [Int]))
liftIO $ length y `shouldBe` 1 where_ $ v JSON.-. (-1) @>. jsonbVal (toJSON [Null])
liftIO $ length z `shouldBe` 0 z <- selectJSON_ $ \v -> v JSON.-. "b" ?&. ["a", "b"]
liftIO $ length w `shouldBe` 0 w <- selectJSON_ $ \v -> do
sqlFailWith "22023" $ selectJSONwhere $ \v -> v JSON.-. "test" @>. jsonbVal (toJSON [String "test"])
v JSON.-. 0 @>. jsonbVal (toJSON ([] :: [Int])) liftIO $ length x `shouldBe` 2
where selectJSON_ f = selectJSON $ \v -> do liftIO $ length y `shouldBe` 1
where_ $ v @>. jsonbVal (object []) liftIO $ length z `shouldBe` 0
||. v @>. jsonbVal (toJSON ([] :: [Int])) liftIO $ length w `shouldBe` 0
where_ $ f v sqlFailWith "22023" $ selectJSONwhere $ \v ->
v JSON.-. 0 @>. jsonbVal (toJSON ([] :: [Int]))
where
selectJSON_ f = selectJSON $ \v -> do
where_
$ v @>. jsonbVal (object [])
||. v @>. jsonbVal (toJSON ([] :: [Int]))
where_ $ f v
testMinusOperatorV10 :: Spec testMinusOperatorV10 :: Spec
testMinusOperatorV10 = testMinusOperatorV10 = do
describe "Minus Operator (PSQL >= v10)" $ do describe "Minus Operator (PSQL >= v10)" $ do
it "creates sane SQL" $ it "creates sane SQL" $ do
createSaneSQL @JSONValue let obj = object ["a" .= False, "b" .= True]
(jsonbVal (object ["a" .= False, "b" .= True]) --. ["a","b"]) encoded = BSL.toStrict $ encode obj
"SELECT (? - ?)\nFROM \"Json\"\n" createSaneSQL @JSONValue
[ PersistDbSpecific "{\"a\":false,\"b\":true}" (jsonbVal obj --. ["a","b"])
, persistTextArray ["a","b"] ] "SELECT (? - ?)\nFROM \"Json\"\n"
it "creates sane SQL (chained)" $ do [ PersistLiteralEscaped encoded
let obj = object ["a" .= [object ["b" .= True]]] , persistTextArray ["a","b"]
createSaneSQL @JSONValue ]
(jsonbVal obj #>. ["a","0"] --. ["b"]) it "creates sane SQL (chained)" $ do
"SELECT ((? #> ?) - ?)\nFROM \"Json\"\n" let obj = object ["a" .= [object ["b" .= True]]]
[ PersistDbSpecific "{\"a\":[{\"b\":true}]}" encoded = BSL.toStrict $ encode obj
, persistTextArray ["a","0"] createSaneSQL @JSONValue
, persistTextArray ["b"] ] (jsonbVal obj #>. ["a","0"] --. ["b"])
it "works as expected" $ run $ do "SELECT ((? #> ?) - ?)\nFROM \"Json\"\n"
x <- selectJSON $ \v -> do [ PersistLiteralEscaped encoded
where_ $ v @>. jsonbVal (toJSON ([] :: [Int])) , persistTextArray ["a","0"]
where_ $ v --. ["test","a"] @>. jsonbVal (toJSON [String "test"]) , persistTextArray ["b"]
y <- selectJSON $ \v -> do ]
where_ $ v @>. jsonbVal (object []) it "works as expected" $ run $ do
where_ $ v --. ["a","b"] <@. jsonbVal (object []) x <- selectJSON $ \v -> do
z <- selectJSON_ $ \v -> v --. ["b"] <@. jsonbVal (object ["a" .= (1 :: Int)]) where_ $ v @>. jsonbVal (toJSON ([] :: [Int]))
w <- selectJSON_ $ \v -> do where_ $ v --. ["test","a"] @>. jsonbVal (toJSON [String "test"])
v --. ["test"] @>. jsonbVal (toJSON [String "test"]) y <- selectJSON $ \v -> do
liftIO $ length x `shouldBe` 0 where_ $ v @>. jsonbVal (object [])
liftIO $ length y `shouldBe` 2 where_ $ v --. ["a","b"] <@. jsonbVal (object [])
liftIO $ length z `shouldBe` 1 z <- selectJSON_ $ \v -> v --. ["b"] <@. jsonbVal (object ["a" .= (1 :: Int)])
liftIO $ length w `shouldBe` 0 w <- selectJSON_ $ \v -> do
sqlFailWith "22023" $ selectJSONwhere $ \v -> v --. ["test"] @>. jsonbVal (toJSON [String "test"])
v --. ["a"] @>. jsonbVal (toJSON ([] :: [Int])) liftIO $ length x `shouldBe` 0
where selectJSON_ f = selectJSON $ \v -> do liftIO $ length y `shouldBe` 2
where_ $ v @>. jsonbVal (object []) liftIO $ length z `shouldBe` 1
liftIO $ length w `shouldBe` 0
sqlFailWith "22023" $ selectJSONwhere $ \v ->
v --. ["a"] @>. jsonbVal (toJSON ([] :: [Int]))
where
selectJSON_ f = selectJSON $ \v -> do
where_ $ v @>. jsonbVal (object [])
||. v @>. jsonbVal (toJSON ([] :: [Int])) ||. v @>. jsonbVal (toJSON ([] :: [Int]))
where_ $ f v where_ $ f v
testHashMinusOperator :: Spec testHashMinusOperator :: Spec
testHashMinusOperator = testHashMinusOperator =
@ -981,14 +1016,14 @@ testHashMinusOperator =
createSaneSQL @JSONValue createSaneSQL @JSONValue
(jsonbVal (object ["a" .= False, "b" .= True]) #-. ["a"]) (jsonbVal (object ["a" .= False, "b" .= True]) #-. ["a"])
"SELECT (? #- ?)\nFROM \"Json\"\n" "SELECT (? #- ?)\nFROM \"Json\"\n"
[ PersistDbSpecific "{\"a\":false,\"b\":true}" [ PersistLiteralEscaped (BSL.toStrict $ encode $ object ["a" .= False, "b" .= True])
, persistTextArray ["a"] ] , persistTextArray ["a"] ]
it "creates sane SQL (chained)" $ do it "creates sane SQL (chained)" $ do
let obj = object ["a" .= [object ["b" .= True]]] let obj = object ["a" .= [object ["b" .= True]]]
createSaneSQL @JSONValue createSaneSQL @JSONValue
(jsonbVal obj ->. "a" #-. ["0","b"]) (jsonbVal obj ->. "a" #-. ["0","b"])
"SELECT ((? -> ?) #- ?)\nFROM \"Json\"\n" "SELECT ((? -> ?) #- ?)\nFROM \"Json\"\n"
[ PersistDbSpecific "{\"a\":[{\"b\":true}]}" [ PersistLiteralEscaped (BSL.toStrict $ encode obj)
, PersistText "a" , PersistText "a"
, persistTextArray ["0","b"] ] , persistTextArray ["0","b"] ]
it "works as expected" $ run $ do it "works as expected" $ run $ do
@ -1309,20 +1344,30 @@ fromValue act = from $ \x -> do
persistTextArray :: [T.Text] -> PersistValue persistTextArray :: [T.Text] -> PersistValue
persistTextArray = PersistArray . fmap PersistText persistTextArray = PersistArray . fmap PersistText
sqlFailWith :: (MonadCatch m, MonadIO m) => ByteString -> SqlPersistT (R.ResourceT m) a -> SqlPersistT (R.ResourceT m) () sqlFailWith :: (HasCallStack, MonadCatch m, MonadIO m, Show a) => ByteString -> SqlPersistT (R.ResourceT m) a -> SqlPersistT (R.ResourceT m) ()
sqlFailWith errState f = do sqlFailWith errState f = do
p <- (f >> return True) `catch` success eres <- try f
when p failed case eres of
where success SqlError{sqlState} Left err ->
| sqlState == errState = return False success err
| otherwise = do Right a ->
liftIO $ expectationFailure $ T.unpack $ T.concat liftIO $ expectationFailure $ mconcat
[ "should fail with: ", errStateT [ "should fail with error code: "
, ", but received: ", TE.decodeUtf8 sqlState , T.unpack errStateT
] , ", but got: "
return False , show a
failed = liftIO $ expectationFailure $ "should fail with: " `mappend` T.unpack errStateT ]
errStateT = TE.decodeUtf8 errState where
success SqlError{sqlState}
| sqlState == errState =
pure ()
| otherwise = do
liftIO $ expectationFailure $ T.unpack $ T.concat
[ "should fail with: ", errStateT
, ", but received: ", TE.decodeUtf8 sqlState
]
errStateT =
TE.decodeUtf8 errState
selectJSONwhere selectJSONwhere
:: MonadIO m :: MonadIO m
@ -1406,8 +1451,26 @@ migrateIt = do
cleanUniques cleanUniques
withConn :: RunDbMonad m => (SqlBackend -> R.ResourceT m a) -> m a withConn :: RunDbMonad m => (SqlBackend -> R.ResourceT m a) -> m a
withConn = withConn f = do
R.runResourceT . withPostgresqlConn "host=localhost port=5432 user=esqutest password=esqutest dbname=esqutest" ea <- try go
case ea of
Left (SomeException se) -> do
ea' <- try go
case ea' of
Left (SomeException se1) ->
if show se == show se1
then throwM se
else throwM se1
Right a ->
pure a
Right a ->
pure a
where
go =
R.runResourceT $
withPostgresqlConn
"host=localhost port=5432 user=esqutest password=esqutest dbname=esqutest"
f
-- | Show the SQL generated by a query -- | Show the SQL generated by a query
showQuery :: (Monad m, ES.SqlSelect a r, BackendCompatible SqlBackend backend) showQuery :: (Monad m, ES.SqlSelect a r, BackendCompatible SqlBackend backend)