EmbedTestMongo.hs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. {-# LANGUAGE EmptyDataDecls #-}
  2. {-# LANGUAGE ExistentialQuantification #-}
  3. {-# LANGUAGE FlexibleInstances #-}
  4. {-# LANGUAGE OverloadedStrings #-}
  5. {-# LANGUAGE MultiParamTypeClasses #-}
  6. {-# LANGUAGE QuasiQuotes #-}
  7. {-# LANGUAGE TemplateHaskell #-}
  8. {-# LANGUAGE TypeFamilies #-}
  9. {-# LANGUAGE UndecidableInstances #-}
  10. {-# OPTIONS_GHC -Wno-unused-top-binds -Wno-orphans -O0 #-}
  11. module EmbedTestMongo (specs) where
  12. import MongoInit
  13. import Control.Exception (Exception, throw)
  14. import Data.List.NonEmpty hiding (insert, length)
  15. import qualified Data.Map as M
  16. import qualified Data.Set as S
  17. import qualified Data.Text as T
  18. import Data.Typeable (Typeable)
  19. import Database.MongoDB (genObjectId)
  20. import Database.MongoDB (Value(String))
  21. import System.Process (readProcess)
  22. import EntityEmbedTestMongo
  23. import Database.Persist.MongoDB
  24. data TestException = TestException
  25. deriving (Show, Typeable, Eq)
  26. instance Exception TestException
  27. instance PersistFieldSql a => PersistFieldSql (NonEmpty a) where
  28. sqlType _ = SqlString
  29. instance PersistField a => PersistField (NonEmpty a) where
  30. toPersistValue = toPersistValue . toList
  31. fromPersistValue pv = do
  32. list <- fromPersistValue pv
  33. case list of
  34. [] -> Left "PersistField: NonEmpty found unexpected Empty List"
  35. (l:ls) -> Right (l:|ls)
  36. mkPersist persistSettings [persistUpperCase|
  37. HasObjectId
  38. oid ObjectId
  39. name Text
  40. deriving Show Eq Read Ord
  41. HasArrayWithObjectIds
  42. name Text
  43. arrayWithObjectIds [HasObjectId]
  44. deriving Show Eq Read Ord
  45. HasArrayWithEntities
  46. hasEntity (Entity ARecord)
  47. arrayWithEntities [AnEntity]
  48. deriving Show Eq Read Ord
  49. OnlyName
  50. name Text
  51. deriving Show Eq Read Ord
  52. HasEmbed
  53. name Text
  54. embed OnlyName
  55. deriving Show Eq Read Ord
  56. HasEmbeds
  57. name Text
  58. embed OnlyName
  59. double HasEmbed
  60. deriving Show Eq Read Ord
  61. HasListEmbed
  62. name Text
  63. list [HasEmbed]
  64. deriving Show Eq Read Ord
  65. HasSetEmbed
  66. name Text
  67. set (S.Set HasEmbed)
  68. deriving Show Eq Read Ord
  69. HasMap
  70. name Text
  71. map (M.Map T.Text T.Text)
  72. deriving Show Eq Read Ord
  73. HasList
  74. list [HasListId]
  75. deriving Show Eq Read Ord
  76. EmbedsHasMap
  77. name Text Maybe
  78. embed HasMap
  79. deriving Show Eq Read Ord
  80. InList
  81. one Int
  82. two Int
  83. deriving Show Eq
  84. ListEmbed
  85. nested [InList]
  86. one Int
  87. two Int
  88. deriving Show Eq
  89. User
  90. ident Text
  91. password Text Maybe
  92. profile Profile
  93. deriving Show Eq Read Ord
  94. Profile
  95. firstName Text
  96. lastName Text
  97. contact Contact Maybe
  98. deriving Show Eq Read Ord
  99. Contact
  100. phone Int
  101. email T.Text
  102. deriving Show Eq Read Ord
  103. Account
  104. userIds (NonEmpty (Key User))
  105. name Text Maybe
  106. customDomains [Text] -- we may want to allow multiple cust domains. use [] instead of maybe
  107. deriving Show Eq Read Ord
  108. HasNestedList
  109. list [IntList]
  110. deriving Show Eq
  111. IntList
  112. ints [Int]
  113. deriving Show Eq
  114. -- We would like to be able to use OnlyNameId
  115. -- But (Key OnlyName) works
  116. MapIdValue
  117. map (M.Map T.Text (Key OnlyName))
  118. deriving Show Eq Read Ord
  119. -- Self refrences are only allowed as a nullable type:
  120. -- a Maybe or a List
  121. SelfList
  122. reference [SelfList]
  123. SelfMaybe
  124. reference SelfMaybe Maybe
  125. -- This failes
  126. -- SelfDirect
  127. -- reference SelfDirect
  128. |]
  129. cleanDB :: (PersistQuery backend, PersistEntityBackend HasMap ~ backend, MonadIO m) => ReaderT backend m ()
  130. cleanDB = do
  131. deleteWhere ([] :: [Filter HasEmbed])
  132. deleteWhere ([] :: [Filter HasEmbeds])
  133. deleteWhere ([] :: [Filter HasListEmbed])
  134. deleteWhere ([] :: [Filter HasSetEmbed])
  135. deleteWhere ([] :: [Filter User])
  136. deleteWhere ([] :: [Filter HasMap])
  137. deleteWhere ([] :: [Filter HasList])
  138. deleteWhere ([] :: [Filter EmbedsHasMap])
  139. deleteWhere ([] :: [Filter ListEmbed])
  140. deleteWhere ([] :: [Filter ARecord])
  141. deleteWhere ([] :: [Filter Account])
  142. deleteWhere ([] :: [Filter HasNestedList])
  143. db :: Action IO () -> Assertion
  144. db = db' cleanDB
  145. unlessM :: MonadIO m => IO Bool -> m () -> m ()
  146. unlessM predicate body = do
  147. b <- liftIO predicate
  148. unless b body
  149. specs :: Spec
  150. specs = describe "embedded entities" $ do
  151. it "simple entities" $ db $ do
  152. let container = HasEmbeds "container" (OnlyName "2")
  153. (HasEmbed "embed" (OnlyName "1"))
  154. contK <- insert container
  155. Just res <- selectFirst [HasEmbedsName ==. "container"] []
  156. res @== Entity contK container
  157. it "query for equality of embeded entity" $ db $ do
  158. let container = HasEmbed "container" (OnlyName "2")
  159. contK <- insert container
  160. Just res <- selectFirst [HasEmbedEmbed ==. OnlyName "2"] []
  161. res @== Entity contK container
  162. it "Set" $ db $ do
  163. let container = HasSetEmbed "set" $ S.fromList
  164. [ HasEmbed "embed" (OnlyName "1")
  165. , HasEmbed "embed" (OnlyName "2")
  166. ]
  167. contK <- insert container
  168. Just res <- selectFirst [HasSetEmbedName ==. "set"] []
  169. res @== Entity contK container
  170. it "Set empty" $ db $ do
  171. let container = HasSetEmbed "set empty" $ S.fromList []
  172. contK <- insert container
  173. Just res <- selectFirst [HasSetEmbedName ==. "set empty"] []
  174. res @== Entity contK container
  175. it "exception" $ flip shouldThrow (== TestException) $ db $ do
  176. let container = HasSetEmbed "set" $ S.fromList
  177. [ HasEmbed "embed" (OnlyName "1")
  178. , HasEmbed "embed" (OnlyName "2")
  179. ]
  180. contK <- insert container
  181. Just res <- selectFirst [HasSetEmbedName ==. throw TestException] []
  182. res @== Entity contK container
  183. it "ListEmbed" $ db $ do
  184. let container = HasListEmbed "list"
  185. [ HasEmbed "embed" (OnlyName "1")
  186. , HasEmbed "embed" (OnlyName "2")
  187. ]
  188. contK <- insert container
  189. Just res <- selectFirst [HasListEmbedName ==. "list"] []
  190. res @== Entity contK container
  191. it "ListEmbed empty" $ db $ do
  192. let container = HasListEmbed "list empty" []
  193. contK <- insert container
  194. Just res <- selectFirst [HasListEmbedName ==. "list empty"] []
  195. res @== Entity contK container
  196. it "List empty" $ db $ do
  197. let container = HasList []
  198. contK <- insert container
  199. Just res <- selectFirst [] []
  200. res @== Entity contK container
  201. it "NonEmpty List wrapper" $ db $ do
  202. let con = Contact 123456 "foo@bar.com"
  203. let prof = Profile "fstN" "lstN" (Just con)
  204. uid <- insert $ User "foo" (Just "pswd") prof
  205. let container = Account (uid:|[]) (Just "Account") []
  206. contK <- insert container
  207. Just res <- selectFirst [AccountUserIds ==. (uid:|[])] []
  208. res @== Entity contK container
  209. it "Map" $ db $ do
  210. let container = HasMap "2 items" $ M.fromList [
  211. ("k1","v1")
  212. , ("k2","v2")
  213. ]
  214. contK <- insert container
  215. Just res <- selectFirst [HasMapName ==. "2 items"] []
  216. res @== Entity contK container
  217. it "Map empty" $ db $ do
  218. let container = HasMap "empty" $ M.fromList []
  219. contK <- insert container
  220. Just res <- selectFirst [HasMapName ==. "empty"] []
  221. res @== Entity contK container
  222. it "Embeds a Map" $ db $ do
  223. let container = EmbedsHasMap (Just "non-empty map") $ HasMap "2 items" $ M.fromList [
  224. ("k1","v1")
  225. , ("k2","v2")
  226. ]
  227. contK <- insert container
  228. Just res <- selectFirst [EmbedsHasMapName ==. Just "non-empty map"] []
  229. res @== Entity contK container
  230. it "Embeds a Map empty" $ db $ do
  231. let container = EmbedsHasMap (Just "empty map") $ HasMap "empty" $ M.fromList []
  232. contK <- insert container
  233. Just res <- selectFirst [EmbedsHasMapName ==. Just "empty map"] []
  234. res @== Entity contK container
  235. it "Embeds a Map with ids as values" $ db $ do
  236. onId <- insert $ OnlyName "nombre"
  237. onId2 <- insert $ OnlyName "nombre2"
  238. let midValue = MapIdValue $ M.fromList [("foo", onId),("bar",onId2)]
  239. mK <- insert midValue
  240. Just mv <- get mK
  241. mv @== midValue
  242. it "List" $ db $ do
  243. k1 <- insert $ HasList []
  244. k2 <- insert $ HasList [k1]
  245. let container = HasList [k1, k2]
  246. contK <- insert container
  247. Just res <- selectFirst [HasListList `anyEq` k2] []
  248. res @== Entity contK container
  249. it "can embed an Entity" $ db $ do
  250. let foo = ARecord "foo"
  251. bar = ARecord "bar"
  252. _ <- insertMany [foo, bar]
  253. arecords <- selectList ([ARecordName ==. "foo"] ||. [ARecordName ==. "bar"]) []
  254. length arecords @== 2
  255. kfoo <- insert foo
  256. let hasEnts = HasArrayWithEntities (Entity kfoo foo) arecords
  257. kEnts <- insert hasEnts
  258. Just retrievedHasEnts <- get kEnts
  259. retrievedHasEnts @== hasEnts
  260. it "can embed objects with ObjectIds" $ db $ do
  261. oid <- liftIO $ genObjectId
  262. let hoid = HasObjectId oid "oid"
  263. hasArr = HasArrayWithObjectIds "array" [hoid]
  264. k <- insert hasArr
  265. Just v <- get k
  266. v @== hasArr
  267. describe "mongoDB filters" $ do
  268. it "mongo single nesting filters" $ db $ do
  269. let usr = User "foo" (Just "pswd") prof
  270. prof = Profile "fstN" "lstN" (Just con)
  271. con = Contact 123456 "foo@bar.com"
  272. uId <- insert usr
  273. Just r1 <- selectFirst [UserProfile &->. ProfileFirstName `nestEq` "fstN"] []
  274. r1 @== (Entity uId usr)
  275. Just r2 <- selectFirst [UserProfile &~>. ProfileContact ?&->. ContactEmail `nestEq` "foo@bar.com", UserIdent ==. "foo"] []
  276. r2 @== (Entity uId usr)
  277. it "mongo embedded array filters" $ db $ do
  278. let container = HasListEmbed "list" [
  279. (HasEmbed "embed" (OnlyName "1"))
  280. , (HasEmbed "embed" (OnlyName "2"))
  281. ]
  282. contK <- insert container
  283. let contEnt = Entity contK container
  284. Just meq <- selectFirst [HasListEmbedList `anyEq` HasEmbed "embed" (OnlyName "1")] []
  285. meq @== contEnt
  286. Just neq1 <- selectFirst [HasListEmbedList ->. HasEmbedName `nestEq` "embed"] []
  287. neq1 @== contEnt
  288. Just nne1 <- selectFirst [HasListEmbedList ->. HasEmbedName `nestNe` "notEmbed"] []
  289. nne1 @== contEnt
  290. Just neq2 <- selectFirst [HasListEmbedList ~>. HasEmbedEmbed &->. OnlyNameName `nestEq` "1"] []
  291. neq2 @== contEnt
  292. Just nbq1 <- selectFirst [HasListEmbedList ->. HasEmbedName `nestBsonEq` String "embed"] []
  293. nbq1 @== contEnt
  294. Just nbq2 <- selectFirst [HasListEmbedList ~>. HasEmbedEmbed &->. OnlyNameName `nestBsonEq` String "1"] []
  295. nbq2 @== contEnt
  296. it "regexp match" $ db $ do
  297. let container = HasListEmbed "list" [
  298. (HasEmbed "embed" (OnlyName "abcd"))
  299. , (HasEmbed "embed" (OnlyName "efgh"))
  300. ]
  301. contK <- insert container
  302. let mkReg t = (t, "ims")
  303. Just res <- selectFirst [HasListEmbedName =~. mkReg "ist"] []
  304. res @== (Entity contK container)
  305. it "nested anyEq" $ db $ do
  306. let top = HasNestedList [IntList [1,2]]
  307. k <- insert top
  308. Nothing <- selectFirst [HasNestedListList ->. IntListInts `nestEq` ([]::[Int])] []
  309. Nothing <- selectFirst [HasNestedListList ->. IntListInts `nestAnyEq` 3] []
  310. Just res <- selectFirst [HasNestedListList ->. IntListInts `nestAnyEq` 2] []
  311. res @== (Entity k top)
  312. describe "mongoDB updates" $ do
  313. it "mongo single nesting updates" $ db $ do
  314. let usr = User "foo" (Just "pswd") prof
  315. prof = Profile "fstN" "lstN" (Just con)
  316. con = Contact 123456 "foo@bar.com"
  317. uid <- insert usr
  318. let newName = "fstN2"
  319. usr1 <- updateGet uid [UserProfile &->. ProfileFirstName `nestSet` newName]
  320. (profileFirstName $ userProfile usr1) @== newName
  321. let newEmail = "foo@example.com"
  322. let newIdent = "bar"
  323. usr2 <- updateGet uid [UserProfile &~>. ProfileContact ?&->. ContactEmail `nestSet` newEmail, UserIdent =. newIdent]
  324. (userIdent usr2) @== newIdent
  325. (fmap contactEmail . profileContact . userProfile $ usr2) @== Just newEmail
  326. it "mongo embedded array updates" $ db $ do
  327. let container = HasListEmbed "list" [
  328. (HasEmbed "embed" (OnlyName "1"))
  329. , (HasEmbed "embed" (OnlyName "2"))
  330. ]
  331. contk <- insert container
  332. let _contEnt = Entity contk container
  333. pushed <- updateGet contk [HasListEmbedList `push` HasEmbed "embed" (OnlyName "3")]
  334. (Prelude.map (onlyNameName . hasEmbedEmbed) $ hasListEmbedList pushed) @== ["1","2","3"]
  335. -- same, don't add anything
  336. addedToSet <- updateGet contk [HasListEmbedList `addToSet` HasEmbed "embed" (OnlyName "3")]
  337. (Prelude.map (onlyNameName . hasEmbedEmbed) $ hasListEmbedList addedToSet) @== ["1","2","3"]
  338. pulled <- updateGet contk [HasListEmbedList `pull` HasEmbed "embed" (OnlyName "3")]
  339. (Prelude.map (onlyNameName . hasEmbedEmbed) $ hasListEmbedList pulled) @== ["1","2"]
  340. -- now it is new
  341. addedToSet2 <- updateGet contk [HasListEmbedList `addToSet` HasEmbed "embed" (OnlyName "3")]
  342. (Prelude.map (onlyNameName . hasEmbedEmbed) $ hasListEmbedList addedToSet2) @== ["1","2","3"]
  343. allPulled <- updateGet contk [eachOp pull HasListEmbedList
  344. [ HasEmbed "embed" (OnlyName "3")
  345. , HasEmbed "embed" (OnlyName "2")
  346. ] ]
  347. (Prelude.map (onlyNameName . hasEmbedEmbed) $ hasListEmbedList allPulled) @== ["1"]
  348. allPushed <- updateGet contk [eachOp push HasListEmbedList
  349. [ HasEmbed "embed" (OnlyName "4")
  350. , HasEmbed "embed" (OnlyName "5")
  351. ] ]
  352. (Prelude.map (onlyNameName . hasEmbedEmbed) $ hasListEmbedList allPushed) @== ["1","4","5"]
  353. it "re-orders json inserted from another source" $ db $ do
  354. let cname = T.unpack $ collectionName (error "ListEmbed" :: ListEmbed)
  355. liftIO $ putStrLn =<< readProcess "mongoimport" ["-d", T.unpack dbName, "-c", cname] "{ \"nested\": [{ \"one\": 1, \"two\": 2 }, { \"two\": 2, \"one\": 1}], \"two\": 2, \"one\": 1, \"_id\" : { \"$oid\" : \"50184f5a92d7ae0000001e89\" } }"
  356. lists <- selectList [] []
  357. fmap entityVal lists @== [ListEmbed [InList 1 2, InList 1 2] 1 2]