git.iko.soy/typeable/bloodhound
on v0.11.1.0 help sign in
1558 lines 66 kB history raw
1{-# LANGUAGE DefaultSignatures #-}
2{-# LANGUAGE DeriveGeneric #-}
3{-# LANGUAGE FlexibleContexts #-}
4{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5{-# LANGUAGE OverloadedStrings #-}
6{-# LANGUAGE ScopedTypeVariables #-}
7{-# LANGUAGE TemplateHaskell #-}
8{-# LANGUAGE TypeOperators #-}
9{-# OPTIONS_GHC -fno-warn-orphans #-}
10module Main where
12import Control.Applicative
13import Control.Error
14import Control.Exception
15import Control.Monad
16import Control.Monad.Reader
17import Data.Aeson
18import Data.Aeson.Types (parseEither)
19import qualified Data.ByteString.Lazy.Char8 as BL8
20import Data.DeriveTH
21import qualified Data.HashMap.Strict as HM
22import Data.List (nub)
23import qualified Data.List as L
24import Data.List.NonEmpty (NonEmpty (..))
25import qualified Data.List.NonEmpty as NE
26import qualified Data.Map.Strict as M
27import Data.Monoid
28import Data.Proxy
29import Data.Text (Text)
30import qualified Data.Text as T
31import Data.Time.Calendar (Day (..), fromGregorian)
32import Data.Time.Clock (NominalDiffTime, UTCTime (..),
33 secondsToDiffTime)
34import Data.Typeable
35import qualified Data.Vector as V
36import Database.Bloodhound
37import GHC.Generics as G
38import Network.HTTP.Client hiding (Proxy)
39import qualified Network.HTTP.Types.Status as NHTS
40import Prelude hiding (filter)
41import Test.Hspec
42import Test.QuickCheck.Property.Monoid (T (..), eq, prop_Monoid)
44import Test.Hspec.QuickCheck (prop)
45import Test.QuickCheck
47testServer :: Server
48testServer = Server "http://localhost:9200"
49testIndex :: IndexName
50testIndex = IndexName "bloodhound-tests-twitter-1"
51testMapping :: MappingName
52testMapping = MappingName "tweet"
54withTestEnv :: BH IO a -> IO a
55withTestEnv = withBH defaultManagerSettings testServer
57validateStatus :: Response body -> Int -> Expectation
58validateStatus resp expected =
59 (NHTS.statusCode $ responseStatus resp)
60 `shouldBe` (expected :: Int)
62createExampleIndex :: BH IO Reply
63createExampleIndex = createIndex (IndexSettings (ShardCount 1) (ReplicaCount 0)) testIndex
64deleteExampleIndex :: BH IO Reply
65deleteExampleIndex = deleteIndex testIndex
67data ServerVersion = ServerVersion Int Int Int deriving (Show, Eq, Ord)
69es13 :: ServerVersion
70es13 = ServerVersion 1 3 0
72es12 :: ServerVersion
73es12 = ServerVersion 1 2 0
75es11 :: ServerVersion
76es11 = ServerVersion 1 1 0
78serverBranch :: ServerVersion -> ServerVersion
79serverBranch (ServerVersion majorVer minorVer patchVer) =
80 ServerVersion majorVer minorVer patchVer
82mkServerVersion :: [Int] -> Maybe ServerVersion
83mkServerVersion [majorVer, minorVer, patchVer] =
84 Just (ServerVersion majorVer minorVer patchVer)
85mkServerVersion _ = Nothing
87getServerVersion :: IO (Maybe ServerVersion)
88getServerVersion = liftM extractVersion (withTestEnv getStatus)
89 where
90 version' = T.splitOn "." . number . version
91 toInt = read . T.unpack
92 parseVersion v = map toInt (version' v)
93 extractVersion = join . liftM (mkServerVersion . parseVersion)
95testServerBranch :: IO (Maybe ServerVersion)
96testServerBranch = getServerVersion >>= \v -> return $ liftM serverBranch v
98atleast :: ServerVersion -> IO Bool
99atleast v = testServerBranch >>= \x -> return $ x >= Just (serverBranch v)
101atmost :: ServerVersion -> IO Bool
102atmost v = testServerBranch >>= \x -> return $ x <= Just (serverBranch v)
104is :: ServerVersion -> IO Bool
105is v = testServerBranch >>= \x -> return $ x == Just (serverBranch v)
107when' :: Monad m => m Bool -> m () -> m ()
108when' b f = b >>= \x -> when x f
110(==~) :: (ApproxEq a, Show a) => a -> a -> Property
111a ==~ b = counterexample (show a ++ " !=~ " ++ show b) (a =~ b)
113propJSON :: forall a. (Arbitrary a, ToJSON a, FromJSON a, Show a, ApproxEq a, Typeable a) => Proxy a -> Spec
114propJSON _ = prop testName $ \(a :: a) ->
115 let jsonStr = "via " <> BL8.unpack (encode a)
116 in counterexample jsonStr (parseEither parseJSON (toJSON a) ==~ Right a)
117 where testName = show ty <> " FromJSON/ToJSON roundtrips"
118 ty = typeOf (undefined :: a)
120data Location = Location { lat :: Double
121 , lon :: Double } deriving (Eq, Generic, Show)
123data Tweet = Tweet { user :: Text
124 , postDate :: UTCTime
125 , message :: Text
126 , age :: Int
127 , location :: Location
128 , extra :: Maybe Text }
129 deriving (Eq, Generic, Show)
131instance ToJSON Tweet where
132 toJSON = genericToJSON defaultOptions
133instance FromJSON Tweet where
134 parseJSON = genericParseJSON defaultOptions
135instance ToJSON Location where
136 toJSON = genericToJSON defaultOptions
137instance FromJSON Location where
138 parseJSON = genericParseJSON defaultOptions
140data ParentMapping = ParentMapping deriving (Eq, Show)
142instance ToJSON ParentMapping where
143 toJSON ParentMapping =
144 object ["parent" .= Null ]
146data ChildMapping = ChildMapping deriving (Eq, Show)
148instance ToJSON ChildMapping where
149 toJSON ChildMapping =
150 object ["child" .=
151 object ["_parent" .= object ["type" .= ("parent" :: Text)]]
152 ]
154data TweetMapping = TweetMapping deriving (Eq, Show)
156instance ToJSON TweetMapping where
157 toJSON TweetMapping =
158 object ["tweet" .=
159 object ["properties" .=
160 object [ "user" .= object ["type" .= ("string" :: Text)]
161 -- Serializing the date as a date is breaking other tests, mysteriously.
162 -- , "postDate" .= object [ "type" .= ("date" :: Text)
163 -- , "format" .= ("YYYY-MM-dd`T`HH:mm:ss.SSSZZ" :: Text)]
164 , "message" .= object ["type" .= ("string" :: Text)]
165 , "age" .= object ["type" .= ("integer" :: Text)]
166 , "location" .= object ["type" .= ("geo_point" :: Text)]
167 ]]]
169exampleTweet :: Tweet
170exampleTweet = Tweet { user = "bitemyapp"
171 , postDate = UTCTime
172 (ModifiedJulianDay 55000)
173 (secondsToDiffTime 10)
174 , message = "Use haskell!"
175 , age = 10000
176 , location = Location 40.12 (-71.34)
177 , extra = Nothing }
179tweetWithExtra :: Tweet
180tweetWithExtra = Tweet { user = "bitemyapp"
181 , postDate = UTCTime
182 (ModifiedJulianDay 55000)
183 (secondsToDiffTime 10)
184 , message = "Use haskell!"
185 , age = 10000
186 , location = Location 40.12 (-71.34)
187 , extra = Just "blah blah" }
189newAge :: Int
190newAge = 31337
192newUser :: Text
193newUser = "someotherapp"
195tweetPatch :: Value
196tweetPatch =
197 object [ "age" .= newAge
198 , "user" .= newUser
199 ]
201patchedTweet :: Tweet
202patchedTweet = exampleTweet{age = newAge, user = newUser}
204otherTweet :: Tweet
205otherTweet = Tweet { user = "notmyapp"
206 , postDate = UTCTime
207 (ModifiedJulianDay 55000)
208 (secondsToDiffTime 11)
209 , message = "Use haskell!"
210 , age = 1000
211 , location = Location 40.12 (-71.34)
212 , extra = Nothing }
214resetIndex :: BH IO ()
215resetIndex = do
216 _ <- deleteExampleIndex
217 _ <- createExampleIndex
218 _ <- putMapping testIndex testMapping TweetMapping
219 return ()
221insertData :: BH IO Reply
222insertData = do
223 resetIndex
224 insertData' defaultIndexDocumentSettings
226insertData' :: IndexDocumentSettings -> BH IO Reply
227insertData' ids = do
228 r <- indexDocument testIndex testMapping ids exampleTweet (DocId "1")
229 _ <- refreshIndex testIndex
230 return r
232updateData :: BH IO Reply
233updateData = do
234 r <- updateDocument testIndex testMapping defaultIndexDocumentSettings tweetPatch (DocId "1")
235 _ <- refreshIndex testIndex
236 return r
238insertOther :: BH IO ()
239insertOther = do
240 _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings otherTweet (DocId "2")
241 _ <- refreshIndex testIndex
242 return ()
244insertExtra :: BH IO ()
245insertExtra = do
246 _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings tweetWithExtra (DocId "4")
247 _ <- refreshIndex testIndex
248 return ()
250insertWithSpaceInId :: BH IO ()
251insertWithSpaceInId = do
252 _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings exampleTweet (DocId "Hello World")
253 _ <- refreshIndex testIndex
254 return ()
256searchTweet :: Search -> BH IO (Either EsError Tweet)
257searchTweet search = do
258 result <- searchTweets search
259 let myTweet :: Either EsError Tweet
260 myTweet = grabFirst result
261 return myTweet
263searchTweets :: Search -> BH IO (Either EsError (SearchResult Tweet))
264searchTweets search = parseEsResponse =<< searchByIndex testIndex search
266searchExpectNoResults :: Search -> BH IO ()
267searchExpectNoResults search = do
268 result <- searchTweets search
269 let emptyHits = fmap (hits . searchHits) result
270 liftIO $
271 emptyHits `shouldBe` Right []
273searchExpectAggs :: Search -> BH IO ()
274searchExpectAggs search = do
275 reply <- searchByIndex testIndex search
276 let isEmpty x = return (M.null x)
277 let result = decode (responseBody reply) :: Maybe (SearchResult Tweet)
278 liftIO $
279 (result >>= aggregations >>= isEmpty) `shouldBe` Just False
281searchValidBucketAgg :: (BucketAggregation a, FromJSON a, Show a) =>
282 Search -> Text -> (Text -> AggregationResults -> Maybe (Bucket a)) -> BH IO ()
283searchValidBucketAgg search aggKey extractor = do
284 reply <- searchByIndex testIndex search
285 let bucketDocs = docCount . head . buckets
286 let result = decode (responseBody reply) :: Maybe (SearchResult Tweet)
287 let count = result >>= aggregations >>= extractor aggKey >>= \x -> return (bucketDocs x)
288 liftIO $
289 count `shouldBe` Just 1
291searchTermsAggHint :: [ExecutionHint] -> BH IO ()
292searchTermsAggHint hints = do
293 let terms hint = TermsAgg $ (mkTermsAggregation "user") { termExecutionHint = Just hint }
294 let search hint = mkAggregateSearch Nothing $ mkAggregations "users" $ terms hint
295 forM_ hints $ searchExpectAggs . search
296 forM_ hints (\x -> searchValidBucketAgg (search x) "users" toTerms)
298searchTweetHighlight :: Search -> BH IO (Either EsError (Maybe HitHighlight))
299searchTweetHighlight search = do
300 result <- searchTweets search
301 let myHighlight = fmap (hitHighlight . head . hits . searchHits) result
302 return myHighlight
304searchExpectSource :: Source -> Either EsError Value -> BH IO ()
305searchExpectSource src expected = do
306 _ <- insertData
307 let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
308 let search = (mkSearch (Just query) Nothing) { source = Just src }
309 reply <- searchByIndex testIndex search
310 result <- parseEsResponse reply
311 let value = grabFirst result
312 liftIO $
313 value `shouldBe` expected
315data BulkTest = BulkTest { name :: Text } deriving (Eq, Generic, Show)
316instance FromJSON BulkTest where
317 parseJSON = genericParseJSON defaultOptions
318instance ToJSON BulkTest where
319 toJSON = genericToJSON defaultOptions
321class GApproxEq f where
322 gApproxEq :: f a -> f a -> Bool
324-- | Unit type
325instance GApproxEq U1 where
326 gApproxEq U1 U1 = True
328-- | Sum type, ensure same constructors, recurse
329instance (GApproxEq a, GApproxEq b) => GApproxEq (a :+: b) where
330 gApproxEq (L1 a) (L1 b) = gApproxEq a b
331 gApproxEq (R1 a) (R1 b) = gApproxEq a b
332 gApproxEq _ _ = False
334-- | Product type, ensure each field is approx eq
335instance (GApproxEq a, GApproxEq b) => GApproxEq (a :*: b) where
336 gApproxEq (a1 :*: b1) (a2 :*: b2) = gApproxEq a1 a2 && gApproxEq b1 b2
338-- | Value type, actually check the values for approx equality
339instance (ApproxEq a) => GApproxEq (K1 i a) where
340 gApproxEq (K1 a) (K1 b) = a =~ b
342instance (GApproxEq f) => GApproxEq (M1 i t f) where
343 gApproxEq (M1 a) (M1 b) = gApproxEq a b
345-- | Typeclass for "equal where it matters". Use this to specify
346-- less-strict equivalence for things such as lists that can wind up
347-- in an unpredictable order
348class ApproxEq a where
349 (=~) :: a -> a -> Bool
350 default (=~) :: (Generic a, GApproxEq (Rep a)) => a -> a -> Bool
351 a =~ b = gApproxEq (G.from a) (G.from b)
353instance ApproxEq NominalDiffTime where (=~) = (==)
354instance ApproxEq UTCTime where (=~) = (==)
355instance ApproxEq Text where (=~) = (==)
356instance ApproxEq Bool where (=~) = (==)
357instance ApproxEq Int where (=~) = (==)
358instance ApproxEq Double where (=~) = (==)
359instance ApproxEq a => ApproxEq (NonEmpty a)
360instance ApproxEq a => ApproxEq (Maybe a)
361instance ApproxEq GeoPoint
362instance ApproxEq Regexp
363instance ApproxEq RangeValue
364instance ApproxEq LessThan
365instance ApproxEq LessThanEq
366instance ApproxEq LessThanD
367instance ApproxEq LessThanEqD
368instance ApproxEq GreaterThan
369instance ApproxEq GreaterThanEq
370instance ApproxEq GreaterThanD
371instance ApproxEq GreaterThanEqD
372instance ApproxEq MinimumMatchHighLow
373instance ApproxEq RegexpFlag
374instance ApproxEq RegexpFlags
375instance ApproxEq NullValue
376instance ApproxEq Version
377instance ApproxEq DistanceRange
378instance ApproxEq IndexName
379instance ApproxEq MappingName
380instance ApproxEq DocId
381instance ApproxEq IndexAliasRouting
382instance ApproxEq RoutingValue
383instance ApproxEq ShardCount
384instance ApproxEq ReplicaCount
385instance ApproxEq TemplateName
386instance ApproxEq TemplatePattern
387instance ApproxEq QueryString
388instance ApproxEq FieldName
389instance ApproxEq CacheName
390instance ApproxEq CacheKey
391instance ApproxEq Existence
392instance ApproxEq CutoffFrequency
393instance ApproxEq Analyzer
394instance ApproxEq Lenient
395instance ApproxEq Tiebreaker
396instance ApproxEq Boost
397instance ApproxEq BoostTerms
398instance ApproxEq MaxExpansions
399instance ApproxEq MinimumMatch
400instance ApproxEq DisableCoord
401instance ApproxEq IgnoreTermFrequency
402instance ApproxEq MinimumTermFrequency
403instance ApproxEq MaxQueryTerms
404instance ApproxEq Fuzziness
405instance ApproxEq PrefixLength
406instance ApproxEq TypeName
407instance ApproxEq PercentMatch
408instance ApproxEq StopWord
409instance ApproxEq QueryPath
410instance ApproxEq AllowLeadingWildcard
411instance ApproxEq LowercaseExpanded
412instance ApproxEq EnablePositionIncrements
413instance ApproxEq AnalyzeWildcard
414instance ApproxEq GeneratePhraseQueries
415instance ApproxEq Locale
416instance ApproxEq MaxWordLength
417instance ApproxEq MinWordLength
418instance ApproxEq PhraseSlop
419instance ApproxEq MinDocFrequency
420instance ApproxEq MaxDocFrequency
421instance ApproxEq Filter
422instance ApproxEq Query
423instance ApproxEq SimpleQueryStringQuery
424instance ApproxEq FieldOrFields
425instance ApproxEq SimpleQueryFlag
426instance ApproxEq RegexpQuery
427instance ApproxEq QueryStringQuery
428instance ApproxEq RangeQuery
429instance ApproxEq PrefixQuery
430instance ApproxEq NestedQuery
431instance ApproxEq MoreLikeThisFieldQuery
432instance ApproxEq MoreLikeThisQuery
433instance ApproxEq IndicesQuery
434instance ApproxEq HasParentQuery
435instance ApproxEq HasChildQuery
436instance ApproxEq FuzzyQuery
437instance ApproxEq FuzzyLikeFieldQuery
438instance ApproxEq FuzzyLikeThisQuery
439instance ApproxEq FilteredQuery
440instance ApproxEq DisMaxQuery
441instance ApproxEq CommonTermsQuery
442instance ApproxEq CommonMinimumMatch
443instance ApproxEq BoostingQuery
444instance ApproxEq BoolQuery
445instance ApproxEq MatchQuery
446instance ApproxEq MultiMatchQueryType
447instance ApproxEq BooleanOperator
448instance ApproxEq ZeroTermsQuery
449instance ApproxEq MatchQueryType
450instance ApproxEq AliasRouting
451instance ApproxEq IndexAliasCreate
452instance ApproxEq SearchAliasRouting
453instance ApproxEq ScoreType
454instance ApproxEq Distance
455instance ApproxEq DistanceUnit
456instance ApproxEq DistanceType
457instance ApproxEq OptimizeBbox
458instance ApproxEq GeoBoundingBoxConstraint
459instance ApproxEq GeoFilterType
460instance ApproxEq GeoBoundingBox
461instance ApproxEq LatLon
462instance ApproxEq RangeExecution
463instance ApproxEq FSType
464instance ApproxEq CompoundFormat
465instance ApproxEq InitialShardCount
466instance ApproxEq Bytes
467instance ApproxEq ReplicaBounds
468instance ApproxEq Term
469instance ApproxEq BoolMatch
470instance ApproxEq MultiMatchQuery
471instance ApproxEq IndexSettings
472instance ApproxEq AllocationPolicy
473instance ApproxEq Char
474instance ApproxEq a => ApproxEq [a] where
475 as =~ bs = and (zipWith (=~) as bs)
476instance (ApproxEq l, ApproxEq r) => ApproxEq (Either l r) where
477 Left a =~ Left b = a =~ b
478 Right a =~ Right b = a =~ b
479 _ =~ _ = False
480instance ApproxEq NodeAttrFilter
481instance ApproxEq NodeAttrName
483-- | Due to the way nodeattrfilters get serialized here, they may come
484-- out in a different order, but they are morally equivalent
485instance ApproxEq UpdatableIndexSetting where
486 RoutingAllocationInclude a =~ RoutingAllocationInclude b =
487 NE.sort a =~ NE.sort b
488 RoutingAllocationExclude a =~ RoutingAllocationExclude b =
489 NE.sort a =~ NE.sort b
490 RoutingAllocationRequire a =~ RoutingAllocationRequire b =
491 NE.sort a =~ NE.sort b
492 a =~ b = a == b
495noDuplicates :: Eq a => [a] -> Bool
496noDuplicates xs = nub xs == xs
498instance Arbitrary NominalDiffTime where
499 arbitrary = fromInteger <$> arbitrary
501instance (Arbitrary k, Ord k, Arbitrary v) => Arbitrary (M.Map k v) where
502 arbitrary = M.fromList <$> arbitrary
504instance Arbitrary Text where
505 arbitrary = T.pack <$> arbitrary
507instance Arbitrary UTCTime where
508 arbitrary = UTCTime
509 <$> arbitrary
510 <*> (fromRational . toRational <$> choose (0::Double, 86400))
512instance Arbitrary Day where
513 arbitrary = ModifiedJulianDay <$> (2000 +) <$> arbitrary
514 shrink = (ModifiedJulianDay <$>) . shrink . toModifiedJulianDay
516instance Arbitrary a => Arbitrary (NonEmpty a) where
517 arbitrary = liftA2 (:|) arbitrary arbitrary
519arbitraryScore :: Gen Score
520arbitraryScore = fmap getPositive <$> arbitrary
522instance (Arbitrary a, Typeable a) => Arbitrary (Hit a) where
523 arbitrary = Hit <$> arbitrary
524 <*> arbitrary
525 <*> arbitrary
526 <*> arbitraryScore
527 <*> arbitrary
528 <*> arbitrary
529 shrink = genericShrink
532instance (Arbitrary a, Typeable a) => Arbitrary (SearchHits a) where
533 arbitrary = reduceSize $ do
534 tot <- getPositive <$> arbitrary
535 score <- arbitraryScore
536 hs <- arbitrary
537 return $ SearchHits tot score hs
538 shrink = genericShrink
540reduceSize :: Gen a -> Gen a
541reduceSize f = sized $ \n -> resize (n `div` 2) f
543getSource :: EsResult a -> Maybe a
544getSource = fmap _source . foundResult
546grabFirst :: Either EsError (SearchResult a) -> Either EsError a
547grabFirst r =
548 case fmap (hitSource . head . hits . searchHits) r of
549 (Left e) -> Left e
550 (Right Nothing) -> Left (EsError 500 "Source was missing")
551 (Right (Just x)) -> Right x
553-------------------------------------------------------------------------------
554arbitraryAlphaNum :: Gen Char
555arbitraryAlphaNum = oneof [choose ('a', 'z')
556 ,choose ('A','Z')
557 , choose ('0', '9')]
559instance Arbitrary RoutingValue where
560 arbitrary = RoutingValue . T.pack <$> listOf1 arbitraryAlphaNum
562instance Arbitrary AliasRouting where
563 arbitrary = oneof [allAlias
564 ,one
565 ,theOther
566 ,both]
567 where one = GranularAliasRouting
568 <$> (Just <$> arbitrary)
569 <*> pure Nothing
570 theOther = GranularAliasRouting Nothing
571 <$> (Just <$> arbitrary)
572 both = GranularAliasRouting
573 <$> (Just <$> arbitrary)
574 <*> (Just <$> arbitrary)
575 allAlias = AllAliasRouting <$> arbitrary
576 shrink = genericShrink
579instance Arbitrary FieldName where
580 arbitrary = FieldName . T.pack <$> listOf1 arbitraryAlphaNum
581 shrink = genericShrink
584instance Arbitrary RegexpFlags where
585 arbitrary = oneof [ pure AllRegexpFlags
586 , pure NoRegexpFlags
587 , SomeRegexpFlags <$> genUniqueFlags
588 ]
589 where genUniqueFlags = NE.fromList . nub <$> listOf1 arbitrary
590 shrink = genericShrink
593instance Arbitrary IndexAliasCreate where
594 arbitrary = IndexAliasCreate <$> arbitrary <*> reduceSize arbitrary
595 shrink = genericShrink
597instance Arbitrary Query where
598 arbitrary = reduceSize $ oneof [ TermQuery <$> arbitrary <*> arbitrary
599 , TermsQuery <$> arbitrary <*> arbitrary
600 , QueryMatchQuery <$> arbitrary
601 , QueryMultiMatchQuery <$> arbitrary
602 , QueryBoolQuery <$> arbitrary
603 , QueryBoostingQuery <$> arbitrary
604 , QueryCommonTermsQuery <$> arbitrary
605 , ConstantScoreFilter <$> arbitrary <*> arbitrary
606 , ConstantScoreQuery <$> arbitrary <*> arbitrary
607 , QueryDisMaxQuery <$> arbitrary
608 , QueryFilteredQuery <$> arbitrary
609 , QueryFuzzyLikeThisQuery <$> arbitrary
610 , QueryFuzzyLikeFieldQuery <$> arbitrary
611 , QueryFuzzyQuery <$> arbitrary
612 , QueryHasChildQuery <$> arbitrary
613 , QueryHasParentQuery <$> arbitrary
614 , IdsQuery <$> arbitrary <*> arbitrary
615 , QueryIndicesQuery <$> arbitrary
616 , MatchAllQuery <$> arbitrary
617 , QueryMoreLikeThisQuery <$> arbitrary
618 , QueryMoreLikeThisFieldQuery <$> arbitrary
619 , QueryNestedQuery <$> arbitrary
620 , QueryPrefixQuery <$> arbitrary
621 , QueryQueryStringQuery <$> arbitrary
622 , QuerySimpleQueryStringQuery <$> arbitrary
623 , QueryRangeQuery <$> arbitrary
624 , QueryRegexpQuery <$> arbitrary
625 ]
626 shrink = genericShrink
628instance Arbitrary Filter where
629 arbitrary = reduceSize $ oneof [ AndFilter <$> arbitrary <*> arbitrary
630 , OrFilter <$> arbitrary <*> arbitrary
631 , NotFilter <$> arbitrary <*> arbitrary
632 , pure IdentityFilter
633 , BoolFilter <$> arbitrary
634 , ExistsFilter <$> arbitrary
635 , GeoBoundingBoxFilter <$> arbitrary
636 , GeoDistanceFilter <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
637 , GeoDistanceRangeFilter <$> arbitrary <*> arbitrary
638 , GeoPolygonFilter <$> arbitrary <*> arbitrary
639 , IdsFilter <$> arbitrary <*> arbitrary
640 , LimitFilter <$> arbitrary
641 , MissingFilter <$> arbitrary <*> arbitrary <*> arbitrary
642 , PrefixFilter <$> arbitrary <*> arbitrary <*> arbitrary
643 , QueryFilter <$> arbitrary <*> arbitrary
644 , RangeFilter <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
645 , RegexpFilter <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
646 , TermFilter <$> arbitrary <*> arbitrary]
647 shrink = genericShrink
649instance Arbitrary ReplicaBounds where
650 arbitrary = oneof [ replicasBounded
651 , replicasLowerBounded
652 , pure ReplicasUnbounded
653 ]
654 where replicasBounded = do Positive a <- arbitrary
655 Positive b <- arbitrary
656 return (ReplicasBounded a b)
657 replicasLowerBounded = do Positive a <- arbitrary
658 return (ReplicasLowerBounded a)
660instance Arbitrary NodeAttrName where
661 arbitrary = NodeAttrName . T.pack . getNonEmpty <$> arbitrary
664instance Arbitrary NodeAttrFilter where
665 arbitrary = do
666 n <- arbitrary
667 s:ss <- listOf1 (listOf1 arbitraryAlphaNum)
668 let ts = T.pack <$> s :| ss
669 return (NodeAttrFilter n ts)
671$(derive makeArbitrary ''IndexName)
672$(derive makeArbitrary ''MappingName)
673$(derive makeArbitrary ''DocId)
674$(derive makeArbitrary ''Version)
675$(derive makeArbitrary ''IndexAliasRouting)
676$(derive makeArbitrary ''ShardCount)
677$(derive makeArbitrary ''ReplicaCount)
678$(derive makeArbitrary ''TemplateName)
679$(derive makeArbitrary ''TemplatePattern)
680$(derive makeArbitrary ''QueryString)
681$(derive makeArbitrary ''CacheName)
682$(derive makeArbitrary ''CacheKey)
683$(derive makeArbitrary ''Existence)
684$(derive makeArbitrary ''CutoffFrequency)
685$(derive makeArbitrary ''Analyzer)
686$(derive makeArbitrary ''MaxExpansions)
687$(derive makeArbitrary ''Lenient)
688$(derive makeArbitrary ''Tiebreaker)
689$(derive makeArbitrary ''Boost)
690$(derive makeArbitrary ''BoostTerms)
691$(derive makeArbitrary ''MinimumMatch)
692$(derive makeArbitrary ''DisableCoord)
693$(derive makeArbitrary ''IgnoreTermFrequency)
694$(derive makeArbitrary ''MinimumTermFrequency)
695$(derive makeArbitrary ''MaxQueryTerms)
696$(derive makeArbitrary ''Fuzziness)
697$(derive makeArbitrary ''PrefixLength)
698$(derive makeArbitrary ''TypeName)
699$(derive makeArbitrary ''PercentMatch)
700$(derive makeArbitrary ''StopWord)
701$(derive makeArbitrary ''QueryPath)
702$(derive makeArbitrary ''AllowLeadingWildcard)
703$(derive makeArbitrary ''LowercaseExpanded)
704$(derive makeArbitrary ''EnablePositionIncrements)
705$(derive makeArbitrary ''AnalyzeWildcard)
706$(derive makeArbitrary ''GeneratePhraseQueries)
707$(derive makeArbitrary ''Locale)
708$(derive makeArbitrary ''MaxWordLength)
709$(derive makeArbitrary ''MinWordLength)
710$(derive makeArbitrary ''PhraseSlop)
711$(derive makeArbitrary ''MinDocFrequency)
712$(derive makeArbitrary ''MaxDocFrequency)
713$(derive makeArbitrary ''Regexp)
714$(derive makeArbitrary ''SimpleQueryStringQuery)
715$(derive makeArbitrary ''FieldOrFields)
716$(derive makeArbitrary ''SimpleQueryFlag)
717$(derive makeArbitrary ''RegexpQuery)
718$(derive makeArbitrary ''QueryStringQuery)
719$(derive makeArbitrary ''RangeQuery)
720$(derive makeArbitrary ''RangeValue)
721$(derive makeArbitrary ''PrefixQuery)
722$(derive makeArbitrary ''NestedQuery)
723$(derive makeArbitrary ''MoreLikeThisFieldQuery)
724$(derive makeArbitrary ''MoreLikeThisQuery)
725$(derive makeArbitrary ''IndicesQuery)
726$(derive makeArbitrary ''HasParentQuery)
727$(derive makeArbitrary ''HasChildQuery)
728$(derive makeArbitrary ''FuzzyQuery)
729$(derive makeArbitrary ''FuzzyLikeFieldQuery)
730$(derive makeArbitrary ''FuzzyLikeThisQuery)
731$(derive makeArbitrary ''FilteredQuery)
732$(derive makeArbitrary ''DisMaxQuery)
733$(derive makeArbitrary ''CommonTermsQuery)
734$(derive makeArbitrary ''DistanceRange)
735$(derive makeArbitrary ''MultiMatchQuery)
736$(derive makeArbitrary ''LessThanD)
737$(derive makeArbitrary ''LessThanEqD)
738$(derive makeArbitrary ''GreaterThanD)
739$(derive makeArbitrary ''GreaterThanEqD)
740$(derive makeArbitrary ''LessThan)
741$(derive makeArbitrary ''LessThanEq)
742$(derive makeArbitrary ''GreaterThan)
743$(derive makeArbitrary ''GreaterThanEq)
744$(derive makeArbitrary ''GeoPoint)
745$(derive makeArbitrary ''NullValue)
746$(derive makeArbitrary ''MinimumMatchHighLow)
747$(derive makeArbitrary ''CommonMinimumMatch)
748$(derive makeArbitrary ''BoostingQuery)
749$(derive makeArbitrary ''BoolQuery)
750$(derive makeArbitrary ''MatchQuery)
751$(derive makeArbitrary ''MultiMatchQueryType)
752$(derive makeArbitrary ''BooleanOperator)
753$(derive makeArbitrary ''ZeroTermsQuery)
754$(derive makeArbitrary ''MatchQueryType)
755$(derive makeArbitrary ''SearchAliasRouting)
756$(derive makeArbitrary ''ScoreType)
757$(derive makeArbitrary ''Distance)
758$(derive makeArbitrary ''DistanceUnit)
759$(derive makeArbitrary ''DistanceType)
760$(derive makeArbitrary ''OptimizeBbox)
761$(derive makeArbitrary ''GeoBoundingBoxConstraint)
762$(derive makeArbitrary ''GeoFilterType)
763$(derive makeArbitrary ''GeoBoundingBox)
764$(derive makeArbitrary ''LatLon)
765$(derive makeArbitrary ''RangeExecution)
766$(derive makeArbitrary ''RegexpFlag)
767$(derive makeArbitrary ''BoolMatch)
768$(derive makeArbitrary ''Term)
769$(derive makeArbitrary ''IndexSettings)
770$(derive makeArbitrary ''UpdatableIndexSetting)
771$(derive makeArbitrary ''Bytes)
772$(derive makeArbitrary ''AllocationPolicy)
773$(derive makeArbitrary ''InitialShardCount)
774$(derive makeArbitrary ''FSType)
775$(derive makeArbitrary ''CompoundFormat)
778main :: IO ()
779main = hspec $ do
781 describe "index create/delete API" $ do
782 it "creates and then deletes the requested index" $ withTestEnv $ do
783 -- priming state.
784 _ <- deleteExampleIndex
785 resp <- createExampleIndex
786 deleteResp <- deleteExampleIndex
787 liftIO $ do
788 validateStatus resp 200
789 validateStatus deleteResp 200
791 describe "error parsing" $ do
792 it "can parse EsErrors" $ withTestEnv $ do
793 res <- getDocument (IndexName "bogus") (MappingName "also_bogus") (DocId "bogus_as_well")
794 let errorResp = eitherDecode (responseBody res)
795 liftIO (errorResp `shouldBe` Right (EsError 404 "IndexMissingException[[bogus] missing]"))
797 describe "document API" $ do
798 it "indexes, updates, gets, and then deletes the generated document" $ withTestEnv $ do
799 _ <- insertData
800 _ <- updateData
801 docInserted <- getDocument testIndex testMapping (DocId "1")
802 let newTweet = eitherDecode
803 (responseBody docInserted) :: Either String (EsResult Tweet)
804 liftIO $ (fmap getSource newTweet `shouldBe` Right (Just patchedTweet))
806 it "indexes, gets, and then deletes the generated document with a DocId containing a space" $ withTestEnv $ do
807 _ <- insertWithSpaceInId
808 docInserted <- getDocument testIndex testMapping (DocId "Hello World")
809 let newTweet = eitherDecode
810 (responseBody docInserted) :: Either String (EsResult Tweet)
811 liftIO $ (fmap getSource newTweet `shouldBe` Right (Just exampleTweet))
813 it "produces a parseable result when looking up a bogus document" $ withTestEnv $ do
814 doc <- getDocument testIndex testMapping (DocId "bogus")
815 let noTweet = eitherDecode
816 (responseBody doc) :: Either String (EsResult Tweet)
817 liftIO $ fmap foundResult noTweet `shouldBe` Right Nothing
819 it "can use optimistic concurrency control" $ withTestEnv $ do
820 let ev = ExternalDocVersion minBound
821 let cfg = defaultIndexDocumentSettings { idsVersionControl = ExternalGT ev }
822 resetIndex
823 res <- insertData' cfg
824 liftIO $ isCreated res `shouldBe` True
825 res' <- insertData' cfg
826 liftIO $ isVersionConflict res' `shouldBe` True
828 it "indexes two documents in a parent/child relationship and checks that the child exists" $ withTestEnv $ do
829 resetIndex
830 _ <- putMapping testIndex (MappingName "parent") ParentMapping
831 _ <- putMapping testIndex (MappingName "child") ChildMapping
832 _ <- indexDocument testIndex (MappingName "parent") defaultIndexDocumentSettings exampleTweet (DocId "1")
833 let parent = (Just . DocumentParent . DocId) "1"
834 ids = IndexDocumentSettings NoVersionControl parent
835 _ <- indexDocument testIndex (MappingName "child") ids otherTweet (DocId "2")
836 _ <- refreshIndex testIndex
837 exists <- documentExists testIndex (MappingName "child") parent (DocId "2")
838 liftIO $ exists `shouldBe` True
840 describe "template API" $ do
841 it "can create a template" $ withTestEnv $ do
842 let idxTpl = IndexTemplate (TemplatePattern "tweet-*") (Just (IndexSettings (ShardCount 1) (ReplicaCount 1))) [toJSON TweetMapping]
843 resp <- putTemplate idxTpl (TemplateName "tweet-tpl")
844 liftIO $ validateStatus resp 200
846 it "can detect if a template exists" $ withTestEnv $ do
847 exists <- templateExists (TemplateName "tweet-tpl")
848 liftIO $ exists `shouldBe` True
850 it "can delete a template" $ withTestEnv $ do
851 resp <- deleteTemplate (TemplateName "tweet-tpl")
852 liftIO $ validateStatus resp 200
854 it "can detect if a template doesn't exist" $ withTestEnv $ do
855 exists <- templateExists (TemplateName "tweet-tpl")
856 liftIO $ exists `shouldBe` False
858 describe "bulk API" $ do
859 it "inserts all documents we request" $ withTestEnv $ do
860 _ <- insertData
861 let firstTest = BulkTest "blah"
862 let secondTest = BulkTest "bloo"
863 let firstDoc = BulkIndex testIndex
864 testMapping (DocId "2") (toJSON firstTest)
865 let secondDoc = BulkCreate testIndex
866 testMapping (DocId "3") (toJSON secondTest)
867 let stream = V.fromList [firstDoc, secondDoc]
868 _ <- bulk stream
869 _ <- refreshIndex testIndex
870 fDoc <- getDocument testIndex testMapping (DocId "2")
871 sDoc <- getDocument testIndex testMapping (DocId "3")
872 let maybeFirst = eitherDecode $ responseBody fDoc :: Either String (EsResult BulkTest)
873 let maybeSecond = eitherDecode $ responseBody sDoc :: Either String (EsResult BulkTest)
874 liftIO $ do
875 fmap getSource maybeFirst `shouldBe` Right (Just firstTest)
876 fmap getSource maybeSecond `shouldBe` Right (Just secondTest)
879 describe "query API" $ do
880 it "returns document for term query and identity filter" $ withTestEnv $ do
881 _ <- insertData
882 let query = TermQuery (Term "user" "bitemyapp") Nothing
883 let filter = IdentityFilter <&&> IdentityFilter
884 let search = mkSearch (Just query) (Just filter)
885 myTweet <- searchTweet search
886 liftIO $
887 myTweet `shouldBe` Right exampleTweet
889 it "handles constant score queries" $ withTestEnv $ do
890 _ <- insertData
891 let query = TermsQuery "user" ("bitemyapp" :| [])
892 let cfQuery = ConstantScoreQuery query (Boost 1.0)
893 let filter = IdentityFilter
894 let search = mkSearch (Just cfQuery) (Just filter)
895 myTweet <- searchTweet search
896 liftIO $
897 myTweet `shouldBe` Right exampleTweet
898 it "handles constant score filters" $ withTestEnv $ do
899 _ <- insertData
900 let query = TermsQuery "user" ("bitemyapp" :| [])
901 let cfFilter = ConstantScoreFilter IdentityFilter (Boost 1.0)
902 let boolQuery = mkBoolQuery [query, cfFilter] [] []
903 let search = mkSearch (Just (QueryBoolQuery boolQuery)) Nothing
904 myTweet <- searchTweet search
905 liftIO $
906 myTweet `shouldBe` Right exampleTweet
909 it "returns document for terms query and identity filter" $ withTestEnv $ do
910 _ <- insertData
911 let query = TermsQuery "user" ("bitemyapp" :| [])
912 let filter = IdentityFilter <&&> IdentityFilter
913 let search = mkSearch (Just query) (Just filter)
914 myTweet <- searchTweet search
915 liftIO $
916 myTweet `shouldBe` Right exampleTweet
918 it "returns document for match query" $ withTestEnv $ do
919 _ <- insertData
920 let query = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
921 let search = mkSearch (Just query) Nothing
922 myTweet <- searchTweet search
923 liftIO $
924 myTweet `shouldBe` Right exampleTweet
926 it "returns document for multi-match query" $ withTestEnv $ do
927 _ <- insertData
928 let flds = [FieldName "user", FieldName "message"]
929 let query = QueryMultiMatchQuery $ mkMultiMatchQuery flds (QueryString "bitemyapp")
930 let search = mkSearch (Just query) Nothing
931 myTweet <- searchTweet search
932 liftIO $
933 myTweet `shouldBe` Right exampleTweet
935 it "returns document for bool query" $ withTestEnv $ do
936 _ <- insertData
937 let innerQuery = QueryMatchQuery $
938 mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
939 let query = QueryBoolQuery $
940 mkBoolQuery [innerQuery] [] []
941 let search = mkSearch (Just query) Nothing
942 myTweet <- searchTweet search
943 liftIO $
944 myTweet `shouldBe` Right exampleTweet
946 it "returns document for boosting query" $ withTestEnv $ do
947 _ <- insertData
948 let posQuery = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
949 let negQuery = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "notmyapp")
950 let query = QueryBoostingQuery $ BoostingQuery posQuery negQuery (Boost 0.2)
951 let search = mkSearch (Just query) Nothing
952 myTweet <- searchTweet search
953 liftIO $
954 myTweet `shouldBe` Right exampleTweet
956 it "returns document for common terms query" $ withTestEnv $ do
957 _ <- insertData
958 let query = QueryCommonTermsQuery $
959 CommonTermsQuery (FieldName "user")
960 (QueryString "bitemyapp")
961 (CutoffFrequency 0.0001)
962 Or Or Nothing Nothing Nothing Nothing
963 let search = mkSearch (Just query) Nothing
964 myTweet <- searchTweet search
965 liftIO $
966 myTweet `shouldBe` Right exampleTweet
969 describe "sorting" $ do
970 it "returns documents in the right order" $ withTestEnv $ do
971 _ <- insertData
972 _ <- insertOther
973 let sortSpec = DefaultSortSpec $ mkSort (FieldName "age") Ascending
974 let search = Search Nothing
975 (Just IdentityFilter) (Just [sortSpec]) Nothing Nothing
976 False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing
977 result <- searchTweets search
978 let myTweet = grabFirst result
979 liftIO $
980 myTweet `shouldBe` Right otherTweet
983 describe "filtering API" $ do
984 it "returns document for composed boolmatch and identity" $ withTestEnv $ do
985 _ <- insertData
986 let queryFilter = BoolFilter (MustMatch (Term "user" "bitemyapp") False)
987 <&&> IdentityFilter
988 let search = mkSearch Nothing (Just queryFilter)
989 myTweet <- searchTweet search
990 liftIO $
991 myTweet `shouldBe` Right exampleTweet
993 it "returns document for term filter" $ withTestEnv $ do
994 _ <- insertData
995 let termFilter = TermFilter (Term "user" "bitemyapp") False
996 let search = mkSearch Nothing (Just termFilter)
997 myTweet <- searchTweet search
998 liftIO $
999 myTweet `shouldBe` Right exampleTweet
1001 it "returns document for existential filter" $ withTestEnv $ do
1002 _ <- insertData
1003 let search = mkSearch Nothing (Just (ExistsFilter (FieldName "user")))
1004 myTweet <- searchTweet search
1005 liftIO $
1006 myTweet `shouldBe` Right exampleTweet
1008 it "returns document for geo boundingbox filter" $ withTestEnv $ do
1009 _ <- insertData
1010 let box = GeoBoundingBox (LatLon 40.73 (-74.1)) (LatLon 40.10 (-71.12))
1011 let bbConstraint = GeoBoundingBoxConstraint (FieldName "tweet.location") box False GeoFilterMemory
1012 let geoFilter = GeoBoundingBoxFilter bbConstraint
1013 let search = mkSearch Nothing (Just geoFilter)
1014 myTweet <- searchTweet search
1015 liftIO $
1016 myTweet `shouldBe` Right exampleTweet
1018 it "doesn't return document for nonsensical boundingbox filter" $ withTestEnv $ do
1019 _ <- insertData
1020 let box = GeoBoundingBox (LatLon 0.73 (-4.1)) (LatLon 0.10 (-1.12))
1021 let bbConstraint = GeoBoundingBoxConstraint (FieldName "tweet.location") box False GeoFilterMemory
1022 let geoFilter = GeoBoundingBoxFilter bbConstraint
1023 let search = mkSearch Nothing (Just geoFilter)
1024 searchExpectNoResults search
1026 it "returns document for geo distance filter" $ withTestEnv $ do
1027 _ <- insertData
1028 let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
1029 let distance = Distance 10.0 Miles
1030 let optimizeBbox = OptimizeGeoFilterType GeoFilterMemory
1031 let geoFilter = GeoDistanceFilter geoPoint distance SloppyArc optimizeBbox False
1032 let search = mkSearch Nothing (Just geoFilter)
1033 myTweet <- searchTweet search
1034 liftIO $
1035 myTweet `shouldBe` Right exampleTweet
1037 it "returns document for geo distance range filter" $ withTestEnv $ do
1038 _ <- insertData
1039 let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
1040 let distanceRange = DistanceRange (Distance 0.0 Miles) (Distance 10.0 Miles)
1041 let geoFilter = GeoDistanceRangeFilter geoPoint distanceRange
1042 let search = mkSearch Nothing (Just geoFilter)
1043 myTweet <- searchTweet search
1044 liftIO $
1045 myTweet `shouldBe` Right exampleTweet
1047 it "doesn't return document for wild geo distance range filter" $ withTestEnv $ do
1048 _ <- insertData
1049 let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
1050 let distanceRange = DistanceRange (Distance 100.0 Miles) (Distance 1000.0 Miles)
1051 let geoFilter = GeoDistanceRangeFilter geoPoint distanceRange
1052 let search = mkSearch Nothing (Just geoFilter)
1053 searchExpectNoResults search
1055 it "returns document for geo polygon filter" $ withTestEnv $ do
1056 _ <- insertData
1057 let points = [LatLon 40.0 (-70.00),
1058 LatLon 40.0 (-72.00),
1059 LatLon 41.0 (-70.00),
1060 LatLon 41.0 (-72.00)]
1061 let geoFilter = GeoPolygonFilter (FieldName "tweet.location") points
1062 let search = mkSearch Nothing (Just geoFilter)
1063 myTweet <- searchTweet search
1064 liftIO $
1065 myTweet `shouldBe` Right exampleTweet
1067 it "doesn't return document for bad geo polygon filter" $ withTestEnv $ do
1068 _ <- insertData
1069 let points = [LatLon 40.0 (-70.00),
1070 LatLon 40.0 (-71.00),
1071 LatLon 41.0 (-70.00),
1072 LatLon 41.0 (-71.00)]
1073 let geoFilter = GeoPolygonFilter (FieldName "tweet.location") points
1074 let search = mkSearch Nothing (Just geoFilter)
1075 searchExpectNoResults search
1077 it "returns document for ids filter" $ withTestEnv $ do
1078 _ <- insertData
1079 let filter = IdsFilter (MappingName "tweet") [DocId "1"]
1080 let search = mkSearch Nothing (Just filter)
1081 myTweet <- searchTweet search
1082 liftIO $
1083 myTweet `shouldBe` Right exampleTweet
1085 it "returns document for Double range filter" $ withTestEnv $ do
1086 _ <- insertData
1087 let filter = RangeFilter (FieldName "age")
1088 (RangeDoubleGtLt (GreaterThan 1000.0) (LessThan 100000.0))
1089 RangeExecutionIndex False
1090 let search = mkSearch Nothing (Just filter)
1091 myTweet <- searchTweet search
1092 liftIO $
1093 myTweet `shouldBe` Right exampleTweet
1095 it "returns document for UTCTime date filter" $ withTestEnv $ do
1096 _ <- insertData
1097 let filter = RangeFilter (FieldName "postDate")
1098 (RangeDateGtLt
1099 (GreaterThanD (UTCTime
1100 (ModifiedJulianDay 54000)
1101 (secondsToDiffTime 0)))
1102 (LessThanD (UTCTime
1103 (ModifiedJulianDay 55000)
1104 (secondsToDiffTime 11))))
1105 RangeExecutionIndex False
1106 let search = mkSearch Nothing (Just filter)
1107 myTweet <- searchTweet search
1108 liftIO $
1109 myTweet `shouldBe` Right exampleTweet
1111 it "returns document for regexp filter" $ withTestEnv $ do
1112 _ <- insertData
1113 let filter = RegexpFilter (FieldName "user") (Regexp "bite.*app")
1114 AllRegexpFlags (CacheName "test") False (CacheKey "key")
1115 let search = mkSearch Nothing (Just filter)
1116 myTweet <- searchTweet search
1117 liftIO $
1118 myTweet `shouldBe` Right exampleTweet
1120 it "doesn't return document for non-matching regexp filter" $ withTestEnv $ do
1121 _ <- insertData
1122 let filter = RegexpFilter (FieldName "user")
1123 (Regexp "boy") AllRegexpFlags
1124 (CacheName "test") False (CacheKey "key")
1125 let search = mkSearch Nothing (Just filter)
1126 searchExpectNoResults search
1128 it "returns document for query filter, uncached" $ withTestEnv $ do
1129 _ <- insertData
1130 let filter = QueryFilter (TermQuery (Term "user" "bitemyapp") Nothing) True
1131 search = mkSearch Nothing (Just filter)
1132 myTweet <- searchTweet search
1133 liftIO $ myTweet `shouldBe` Right exampleTweet
1135 it "returns document for query filter, cached" $ withTestEnv $ do
1136 _ <- insertData
1137 let filter = QueryFilter (TermQuery (Term "user" "bitemyapp") Nothing) False
1138 search = mkSearch Nothing (Just filter)
1139 myTweet <- searchTweet search
1140 liftIO $ myTweet `shouldBe` Right exampleTweet
1142 describe "Aggregation API" $ do
1143 it "returns term aggregation results" $ withTestEnv $ do
1144 _ <- insertData
1145 let terms = TermsAgg $ mkTermsAggregation "user"
1146 let search = mkAggregateSearch Nothing $ mkAggregations "users" terms
1147 searchExpectAggs search
1148 searchValidBucketAgg search "users" toTerms
1150 it "can give collection hint parameters to term aggregations" $ when' (atleast es13) $ withTestEnv $ do
1151 _ <- insertData
1152 let terms = TermsAgg $ (mkTermsAggregation "user") { termCollectMode = Just BreadthFirst }
1153 let search = mkAggregateSearch Nothing $ mkAggregations "users" terms
1154 searchExpectAggs search
1155 searchValidBucketAgg search "users" toTerms
1157 -- One of these fails with 1.7.3
1158 it "can give execution hint parameters to term aggregations" $ when' (atmost es11) $ withTestEnv $ do
1159 _ <- insertData
1160 searchTermsAggHint [Map, Ordinals]
1162 it "can give execution hint parameters to term aggregations" $ when' (is es12) $ withTestEnv $ do
1163 _ <- insertData
1164 searchTermsAggHint [GlobalOrdinals, GlobalOrdinalsHash, GlobalOrdinalsLowCardinality, Map, Ordinals]
1166 it "can give execution hint parameters to term aggregations" $ when' (atleast es12) $ withTestEnv $ do
1167 _ <- insertData
1168 searchTermsAggHint [GlobalOrdinals, GlobalOrdinalsHash, GlobalOrdinalsLowCardinality, Map]
1169 -- One of the above.
1171 it "can execute value_count aggregations" $ withTestEnv $ do
1172 _ <- insertData
1173 _ <- insertOther
1174 let ags = mkAggregations "user_count" (ValueCountAgg (FieldValueCount (FieldName "user"))) <>
1175 mkAggregations "bogus_count" (ValueCountAgg (FieldValueCount (FieldName "bogus")))
1176 let search = mkAggregateSearch Nothing ags
1177 let docCountPair k n = (k, object ["value" .= Number n])
1178 res <- searchTweets search
1179 liftIO $
1180 fmap aggregations res `shouldBe` Right (Just (M.fromList [ docCountPair "user_count" 2
1181 , docCountPair "bogus_count" 0
1182 ]))
1184 it "can execute filter aggregations" $ withTestEnv $ do
1185 _ <- insertData
1186 _ <- insertOther
1187 let ags = mkAggregations "bitemyapps" (FilterAgg (FilterAggregation (TermFilter (Term "user" "bitemyapp") defaultCache) Nothing)) <>
1188 mkAggregations "notmyapps" (FilterAgg (FilterAggregation (TermFilter (Term "user" "notmyapp") defaultCache) Nothing))
1189 let search = mkAggregateSearch Nothing ags
1190 let docCountPair k n = (k, object ["doc_count" .= Number n])
1191 res <- searchTweets search
1192 liftIO $
1193 fmap aggregations res `shouldBe` Right (Just (M.fromList [ docCountPair "bitemyapps" 1
1194 , docCountPair "notmyapps" 1
1195 ]))
1196 it "can execute date_range aggregations" $ withTestEnv $ do
1197 let now = fromGregorian 2015 3 14
1198 let ltAMonthAgo = UTCTime (fromGregorian 2015 3 1) 0
1199 let ltAWeekAgo = UTCTime (fromGregorian 2015 3 10) 0
1200 let oldDoc = exampleTweet { postDate = ltAMonthAgo }
1201 let newDoc = exampleTweet { postDate = ltAWeekAgo }
1202 _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings oldDoc (DocId "1")
1203 _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings newDoc (DocId "2")
1204 _ <- refreshIndex testIndex
1205 let thisMonth = DateRangeFrom (DateMathExpr (DMDate now) [SubtractTime 1 DMMonth])
1206 let thisWeek = DateRangeFrom (DateMathExpr (DMDate now) [SubtractTime 1 DMWeek])
1207 let agg = DateRangeAggregation (FieldName "postDate") Nothing (thisMonth :| [thisWeek])
1208 let ags = mkAggregations "date_ranges" (DateRangeAgg agg)
1209 let search = mkAggregateSearch Nothing ags
1210 res <- searchTweets search
1211 liftIO $ hitsTotal . searchHits <$> res `shouldBe` Right 2
1212 let bucks = do magrs <- fmapL show (aggregations <$> res)
1213 agrs <- note "no aggregations returned" magrs
1214 rawBucks <- note "no date_ranges aggregation" $ M.lookup "date_ranges" agrs
1215 parseEither parseJSON rawBucks
1216 let fromMonthT = UTCTime (fromGregorian 2015 2 14) 0
1217 let fromWeekT = UTCTime (fromGregorian 2015 3 7) 0
1218 liftIO $ buckets <$> bucks `shouldBe` Right [ DateRangeResult "2015-02-14T00:00:00.000Z-*"
1219 (Just fromMonthT)
1220 (Just "2015-02-14T00:00:00.000Z")
1221 Nothing
1222 Nothing
1224 Nothing
1225 , DateRangeResult "2015-03-07T00:00:00.000Z-*"
1226 (Just fromWeekT)
1227 (Just "2015-03-07T00:00:00.000Z")
1228 Nothing
1229 Nothing
1231 Nothing
1234 it "returns date histogram aggregation results" $ withTestEnv $ do
1235 _ <- insertData
1236 let histogram = DateHistogramAgg $ mkDateHistogram (FieldName "postDate") Minute
1237 let search = mkAggregateSearch Nothing (mkAggregations "byDate" histogram)
1238 searchExpectAggs search
1239 searchValidBucketAgg search "byDate" toDateHistogram
1241 it "returns date histogram using fractional date" $ withTestEnv $ do
1242 _ <- insertData
1243 let periods = [Year, Quarter, Month, Week, Day, Hour, Minute, Second]
1244 let fractionals = map (FractionalInterval 1.5) [Weeks, Days, Hours, Minutes, Seconds]
1245 let intervals = periods ++ fractionals
1246 let histogram = mkDateHistogram (FieldName "postDate")
1247 let search interval = mkAggregateSearch Nothing $ mkAggregations "byDate" $ DateHistogramAgg (histogram interval)
1248 let expect interval = searchExpectAggs (search interval)
1249 let valid interval = searchValidBucketAgg (search interval) "byDate" toDateHistogram
1250 forM_ intervals expect
1251 forM_ intervals valid
1253 it "can execute missing aggregations" $ withTestEnv $ do
1254 _ <- insertData
1255 _ <- insertExtra
1256 let ags = mkAggregations "missing_agg" (MissingAgg (MissingAggregation "extra"))
1257 let search = mkAggregateSearch Nothing ags
1258 let docCountPair k n = (k, object ["doc_count" .= Number n])
1259 res <- searchTweets search
1260 liftIO $
1261 fmap aggregations res `shouldBe` Right (Just (M.fromList [docCountPair "missing_agg" 1]))
1263 describe "Highlights API" $ do
1265 it "returns highlight from query when there should be one" $ withTestEnv $ do
1266 _ <- insertData
1267 _ <- insertOther
1268 let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
1269 let testHighlight = Highlights Nothing [FieldHighlight (FieldName "message") Nothing]
1271 let search = mkHighlightSearch (Just query) testHighlight
1272 myHighlight <- searchTweetHighlight search
1273 liftIO $
1274 myHighlight `shouldBe` Right (Just (M.fromList [("message",["Use <em>haskell</em>!"])]))
1276 it "doesn't return highlight from a query when it shouldn't" $ withTestEnv $ do
1277 _ <- insertData
1278 _ <- insertOther
1279 let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
1280 let testHighlight = Highlights Nothing [FieldHighlight (FieldName "user") Nothing]
1282 let search = mkHighlightSearch (Just query) testHighlight
1283 myHighlight <- searchTweetHighlight search
1284 liftIO $
1285 myHighlight `shouldBe` Right Nothing
1287 describe "Source filtering" $ do
1289 it "doesn't include source when sources are disabled" $ withTestEnv $ do
1290 searchExpectSource
1291 NoSource
1292 (Left (EsError 500 "Source was missing"))
1294 it "includes a source" $ withTestEnv $ do
1295 searchExpectSource
1296 (SourcePatterns (PopPattern (Pattern "message")))
1297 (Right (Object (HM.fromList [("message", String "Use haskell!")])))
1299 it "includes sources" $ withTestEnv $ do
1300 searchExpectSource
1301 (SourcePatterns (PopPatterns [Pattern "user", Pattern "message"]))
1302 (Right (Object (HM.fromList [("user",String "bitemyapp"),("message", String "Use haskell!")])))
1304 it "includes source patterns" $ withTestEnv $ do
1305 searchExpectSource
1306 (SourcePatterns (PopPattern (Pattern "*ge")))
1307 (Right (Object (HM.fromList [("age", Number 10000),("message", String "Use haskell!")])))
1309 it "excludes source patterns" $ withTestEnv $ do
1310 searchExpectSource
1311 (SourceIncludeExclude (Include [])
1312 (Exclude [Pattern "l*", Pattern "*ge", Pattern "postDate", Pattern "extra"]))
1313 (Right (Object (HM.fromList [("user",String "bitemyapp")])))
1315 describe "ToJSON RegexpFlags" $ do
1316 it "generates the correct JSON for AllRegexpFlags" $
1317 toJSON AllRegexpFlags `shouldBe` String "ALL"
1319 it "generates the correct JSON for NoRegexpFlags" $
1320 toJSON NoRegexpFlags `shouldBe` String "NONE"
1322 it "generates the correct JSON for SomeRegexpFlags" $
1323 let flags = AnyString :| [ Automaton
1324 , Complement
1325 , Empty
1326 , Intersection
1327 , Interval ]
1328 in toJSON (SomeRegexpFlags flags) `shouldBe` String "ANYSTRING|AUTOMATON|COMPLEMENT|EMPTY|INTERSECTION|INTERVAL"
1330 prop "removes duplicates from flags" $ \(flags :: RegexpFlags) ->
1331 let String str = toJSON flags
1332 flagStrs = T.splitOn "|" str
1333 in noDuplicates flagStrs
1335 describe "omitNulls" $ do
1336 it "checks that omitNulls drops list elements when it should" $
1337 let dropped = omitNulls $ [ "test1" .= (toJSON ([] :: [Int]))
1338 , "test2" .= (toJSON ("some value" :: Text))]
1339 in dropped `shouldBe` Object (HM.fromList [("test2", String "some value")])
1341 it "checks that omitNulls doesn't drop list elements when it shouldn't" $
1342 let notDropped = omitNulls $ [ "test1" .= (toJSON ([1] :: [Int]))
1343 , "test2" .= (toJSON ("some value" :: Text))]
1344 in notDropped `shouldBe` Object (HM.fromList [ ("test1", Array (V.fromList [Number 1.0]))
1345 , ("test2", String "some value")])
1346 it "checks that omitNulls drops non list elements when it should" $
1347 let dropped = omitNulls $ [ "test1" .= (toJSON Null)
1348 , "test2" .= (toJSON ("some value" :: Text))]
1349 in dropped `shouldBe` Object (HM.fromList [("test2", String "some value")])
1350 it "checks that omitNulls doesn't drop non list elements when it shouldn't" $
1351 let notDropped = omitNulls $ [ "test1" .= (toJSON (1 :: Int))
1352 , "test2" .= (toJSON ("some value" :: Text))]
1353 in notDropped `shouldBe` Object (HM.fromList [ ("test1", Number 1.0)
1354 , ("test2", String "some value")])
1355 describe "Monoid (SearchHits a)" $ do
1356 prop "abides the monoid laws" $ eq $
1357 prop_Monoid (T :: T (SearchHits ()))
1359 describe "mkDocVersion" $ do
1360 prop "can never construct an out of range docVersion" $ \i ->
1361 let res = mkDocVersion i
1362 in case res of
1363 Nothing -> property True
1364 Just dv -> (dv >= minBound) .&&.
1365 (dv <= maxBound) .&&.
1366 docVersionNumber dv === i
1368 describe "Enum DocVersion" $ do
1369 it "follows the laws of Enum, Bounded" $ do
1370 evaluate (succ maxBound :: DocVersion) `shouldThrow` anyErrorCall
1371 evaluate (pred minBound :: DocVersion) `shouldThrow` anyErrorCall
1372 evaluate (toEnum 0 :: DocVersion) `shouldThrow` anyErrorCall
1373 evaluate (toEnum 9200000000000000001 :: DocVersion) `shouldThrow` anyErrorCall
1374 enumFrom (pred maxBound :: DocVersion) `shouldBe` [pred maxBound, maxBound]
1375 enumFrom (pred maxBound :: DocVersion) `shouldBe` [pred maxBound, maxBound]
1376 enumFromThen minBound (pred maxBound :: DocVersion) `shouldBe` [minBound, pred maxBound]
1378 describe "scan&scroll API" $ do
1379 it "returns documents using the scan&scroll API" $ withTestEnv $ do
1380 _ <- insertData
1381 _ <- insertOther
1382 let search = (mkSearch (Just $ MatchAllQuery Nothing) Nothing) { size = (Size 1) }
1383 regular_search <- searchTweet search
1384 scan_search' <- scanSearch testIndex testMapping search :: BH IO [Hit Tweet]
1385 let scan_search = map hitSource scan_search'
1386 liftIO $
1387 regular_search `shouldBe` Right exampleTweet -- Check that the size restrtiction is being honored
1388 liftIO $
1389 scan_search `shouldMatchList` [Just exampleTweet, Just otherTweet]
1391 describe "index aliases" $ do
1392 it "handles the simple case of aliasing an existing index" $ do
1393 let alias = IndexAlias (testIndex) (IndexAliasName (IndexName "bloodhound-tests-twitter-1-alias"))
1394 let create = IndexAliasCreate Nothing Nothing
1395 let action = AddAlias alias create
1397 withTestEnv $ do
1398 resetIndex
1399 resp <- updateIndexAliases (action :| [])
1400 liftIO $ validateStatus resp 200
1401 let cleanup = withTestEnv (updateIndexAliases (RemoveAlias alias :| []))
1402 (do aliases <- withTestEnv getIndexAliases
1403 let expected = IndexAliasSummary alias create
1404 case aliases of
1405 Right (IndexAliasesSummary summs) ->
1406 L.find ((== alias) . indexAliasSummaryAlias) summs `shouldBe` Just expected
1407 Left e -> expectationFailure ("Expected an IndexAliasesSummary but got " <> show e)) `finally` cleanup
1409 it "handles an alias with routing and a filter" $ do
1410 let alias = IndexAlias (testIndex) (IndexAliasName (IndexName "bloodhound-tests-twitter-1-alias"))
1411 let sar = SearchAliasRouting (RoutingValue "search val" :| [])
1412 let iar = IndexAliasRouting (RoutingValue "index val")
1413 let routing = GranularAliasRouting (Just sar) (Just iar)
1414 let filter = LimitFilter 42
1415 let create = IndexAliasCreate (Just routing) (Just filter)
1416 let action = AddAlias alias create
1418 withTestEnv $ do
1419 resetIndex
1420 resp <- updateIndexAliases (action :| [])
1421 liftIO $ validateStatus resp 200
1422 let cleanup = withTestEnv (updateIndexAliases (RemoveAlias alias :| []))
1423 (do aliases <- withTestEnv getIndexAliases
1424 let expected = IndexAliasSummary alias create
1425 case aliases of
1426 Right (IndexAliasesSummary summs) ->
1427 L.find ((== alias) . indexAliasSummaryAlias) summs `shouldBe` Just expected
1428 Left e -> expectationFailure ("Expected an IndexAliasesSummary but got " <> show e)) `finally` cleanup
1430 describe "Index Listing" $ do
1431 it "returns a list of index names" $ withTestEnv $ do
1432 _ <- createExampleIndex
1433 ixns <- listIndices
1434 liftIO (ixns `shouldContain` [testIndex])
1436 describe "Index Settings" $ do
1437 it "persists settings" $ withTestEnv $ do
1438 _ <- deleteExampleIndex
1439 _ <- createExampleIndex
1440 let updates = BlocksWrite False :| []
1441 updateResp <- updateIndexSettings updates testIndex
1442 liftIO $ validateStatus updateResp 200
1443 getResp <- getIndexSettings testIndex
1444 liftIO $
1445 getResp `shouldBe` Right (IndexSettingsSummary
1446 testIndex
1447 (IndexSettings (ShardCount 1) (ReplicaCount 0))
1448 (NE.toList updates))
1450 describe "Index Optimization" $ do
1451 it "returns a successful response upon completion" $ withTestEnv $ do
1452 _ <- createExampleIndex
1453 resp <- optimizeIndex (IndexList (testIndex :| [])) defaultIndexOptimizationSettings
1454 liftIO $ validateStatus resp 200
1456 describe "JSON instances" $ do
1457 propJSON (Proxy :: Proxy Version)
1458 propJSON (Proxy :: Proxy IndexName)
1459 propJSON (Proxy :: Proxy MappingName)
1460 propJSON (Proxy :: Proxy DocId)
1461 propJSON (Proxy :: Proxy IndexAliasRouting)
1462 propJSON (Proxy :: Proxy RoutingValue)
1463 propJSON (Proxy :: Proxy ShardCount)
1464 propJSON (Proxy :: Proxy ReplicaCount)
1465 propJSON (Proxy :: Proxy TemplateName)
1466 propJSON (Proxy :: Proxy TemplatePattern)
1467 propJSON (Proxy :: Proxy QueryString)
1468 propJSON (Proxy :: Proxy FieldName)
1469 propJSON (Proxy :: Proxy CacheName)
1470 propJSON (Proxy :: Proxy CacheKey)
1471 propJSON (Proxy :: Proxy Existence)
1472 propJSON (Proxy :: Proxy CutoffFrequency)
1473 propJSON (Proxy :: Proxy Analyzer)
1474 propJSON (Proxy :: Proxy MaxExpansions)
1475 propJSON (Proxy :: Proxy Lenient)
1476 propJSON (Proxy :: Proxy Tiebreaker)
1477 propJSON (Proxy :: Proxy Boost)
1478 propJSON (Proxy :: Proxy BoostTerms)
1479 propJSON (Proxy :: Proxy MinimumMatch)
1480 propJSON (Proxy :: Proxy DisableCoord)
1481 propJSON (Proxy :: Proxy IgnoreTermFrequency)
1482 propJSON (Proxy :: Proxy MinimumTermFrequency)
1483 propJSON (Proxy :: Proxy MaxQueryTerms)
1484 propJSON (Proxy :: Proxy Fuzziness)
1485 propJSON (Proxy :: Proxy PrefixLength)
1486 propJSON (Proxy :: Proxy TypeName)
1487 propJSON (Proxy :: Proxy PercentMatch)
1488 propJSON (Proxy :: Proxy StopWord)
1489 propJSON (Proxy :: Proxy QueryPath)
1490 propJSON (Proxy :: Proxy AllowLeadingWildcard)
1491 propJSON (Proxy :: Proxy LowercaseExpanded)
1492 propJSON (Proxy :: Proxy EnablePositionIncrements)
1493 propJSON (Proxy :: Proxy AnalyzeWildcard)
1494 propJSON (Proxy :: Proxy GeneratePhraseQueries)
1495 propJSON (Proxy :: Proxy Locale)
1496 propJSON (Proxy :: Proxy MaxWordLength)
1497 propJSON (Proxy :: Proxy MinWordLength)
1498 propJSON (Proxy :: Proxy PhraseSlop)
1499 propJSON (Proxy :: Proxy MinDocFrequency)
1500 propJSON (Proxy :: Proxy MaxDocFrequency)
1501 propJSON (Proxy :: Proxy Filter)
1502 propJSON (Proxy :: Proxy Query)
1503 propJSON (Proxy :: Proxy SimpleQueryStringQuery)
1504 propJSON (Proxy :: Proxy FieldOrFields)
1505 propJSON (Proxy :: Proxy SimpleQueryFlag)
1506 propJSON (Proxy :: Proxy RegexpQuery)
1507 propJSON (Proxy :: Proxy QueryStringQuery)
1508 propJSON (Proxy :: Proxy RangeQuery)
1509 propJSON (Proxy :: Proxy PrefixQuery)
1510 propJSON (Proxy :: Proxy NestedQuery)
1511 propJSON (Proxy :: Proxy MoreLikeThisFieldQuery)
1512 propJSON (Proxy :: Proxy MoreLikeThisQuery)
1513 propJSON (Proxy :: Proxy IndicesQuery)
1514 propJSON (Proxy :: Proxy HasParentQuery)
1515 propJSON (Proxy :: Proxy HasChildQuery)
1516 propJSON (Proxy :: Proxy FuzzyQuery)
1517 propJSON (Proxy :: Proxy FuzzyLikeFieldQuery)
1518 propJSON (Proxy :: Proxy FuzzyLikeThisQuery)
1519 propJSON (Proxy :: Proxy FilteredQuery)
1520 propJSON (Proxy :: Proxy DisMaxQuery)
1521 propJSON (Proxy :: Proxy CommonTermsQuery)
1522 propJSON (Proxy :: Proxy CommonMinimumMatch)
1523 propJSON (Proxy :: Proxy BoostingQuery)
1524 propJSON (Proxy :: Proxy BoolQuery)
1525 propJSON (Proxy :: Proxy MatchQuery)
1526 propJSON (Proxy :: Proxy MultiMatchQueryType)
1527 propJSON (Proxy :: Proxy BooleanOperator)
1528 propJSON (Proxy :: Proxy ZeroTermsQuery)
1529 propJSON (Proxy :: Proxy MatchQueryType)
1530 propJSON (Proxy :: Proxy AliasRouting)
1531 propJSON (Proxy :: Proxy IndexAliasCreate)
1532 propJSON (Proxy :: Proxy SearchAliasRouting)
1533 propJSON (Proxy :: Proxy ScoreType)
1534 propJSON (Proxy :: Proxy Distance)
1535 propJSON (Proxy :: Proxy DistanceUnit)
1536 propJSON (Proxy :: Proxy DistanceType)
1537 propJSON (Proxy :: Proxy OptimizeBbox)
1538 propJSON (Proxy :: Proxy GeoBoundingBoxConstraint)
1539 propJSON (Proxy :: Proxy GeoFilterType)
1540 propJSON (Proxy :: Proxy GeoBoundingBox)
1541 propJSON (Proxy :: Proxy LatLon)
1542 propJSON (Proxy :: Proxy RangeExecution)
1543 prop "RegexpFlags FromJSON/ToJSON roundtrips, removing dups " $ \rfs ->
1544 let expected = case rfs of
1545 SomeRegexpFlags fs -> SomeRegexpFlags (NE.fromList (nub (NE.toList fs)))
1546 x -> x
1547 in parseEither parseJSON (toJSON rfs) === Right expected
1548 propJSON (Proxy :: Proxy BoolMatch)
1549 propJSON (Proxy :: Proxy Term)
1550 propJSON (Proxy :: Proxy MultiMatchQuery)
1551 propJSON (Proxy :: Proxy IndexSettings)
1552 propJSON (Proxy :: Proxy UpdatableIndexSetting)
1553 propJSON (Proxy :: Proxy ReplicaBounds)
1554 propJSON (Proxy :: Proxy Bytes)
1555 propJSON (Proxy :: Proxy AllocationPolicy)
1556 propJSON (Proxy :: Proxy InitialShardCount)
1557 propJSON (Proxy :: Proxy FSType)
1558 propJSON (Proxy :: Proxy CompoundFormat)