git.iko.soy/typeable/bloodhound
on master help sign in
0.5.0.0 yissss
commit8a5c4b46f8671b2bd14b7537b7e30624c8de4b2a authorChris Allen <cma@bitemyapp.com> dateFri 12 Dec 2014, 00:28 (12 years ago) parentd3fa352
5 files changed+114−17
bloodhound.cabal+1−1 changelog.md+12 src/Database/Bloodhound/Client.hs+80−4 src/Database/Bloodhound/Types.hs+7−7 tests/tests.hs+14−5
bloodhound.cabal+1−1
@@ -1,5 +1,5 @@
11name: bloodhound
2−version: 0.4.0.2
2+version: 0.5.0.0
33synopsis: ElasticSearch client library for Haskell
44description: ElasticSearch made awesome for Haskell hackers
55homepage: https://github.com/bitemyapp/bloodhound
changelog.md+12
@@ -1,9 +1,21 @@
1+0.5.0.0
2+===================
3+
4+* Fixed and changed TermsQuery (This caused the major bump)
5+
6+* Removed benchmarks from travis.yml
7+
8+* Added doctests, examples for Database.Bloodhound.Client. Haddocks should be much nicer.
9+
10+* Various fixes, reformatting
11+
1120.4.0.0
213===================
314
415* Term and date aggregation - thanks to Christopher Guiney! (@chrisguiney)
516
617Following three thanks to Liam Atkins (@latkins)
18+
719* omitNulls changed to exclude empty lists and null values
820
921* BoolQuery must/mustNot/Should changed from Maybe (Query|[Query]) to [Query] thanks to @latkins
src/Database/Bloodhound/Client.hs+80−4
@@ -108,12 +108,22 @@ import Database.Bloodhound.Types
108108--instance ToJSON BulkTest
109109-- :}
110110
111+-- | 'mkShardCount' is a straight-forward smart constructor for 'ShardCount'
112+-- which rejects 'Int' values below 1 and above 1000.
113+--
114+-- >>> mkShardCount 10
115+-- Just (ShardCount 10)
111116mkShardCount :: Int -> Maybe ShardCount
112117mkShardCount n
113118 | n < 1 = Nothing
114119 | n > 1000 = Nothing
115120 | otherwise = Just (ShardCount n)
116121
122+-- | 'mkReplicaCount' is a straight-forward smart constructor for 'ReplicaCount'
123+-- which rejects 'Int' values below 1 and above 1000.
124+--
125+-- >>> mkReplicaCount 10
126+-- Just (ReplicaCount 10)
117127mkReplicaCount :: Int -> Maybe ReplicaCount
118128mkReplicaCount n
119129 | n < 1 = Nothing
@@ -152,14 +162,17 @@ post = dispatch NHTM.methodPost
152162-- http://hackage.haskell.org/package/http-client-lens-0.1.0/docs/Network-HTTP-Client-Lens.html
153163-- https://github.com/supki/libjenkins/blob/master/src/Jenkins/Rest/Internal.hs
154164
165+-- | 'getStatus' fetches the 'Status' of a 'Server'
166+--
167+-- >>> getStatus testServer
168+-- Just (Status {ok = Nothing, status = 200, name = "Arena", version = Version {number = "1.4.1", build_hash = "89d3241d670db65f994242c8e8383b169779e2d4", build_timestamp = 2014-11-26 15:49:29 UTC, build_snapshot = False, lucene_version = "4.10.2"}, tagline = "You Know, for Search"})
155169getStatus :: Server -> IO (Maybe Status)
156170getStatus (Server server) = do
157171 request <- parseUrl $ joinPath [server]
158172 response <- withManager defaultManagerSettings $ httpLbs request
159173 return $ decode (responseBody response)
160174
161−-- | createIndex will create an index given a 'Server',
162−-- 'IndexSettings', and an 'IndexName'
175+-- | 'createIndex' will create an index given a 'Server', 'IndexSettings', and an 'IndexName'.
163176--
164177-- >>> response <- createIndex testServer defaultIndexSettings (IndexName "didimakeanindex")
165178-- >>> respIsTwoHunna response
@@ -172,8 +185,7 @@ createIndex (Server server) indexSettings (IndexName indexName) =
172185 where url = joinPath [server, indexName]
173186 body = Just $ encode indexSettings
174187
175−-- | deleteIndex will delete an index given a 'Server',
176−-- and an 'IndexName'
188+-- | 'deleteIndex' will delete an index given a 'Server', and an 'IndexName'.
177189--
178190-- >>> response <- createIndex testServer defaultIndexSettings (IndexName "didimakeanindex")
179191-- >>> response <- deleteIndex testServer (IndexName "didimakeanindex")
@@ -196,6 +208,10 @@ existentialQuery url = do
196208 reply <- head url
197209 return (reply, respIsTwoHunna reply)
198210
211+-- | 'indexExists' enables you to check if an index exists. Returns 'Bool'
212+-- in IO
213+--
214+-- >>> exists <- indexExists testServer testIndex
199215indexExists :: Server -> IndexName -> IO Bool
200216indexExists (Server server) (IndexName indexName) = do
201217 (_, exists) <- existentialQuery url
@@ -223,9 +239,17 @@ openOrCloseIndexes oci (Server server) (IndexName indexName) =
223239 where ociString = stringifyOCIndex oci
224240 url = joinPath [server, indexName, ociString]
225241
242+-- | 'openIndex' opens an index given a 'Server' and an 'IndexName'. Explained in further detail at
243+-- http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-open-close.html
244+--
245+-- >>> reply <- openIndex testServer testIndex
226246openIndex :: Server -> IndexName -> IO Reply
227247openIndex = openOrCloseIndexes OpenIndex
228248
249+-- | 'closeIndex' closes an index given a 'Server' and an 'IndexName'. Explained in further detail at
250+-- http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-open-close.html
251+--
252+-- >>> reply <- closeIndex testServer testIndex
229253closeIndex :: Server -> IndexName -> IO Reply
230254closeIndex = openOrCloseIndexes CloseIndex
231255
@@ -361,6 +385,10 @@ getDocument (Server server) (IndexName indexName)
361385 (MappingName mappingName) (DocId docId) =
362386 get $ joinPath [server, indexName, mappingName, docId]
363387
388+-- | 'documentExists' enables you to check if a document exists. Returns 'Bool'
389+-- in IO
390+--
391+-- >>> exists <- documentExists testServer testIndex testMapping (DocId "1")
364392documentExists :: Server -> IndexName -> MappingName
365393 -> DocId -> IO Bool
366394documentExists (Server server) (IndexName indexName)
@@ -372,27 +400,75 @@ documentExists (Server server) (IndexName indexName)
372400dispatchSearch :: String -> Search -> IO Reply
373401dispatchSearch url search = post url (Just (encode search))
374402
403+-- | 'searchAll', given a 'Search', will perform that search against all indexes
404+-- on an Elasticsearch server. Try to avoid doing this if it can be helped.
405+--
406+-- >>> let query = TermQuery (Term "user" "bitemyapp") Nothing
407+-- >>> let search = mkSearch (Just query) Nothing
408+-- >>> reply <- searchAll testServer search
375409searchAll :: Server -> Search -> IO Reply
376410searchAll (Server server) = dispatchSearch url where
377411 url = joinPath [server, "_search"]
378412
413+-- | 'searchByIndex', given a 'Search' and an 'IndexName', will perform that search
414+-- against all mappings within an index on an Elasticsearch server.
415+--
416+-- >>> let query = TermQuery (Term "user" "bitemyapp") Nothing
417+-- >>> let search = mkSearch (Just query) Nothing
418+-- >>> reply <- searchByIndex testServer testIndex search
379419searchByIndex :: Server -> IndexName -> Search -> IO Reply
380420searchByIndex (Server server) (IndexName indexName) = dispatchSearch url where
381421 url = joinPath [server, indexName, "_search"]
382422
423+-- | 'searchByType', given a 'Search', 'IndexName', and 'MappingName', will perform that
424+-- search against a specific mapping within an index on an Elasticsearch server.
425+--
426+-- >>> let query = TermQuery (Term "user" "bitemyapp") Nothing
427+-- >>> let search = mkSearch (Just query) Nothing
428+-- >>> reply <- searchByType testServer testIndex testMapping search
383429searchByType :: Server -> IndexName -> MappingName -> Search -> IO Reply
384430searchByType (Server server) (IndexName indexName)
385431 (MappingName mappingName) = dispatchSearch url where
386432 url = joinPath [server, indexName, mappingName, "_search"]
387433
434+-- | 'mkSearch' is a helper function for defaulting additional fields of a 'Search'
435+-- to Nothing in case you only care about your 'Query' and 'Filter'. Use record update
436+-- syntax if you want to add things like aggregations or highlights while still using
437+-- this helper function.
438+--
439+-- >>> let query = TermQuery (Term "user" "bitemyapp") Nothing
440+-- >>> mkSearch (Just query) Nothing
441+-- Search {queryBody = Just (TermQuery (Term {termField = "user", termValue = "bitemyapp"}) Nothing), filterBody = Nothing, sortBody = Nothing, aggBody = Nothing, highlight = Nothing, trackSortScores = False, from = 0, size = 10}
388442mkSearch :: Maybe Query -> Maybe Filter -> Search
389443mkSearch query filter = Search query filter Nothing Nothing Nothing False 0 10
390444
445+-- | 'mkAggregateSearch' is a helper function that defaults everything in a 'Search' except for
446+-- the 'Query' and the 'Aggregation'.
447+--
448+-- >>> let terms = TermsAgg $ (mkTermsAggregation "user") { termCollectMode = Just BreadthFirst }
449+-- >>> terms
450+-- TermsAgg (TermsAggregation {term = Left "user", termInclude = Nothing, termExclude = Nothing, termOrder = Nothing, termMinDocCount = Nothing, termSize = Nothing, termShardSize = Nothing, termCollectMode = Just BreadthFirst, termExecutionHint = Nothing, termAggs = Nothing})
451+-- >>> let myAggregation = mkAggregateSearch Nothing $ mkAggregations "users" terms
391452mkAggregateSearch :: Maybe Query -> Aggregations -> Search
392453mkAggregateSearch query mkSearchAggs = Search query Nothing Nothing (Just mkSearchAggs) Nothing False 0 0
393454
455+-- | 'mkHighlightSearch' is a helper function that defaults everything in a 'Search' except for
456+-- the 'Query' and the 'Aggregation'.
457+--
458+-- >>> let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
459+-- >>> let testHighlight = Highlights Nothing [FieldHighlight (FieldName "message") Nothing]
460+-- >>> let search = mkHighlightSearch (Just query) testHighlight
394461mkHighlightSearch :: Maybe Query -> Highlights -> Search
395462mkHighlightSearch query searchHighlights = Search query Nothing Nothing Nothing (Just searchHighlights) False 0 10
396463
464+-- | 'pageSearch' is a helper function that takes a search and assigns the page from and to
465+-- fields for the search.
466+--
467+-- >>> let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
468+-- >>> let search = mkSearch (Just query) Nothing
469+-- >>> search
470+-- Search {queryBody = Just (QueryMatchQuery (MatchQuery {matchQueryField = FieldName "_all", matchQueryQueryString = QueryString "haskell", matchQueryOperator = Or, matchQueryZeroTerms = ZeroTermsNone, matchQueryCutoffFrequency = Nothing, matchQueryMatchType = Nothing, matchQueryAnalyzer = Nothing, matchQueryMaxExpansions = Nothing, matchQueryLenient = Nothing})), filterBody = Nothing, sortBody = Nothing, aggBody = Nothing, highlight = Nothing, trackSortScores = False, from = 0, size = 10}
471+-- >>> pageSearch 10 100 search
472+-- Search {queryBody = Just (QueryMatchQuery (MatchQuery {matchQueryField = FieldName "_all", matchQueryQueryString = QueryString "haskell", matchQueryOperator = Or, matchQueryZeroTerms = ZeroTermsNone, matchQueryCutoffFrequency = Nothing, matchQueryMatchType = Nothing, matchQueryAnalyzer = Nothing, matchQueryMaxExpansions = Nothing, matchQueryLenient = Nothing})), filterBody = Nothing, sortBody = Nothing, aggBody = Nothing, highlight = Nothing, trackSortScores = False, from = 10, size = 100}
397473pageSearch :: Int -> Int -> Search -> Search
398474pageSearch pageFrom pageSize search = search { from = pageFrom, size = pageSize }
src/Database/Bloodhound/Types.hs+7−7
@@ -196,7 +196,7 @@ import Data.Aeson
196196import Data.Aeson.Types (Pair, emptyObject, parseMaybe)
197197import qualified Data.ByteString.Lazy.Char8 as L
198198import Data.List (nub)
199−import Data.List.NonEmpty (NonEmpty (..))
199+import Data.List.NonEmpty (NonEmpty(..), toList)
200200import qualified Data.Map.Strict as M
201201import Data.Monoid
202202import Data.Text (Text)
@@ -598,7 +598,7 @@ data HighlightTag = TagSchema Text
598598
599599data Query =
600600 TermQuery Term (Maybe Boost)
601− | TermsQuery [Term] MinimumMatch
601+ | TermsQuery (NonEmpty Term)
602602 | QueryMatchQuery MatchQuery
603603 | QueryMultiMatchQuery MultiMatchQuery
604604 | QueryBoolQuery BoolQuery
@@ -1380,12 +1380,12 @@ instance ToJSON Query where
13801380 boosted = maybe [] (return . ("boost" .=)) boost
13811381 merged = mappend base boosted
13821382
1383− toJSON (TermsQuery terms termsQueryMinimumMatch) =
1383+ toJSON (TermsQuery terms) =
13841384 object [ "terms" .= object conjoined ]
1385− where conjoined =
1386− [ "tags" .= fmap toJSON terms
1387− , "minimum_should_match" .= toJSON termsQueryMinimumMatch ]
1388−
1385+ where conjoined = [ getTermsField terms .=
1386+ fmap (toJSON . getTermValue) (toList terms)]
1387+ getTermsField ((Term f _ ) :| _) = f
1388+ getTermValue (Term _ v) = v
13891389 toJSON (IdsQuery idsQueryMappingName docIds) =
13901390 object [ "ids" .= object conjoined ]
13911391 where conjoined = [ "type" .= toJSON idsQueryMappingName
tests/tests.hs+14−5
@@ -7,9 +7,10 @@ module Main where
77import Control.Applicative
88import Control.Monad
99import Data.Aeson
10−import Data.HashMap.Strict (fromList)
10+import qualified Data.HashMap.Strict as HM
1111import Data.List (nub)
1212import Data.List.NonEmpty (NonEmpty (..))
13+import qualified Data.List.NonEmpty as NE
1314import qualified Data.Map.Strict as M
1415import Data.Text (Text)
1516import qualified Data.Text as T
@@ -269,6 +270,14 @@ main = hspec $ do
269270 myTweet <- searchTweet search
270271 myTweet `shouldBe` Right exampleTweet
271272
273+ it "returns document for terms query and identity filter" $ do
274+ _ <- insertData
275+ let query = TermsQuery (NE.fromList [(Term "user" "bitemyapp")])
276+ let filter = IdentityFilter <&&> IdentityFilter
277+ let search = mkSearch (Just query) (Just filter)
278+ myTweet <- searchTweet search
279+ myTweet `shouldBe` Right exampleTweet
280+
272281 it "returns document for match query" $ do
273282 _ <- insertData
274283 let query = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
@@ -542,19 +551,19 @@ main = hspec $ do
542551 it "checks that omitNulls drops list elements when it should" $
543552 let dropped = omitNulls $ [ "test1" .= (toJSON ([] :: [Int]))
544553 , "test2" .= (toJSON ("some value" :: Text))]
545− in dropped `shouldBe` Object (fromList [("test2", String "some value")])
554+ in dropped `shouldBe` Object (HM.fromList [("test2", String "some value")])
546555
547556 it "checks that omitNulls doesn't drop list elements when it shouldn't" $
548557 let notDropped = omitNulls $ [ "test1" .= (toJSON ([1] :: [Int]))
549558 , "test2" .= (toJSON ("some value" :: Text))]
550− in notDropped `shouldBe` Object (fromList [ ("test1", Array (V.fromList [Number 1.0]))
559+ in notDropped `shouldBe` Object (HM.fromList [ ("test1", Array (V.fromList [Number 1.0]))
551560 , ("test2", String "some value")])
552561 it "checks that omitNulls drops non list elements when it should" $
553562 let dropped = omitNulls $ [ "test1" .= (toJSON Null)
554563 , "test2" .= (toJSON ("some value" :: Text))]
555− in dropped `shouldBe` Object (fromList [("test2", String "some value")])
564+ in dropped `shouldBe` Object (HM.fromList [("test2", String "some value")])
556565 it "checks that omitNulls doesn't drop non list elements when it shouldn't" $
557566 let notDropped = omitNulls $ [ "test1" .= (toJSON (1 :: Int))
558567 , "test2" .= (toJSON ("some value" :: Text))]
559− in notDropped `shouldBe` Object (fromList [ ("test1", Number 1.0)
568+ in notDropped `shouldBe` Object (HM.fromList [ ("test1", Number 1.0)
560569 , ("test2", String "some value")])