git.iko.soy/typeable/bloodhound
on master help sign in
Merge pull request #174 from bitemyapp/drop-doctests
17c8266Chris Allen
.gitignore+1
@@ -16,3 +16,4 @@ bloodhound.iml
16.hg/00changelog.i
16.hg/00changelog.i
17.hg
17.hg
18.hgignore
18.hgignore
19+examples/bloodhound-examples.cabal
.travis.yml−1
@@ -36,7 +36,6 @@ script:
36 - stack setup
36 - stack setup
37 - stack update
37 - stack update
38 - stack build -j1
38 - stack build -j1
39− - stack test bloodhound:doctests --flag bloodhound:$ESFLAG
40 - stack test bloodhound:tests --test-arguments="--qc-max-success 500" --flag bloodhound:$ESFLAG
39 - stack test bloodhound:tests --test-arguments="--qc-max-success 500" --flag bloodhound:$ESFLAG
41
40
42cache:
41cache:
README.md+14−834
@@ -21,9 +21,19 @@ Endorsements
21Version compatibility
21Version compatibility
22---------------------
22---------------------
23
23
24−Elasticsearch \>=1.0 && \<2.0 is recommended. Bloodhound mostly works with 0.9.x, but I don't recommend it if you expect everything to work. As of Bloodhound 0.3 all \>=1.0 && \<2.0 versions of Elasticsearch work. Some (or even most?) features will work with versions \>=2.0, but it is not officially supported yet.
24+As of version 0.13.0.0, Bloodhound has 2 separate module trees for
25+ElasticSearch versions 1 and 5. Import the module that is appropriate
26+for your use case. If you would like to add support for another major
27+version, open a ticket expressing your intend and follow the pattern
28+used for other versions. We weighed the idea of sharing code between
29+versions but it just got too messy, especially considering the
30+instability of the ElasticSearch API. We switched to a model which
31+would allow the persons responsible for a particular protocol version
32+to maintain that version while avoiding conflict with other versions.
33+
34+See our [TravisCI](https://travis-ci.org/bitemyapp/bloodhound) for a
35+listing of ElasticSearch version we test against.
25
36
26−Current versions we test against are 1.2.4, 1.3.6, 1.4.1, 1.5.2, 1.6.0, and 1.7.2. We also check that GHC 7.6 and 7.8 both build and pass tests. See our [TravisCI](https://travis-ci.org/bitemyapp/bloodhound) to learn more.
27
37
28Stability
38Stability
29---------
39---------
@@ -41,7 +51,7 @@ Steps to run the tests locally:
41 3. In your local Bloodhound directory, run `stack setup && stack build`
51 3. In your local Bloodhound directory, run `stack setup && stack build`
42 4. Start the desired version of ElasticSearch at `localhost:9200`, which should be the default.
52 4. Start the desired version of ElasticSearch at `localhost:9200`, which should be the default.
43 5. Run `stack test` in your local Bloodhound directory.
53 5. Run `stack test` in your local Bloodhound directory.
44− 6. The unit tests will pass if you re-execute `stack test`, but some of the doctests might fail due to existing data in ElasticSearch. If you want to start with a clean slate, stop your ElasticSearch instance, delete the `data/` folder in the ElasticSearch installation, restart ElasticSearch, and re-run `stack test`.
54+ 6. The unit tests will pass if you re-execute `stack test`. If you want to start with a clean slate, stop your ElasticSearch instance, delete the `data/` folder in the ElasticSearch installation, restart ElasticSearch, and re-run `stack test`.
45
55
46
56
47Hackage page and Haddock documentation
57Hackage page and Haddock documentation
@@ -57,837 +67,7 @@ It's not using Bloodhound, but if you need an introduction to or overview of Ela
57Examples
67Examples
58========
68========
59
69
60−Index Operations
70+See the [examples](htts://github.com/bitemyapp/bloodhound/tree/master/examples) directory for example code.
61−----------------
62−
63−### Create Index
64−
65−``` {.haskell}
66−
67−-- Formatted for use in ghci, so there are "let"s in front of the decls.
68−
69−-- if you see :{ and :}, they're so you can copy-paste
70−-- the multi-line examples into your ghci REPL.
71−
72−:set -XDeriveGeneric
73−:set -XOverloadedStrings
74−:{
75−import Control.Applicative
76−import Database.Bloodhound
77−import Data.Aeson
78−import Data.Either (Either(..))
79−import Data.Maybe (fromJust)
80−import Data.Time.Calendar (Day(..))
81−import Data.Time.Clock (secondsToDiffTime, UTCTime(..))
82−import Data.Text (Text)
83−import GHC.Generics (Generic)
84−import Network.HTTP.Client
85−import qualified Network.HTTP.Types.Status as NHTS
86−
87−-- no trailing slashes in servers, library handles building the path.
88−let testServer = (Server "http://localhost:9200")
89−let testIndex = IndexName "twitter"
90−let testMapping = MappingName "tweet"
91−let withBH' = withBH defaultManagerSettings testServer
92−
93−-- defaultIndexSettings is exported by Database.Bloodhound as well
94−let defaultIndexSettings = IndexSettings (ShardCount 3) (ReplicaCount 2)
95−
96−-- createIndex returns MonadBH m => m Reply. You can use withBH for
97−-- one-off commands or you can use runBH to group together commands
98−-- and to pass in your own HTTP manager for pipelining.
99−
100−-- response :: Reply, Reply is a synonym for Network.HTTP.Conduit.Response
101−response <- withBH' $ createIndex defaultIndexSettings testIndex
102−:}
103−
104−```
105−
106−### Delete Index
107−
108−#### Code
109−
110−``` {.haskell}
111−
112−-- response :: Reply
113−response <- withBH' $ deleteIndex testIndex
114−
115−```
116−
117−#### Example Response
118−
119−``` {.haskell}
120−
121−-- print response if it was a success
122−Response {responseStatus = Status {statusCode = 200, statusMessage = "OK"}
123− , responseVersion = HTTP/1.1
124− , responseHeaders = [("Content-Type", "application/json; charset=UTF-8")
125− , ("Content-Length", "21")]
126− , responseBody = "{\"acknowledged\":true}"
127− , responseCookieJar = CJ {expose = []}
128− , responseClose' = ResponseClose}
129−
130−-- if the index to be deleted didn't exist anyway
131−Response {responseStatus = Status {statusCode = 404, statusMessage = "Not Found"}
132− , responseVersion = HTTP/1.1
133− , responseHeaders = [("Content-Type", "application/json; charset=UTF-8")
134− , ("Content-Length","65")]
135− , responseBody = "{\"error\":\"IndexMissingException[[twitter] missing]\",\"status\":404}"
136− , responseCookieJar = CJ {expose = []}
137− , responseClose' = ResponseClose}
138−
139−```
140−
141−### Refresh Index
142−
143−#### Note, you **have** to do this if you expect to read what you just wrote
144−
145−``` {.haskell}
146−
147−resp <- withBH' $ refreshIndex testIndex
148−
149−```
150−
151−#### Example Response
152−
153−``` {.haskell}
154−
155−-- print resp on success
156−Response {responseStatus = Status {statusCode = 200, statusMessage = "OK"}
157− , responseVersion = HTTP/1.1
158− , responseHeaders = [("Content-Type", "application/json; charset=UTF-8")
159− , ("Content-Length","50")]
160− , responseBody = "{\"_shards\":{\"total\":10,\"successful\":5,\"failed\":0}}"
161− , responseCookieJar = CJ {expose = []}
162− , responseClose' = ResponseClose}
163−
164−```
165−
166−Mapping Operations
167−------------------
168−
169−### Create Mapping
170−
171−``` {.haskell}
172−
173−-- don't forget imports and the like at the top.
174−
175−data TweetMapping = TweetMapping deriving (Eq, Show)
176−
177−-- I know writing the JSON manually sucks.
178−-- I don't have a proper data type for Mappings yet.
179−-- Let me know if this is something you need.
180−
181−:{
182−instance ToJSON TweetMapping where
183− toJSON TweetMapping =
184− object ["properties" .=
185− object ["location" .=
186− object ["type" .= ("geo_point" :: Text)]]]
187−:}
188−
189−resp <- withBH' $ putMapping testIndex testMapping TweetMapping
190−
191−```
192−
193−### Delete Mapping
194−
195−``` {.haskell}
196−
197−resp <- withBH' $ deleteMapping testIndex testMapping
198−
199−```
200−
201−Document Operations
202−-------------------
203−
204−### Indexing Documents
205−
206−``` {.haskell}
207−
208−-- don't forget the imports and derive generic setting for ghci
209−-- at the beginning of the examples.
210−
211−:{
212−data Location = Location { lat :: Double
213− , lon :: Double } deriving (Eq, Generic, Show)
214−
215−data Tweet = Tweet { user :: Text
216− , postDate :: UTCTime
217− , message :: Text
218− , age :: Int
219− , location :: Location } deriving (Eq, Generic, Show)
220−
221−exampleTweet = Tweet { user = "bitemyapp"
222− , postDate = UTCTime
223− (ModifiedJulianDay 55000)
224− (secondsToDiffTime 10)
225− , message = "Use haskell!"
226− , age = 10000
227− , location = Location 40.12 (-71.34) }
228−
229−-- automagic (generic) derivation of instances because we're lazy.
230−instance ToJSON Tweet
231−instance FromJSON Tweet
232−instance ToJSON Location
233−instance FromJSON Location
234−:}
235−
236−-- Should be able to toJSON and encode the data structures like this:
237−-- λ> toJSON $ Location 10.0 10.0
238−-- Object fromList [("lat",Number 10.0),("lon",Number 10.0)]
239−-- λ> encode $ Location 10.0 10.0
240−-- "{\"lat\":10,\"lon\":10}"
241−
242−resp <- withBH' $ indexDocument testIndex testMapping defaultIndexDocumentSettings exampleTweet (DocId "1")
243−
244−```
245−
246−#### Example Response
247−
248−``` {.haskell}
249−
250−Response {responseStatus =
251− Status {statusCode = 200, statusMessage = "OK"}
252− , responseVersion = HTTP/1.1, responseHeaders =
253− [("Content-Type","application/json; charset=UTF-8"),
254− ("Content-Length","75")]
255− , responseBody = "{\"_index\":\"twitter\",\"_type\":\"tweet\",\"_id\":\"1\",\"_version\":2,\"created\":false}"
256− , responseCookieJar = CJ {expose = []}, responseClose' = ResponseClose}
257−
258−```
259−
260−### Deleting Documents
261−
262−``` {.haskell}
263−
264−resp <- withBH' $ deleteDocument testIndex testMapping (DocId "1")
265−
266−```
267−
268−### Getting Documents
269−
270−``` {.haskell}
271−
272−-- n.b., you'll need the earlier imports. responseBody is from http-conduit
273−
274−resp <- withBH' $ getDocument testIndex testMapping (DocId "1")
275−
276−-- responseBody :: Response body -> body
277−let body = responseBody resp
278−
279−-- you have two options, you use decode and just get Maybe (EsResult Tweet)
280−-- or you can use eitherDecode and get Either String (EsResult Tweet)
281−
282−let maybeResult = decode body :: Maybe (EsResult Tweet)
283−-- the explicit typing is so Aeson knows how to parse the JSON.
284−
285−-- use either if you want to know why something failed to parse.
286−-- (string errors, sadly)
287−let eitherResult = eitherDecode body :: Either String (EsResult Tweet)
288−
289−-- print eitherResult should look like:
290−Right (EsResult {_index = "twitter"
291− , _type = "tweet"
292− , _id = "1"
293− , foundResult = Just (EsResultFound { _version = 2
294− , _source = Tweet {user = "bitemyapp"
295− , postDate = 2009-06-18 00:00:10 UTC
296− , message = "Use haskell!"
297− , age = 10000
298− , location = Location {lat = 40.12, lon = -71.34}}})})
299−
300−-- _source in EsResultFound is parametric, we dispatch the type by passing in what we expect (Tweet) as a parameter to EsResult.
301−
302−-- use the _source record accessor to get at your document
303−fmap (fmap _source . foundResult) eitherResult
304−Right (Just (Tweet {user = "bitemyapp"
305− , postDate = 2009-06-18 00:00:10 UTC
306− , message = "Use haskell!"
307− , age = 10000
308− , location = Location {lat = 40.12, lon = -71.34}}))
309−
310−```
311−
312−Bulk Operations
313−---------------
314−
315−### Bulk create, index
316−
317−``` {.haskell}
318−
319−-- don't forget the imports and derive generic setting for ghci
320−-- at the beginning of the examples.
321−
322−:{
323−-- Using the earlier Tweet datatype and exampleTweet data
324−
325−-- just changing up the data a bit.
326−let bulkTest = exampleTweet { user = "blah" }
327−let bulkTestTwo = exampleTweet { message = "woohoo!" }
328−
329−-- create only bulk operation
330−-- BulkCreate :: IndexName -> MappingName -> DocId -> Value -> BulkOperation
331−let firstOp = BulkCreate testIndex
332− testMapping (DocId "3") (toJSON bulkTest)
333−
334−-- index operation "create or update"
335−let sndOp = BulkIndex testIndex
336− testMapping (DocId "4") (toJSON bulkTestTwo)
337−
338−-- Some explanation, the final "Value" type that BulkIndex,
339−-- BulkCreate, and BulkUpdate accept is the actual document
340−-- data that your operation applies to. BulkDelete doesn't
341−-- take a value because it's just deleting whatever DocId
342−-- you pass.
343−
344−-- list of bulk operations
345−let stream = [firstDoc, secondDoc]
346−
347−-- Fire off the actual bulk request
348−-- bulk :: Vector BulkOperation -> IO Reply
349−resp <- withBH' $ bulk stream
350−:}
351−
352−```
353−
354−### Encoding individual bulk API operations
355−
356−``` {.haskell}
357−-- the following functions are exported in Bloodhound so
358−-- you can build up bulk operations yourself
359−encodeBulkOperations :: V.Vector BulkOperation -> L.ByteString
360−encodeBulkOperation :: BulkOperation -> L.ByteString
361−
362−-- How to use the above:
363−data BulkTest = BulkTest { name :: Text } deriving (Eq, Generic, Show)
364−instance FromJSON BulkTest
365−instance ToJSON BulkTest
366−
367−_ <- insertData
368−let firstTest = BulkTest "blah"
369−let secondTest = BulkTest "bloo"
370−let firstDoc = BulkIndex testIndex
371− testMapping (DocId "2") (toJSON firstTest)
372−let secondDoc = BulkCreate testIndex
373− testMapping (DocId "3") (toJSON secondTest)
374−let stream = V.fromList [firstDoc, secondDoc] :: V.Vector BulkOperation
375−
376−-- to encode yourself
377−let firstDocEncoded = encode firstDoc :: L.ByteString
378−
379−-- to encode a vector of bulk operations
380−let encodedOperations = encodeBulkOperations stream
381−
382−-- to insert into a particular server
383−-- bulk :: V.Vector BulkOperation -> IO Reply
384−_ <- withBH' $ bulk streamp
385−
386−```
387−
388−Search
389−------
390−
391−### Querying
392−
393−#### Term Query
394−
395−``` {.haskell}
396−
397−-- exported by the Client module, just defaults some stuff.
398−-- mkSearch :: Maybe Query -> Maybe Filter -> Search
399−-- mkSearch query filter = Search query filter Nothing False (From 0) (Size 10) Nothing
400−
401−let query = TermQuery (Term "user" "bitemyapp") Nothing
402−
403−-- AND'ing identity filter with itself and then tacking it onto a query
404−-- search should be a null-operation. I include it for the sake of example.
405−-- <||> (or/plus) should make it into a search that returns everything.
406−
407−let filter = IdentityFilter <&&> IdentityFilter
408−
409−-- constructing the search object the searchByIndex function dispatches on.
410−let search = mkSearch (Just query) (Just filter)
411−
412−-- you can also searchByType and specify the mapping name.
413−reply <- withBH' $ searchByIndex testIndex search
414−
415−let result = eitherDecode (responseBody reply) :: Either String (SearchResult Tweet)
416−
417−λ> fmap (hits . searchHits) result
418−Right [Hit {hitIndex = IndexName "twitter"
419− , hitType = MappingName "tweet"
420− , hitDocId = DocId "1"
421− , hitScore = 0.30685282
422− , hitSource = Tweet {user = "bitemyapp"
423− , postDate = 2009-06-18 00:00:10 UTC
424− , message = "Use haskell!"
425− , age = 10000
426− , location = Location {lat = 40.12, lon = -71.34}}}]
427−
428−```
429−
430−#### Match Query
431−
432−``` {.haskell}
433−
434−let query = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
435−let search = mkSearch (Just query) Nothing
436−
437−```
438−
439−#### Multi-Match Query
440−
441−``` {.haskell}
442−
443−let fields = [FieldName "user", FieldName "message"]
444−let query = QueryMultiMatchQuery $ mkMultiMatchQuery fields (QueryString "bitemyapp")
445−let search = mkSearch (Just query) Nothing
446−
447−```
448−
449−#### Bool Query
450−
451−``` {.haskell}
452−
453−let innerQuery = QueryMatchQuery $
454− mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
455−let query = QueryBoolQuery $
456− mkBoolQuery [innerQuery] [] []
457−let search = mkSearch (Just query) Nothing
458−
459−```
460−
461−#### Boosting Query
462−
463−``` {.haskell}
464−
465−let posQuery = QueryMatchQuery $
466− mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
467−let negQuery = QueryMatchQuery $
468− mkMatchQuery (FieldName "user") (QueryString "notmyapp")
469−let query = QueryBoostingQuery $
470− BoostingQuery posQuery negQuery (Boost 0.2)
471−
472−```
473−
474−#### Rest of the query/filter types
475−
476−Just follow the pattern you've seen here and check the Hackage API documentation.
477−
478−### Sorting
479−
480−``` {.haskell}
481−
482−let sortSpec = DefaultSortSpec $ mkSort (FieldName "age") Ascending
483−
484−-- mkSort is a shortcut function that takes a FieldName and a SortOrder
485−-- to generate a vanilla DefaultSort.
486−-- checkt the DefaultSort type for the full list of customizable options.
487−
488−-- From and size are integers for pagination.
489−
490−-- When sorting on a field, scores are not computed. By setting TrackSortScores to true, scores will still be computed and tracked.
491−
492−-- type Sort = [SortSpec]
493−-- type TrackSortScores = Bool
494−-- type From = Int
495−-- type Size = Int
496−
497−-- Search takes Maybe Query
498−-- -> Maybe Filter
499−-- -> Maybe Sort
500−-- -> TrackSortScores
501−-- -> From -> Size
502−-- -> Maybe [FieldName]
503−
504−-- just add more sortspecs to the list if you want tie-breakers.
505−let search = Search Nothing (Just IdentityFilter) (Just [sortSpec]) False (From 0) (Size 10) Nothing
506−
507−```
508−
509−### Field selection
510−
511−If you only want certain fields from the source document returned, you can
512−set the "fields" field of the Search record.
513−
514−``` {.haskell}
515−
516−let search' = mkSearch (Just (MatchAllQuery Nothing)) Nothing
517− search = search' { fields = Just [FieldName "updated"] }
518−
519−```
520−
521−### Filtering
522−
523−#### And, Not, and Or filters
524−
525−Filters form a monoid and seminearring.
526−
527−``` {.haskell}
528−
529−instance Monoid Filter where
530− mempty = IdentityFilter
531− mappend a b = AndFilter [a, b] defaultCache
532−
533−instance Seminearring Filter where
534− a <||> b = OrFilter [a, b] defaultCache
535−
536−-- AndFilter and OrFilter take [Filter] as an argument.
537−
538−-- This will return anything, because IdentityFilter returns everything
539−OrFilter [IdentityFilter, someOtherFilter] False
540−
541−-- This will return exactly what someOtherFilter returns
542−AndFilter [IdentityFilter, someOtherFilter] False
543−
544−-- Thanks to the seminearring and monoid, the above can be expressed as:
545−
546−-- "and"
547−IdentityFilter <&&> someOtherFilter
548−
549−-- "or"
550−IdentityFilter <||> someOtherFilter
551−
552−-- Also there is a NotFilter, it only accepts a single filter, not a list.
553−
554−NotFilter someOtherFilter False
555−
556−```
557−
558−#### Identity Filter
559−
560−``` {.haskell}
561−
562−-- And'ing two Identity
563−let queryFilter = IdentityFilter <&&> IdentityFilter
564−
565−let search = mkSearch Nothing (Just queryFilter)
566−
567−reply <- withBH' $ searchByType testIndex testMapping search
568−
569−```
570−
571−#### Boolean Filter
572−
573−Similar to boolean queries.
574−
575−``` {.haskell}
576−
577−-- Will return only items whose "user" field contains the term "bitemyapp"
578−let queryFilter = BoolFilter (MustMatch (Term "user" "bitemyapp") False)
579−
580−-- Will return only items whose "user" field does not contain the term "bitemyapp"
581−let queryFilter = BoolFilter (MustNotMatch (Term "user" "bitemyapp") False)
582−
583−-- The clause (query) should appear in the matching document.
584−-- In a boolean query with no must clauses, one or more should
585−-- clauses must match a document. The minimum number of should
586−-- clauses to match can be set using the minimum_should_match parameter.
587−let queryFilter = BoolFilter (ShouldMatch [(Term "user" "bitemyapp")] False)
588−
589−```
590−
591−#### Exists Filter
592−
593−``` {.haskell}
594−
595−-- Will filter for documents that have the field "user"
596−let existsFilter = ExistsFilter (FieldName "user")
597−
598−```
599−
600−#### Geo BoundingBox Filter
601−
602−``` {.haskell}
603−
604−-- topLeft and bottomRight
605−let box = GeoBoundingBox (LatLon 40.73 (-74.1)) (LatLon 40.10 (-71.12))
606−
607−let constraint = GeoBoundingBoxConstraint (FieldName "tweet.location") box False GeoFilterMemory
608−
609−```
610−
611−#### Geo Distance Filter
612−
613−``` {.haskell}
614−
615−let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
616−
617−-- coefficient and units
618−let distance = Distance 10.0 Miles
619−
620−-- GeoFilterType or NoOptimizeBbox
621−let optimizeBbox = OptimizeGeoFilterType GeoFilterMemory
622−
623−-- SloppyArc is the usual/default optimization in Elasticsearch today
624−-- but pre-1.0 versions will need to pick Arc or Plane.
625−
626−let geoFilter = GeoDistanceFilter geoPoint distance SloppyArc optimizeBbox False
627−
628−```
629−
630−#### Geo Distance Range Filter
631−
632−Think of a donut and you won't be far off.
633−
634−``` {.haskell}
635−
636−let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
637−
638−let distanceRange = DistanceRange (Distance 0.0 Miles) (Distance 10.0 Miles)
639−
640−let geoFilter = GeoDistanceRangeFilter geoPoint distanceRange
641−
642−```
643−
644−#### Geo Polygon Filter
645−
646−``` {.haskell}
647−
648−-- I think I drew a square here.
649−let points = [LatLon 40.0 (-70.00),
650− LatLon 40.0 (-72.00),
651− LatLon 41.0 (-70.00),
652− LatLon 41.0 (-72.00)]
653−
654−let geoFilter = GeoPolygonFilter (FieldName "tweet.location") points
655−
656−```
657−
658−#### Document IDs filter
659−
660−``` {.haskell}
661−
662−-- takes a mapping name and a list of DocIds
663−IdsFilter (MappingName "tweet") [DocId "1"]
664−
665−```
666−
667−#### Range Filter
668−
669−``` {.haskell}
670−
671−-- RangeFilter :: FieldName
672−-- -> RangeValue
673−-- -> RangeExecution
674−-- -> Cache -> Filter
675−
676−let filter = RangeFilter (FieldName "age")
677− (RangeGtLt (GreaterThan 1000.0) (LessThan 100000.0))
678− RangeExecutionIndex False
679−
680−```
681−
682−``` {.haskell}
683−
684−let filter = RangeFilter (FieldName "age")
685− (RangeLte (LessThanEq 100000.0))
686− RangeExecutionIndex False
687−
688−```
689−
690−##### Date Ranges
691−
692−Date ranges are expressed in UTCTime. Date ranges use the same range bound constructors as numerics, except that they end in "D".
693−
694−Note that compatibility with ES is tested only down to seconds.
695−
696−``` {.haskell}
697−
698−let filter = RangeFilter (FieldName "postDate")
699− (RangeDateGtLte
700− (GreaterThanD (UTCTime
701− (ModifiedJulianDay 55000)
702− (secondsToDiffTime 9)))
703− (LessThanEqD (UTCTime
704− (ModifiedJulianDay 55000)
705− (secondsToDiffTime 11))))
706− RangeExecutionIndex False
707−```
708−
709−#### Regexp Filter
710−
711−``` {.haskell}
712−
713−-- RegexpFilter
714−-- :: FieldName
715−-- -> Regexp
716−-- -> RegexpFlags
717−-- -> CacheName
718−-- -> Cache
719−-- -> CacheKey
720−-- -> Filter
721−let filter = RegexpFilter (FieldName "user") (Regexp "bite.*app")
722− AllRegexpFlags (CacheName "test") False (CacheKey "key")
723−
724−-- n.b.
725−-- data RegexpFlags = AllRegexpFlags
726−-- | NoRegexpFlags
727−-- | SomeRegexpFlags (NonEmpty RegexpFlag) deriving (Eq, Show)
728−
729−-- data RegexpFlag = AnyString
730−-- | Automaton
731−-- | Complement
732−-- | Empty
733−-- | Intersection
734−-- | Interval deriving (Eq, Show)
735−
736−```
737−
738−### Aggregations
739−
740−#### Adding aggregations to search
741−
742−Aggregations can now be added to search queries, or made on their own.
743−
744−``` {.haskell}
745−type Aggregations = M.Map Text Aggregation
746−data Aggregation
747− = TermsAgg TermsAggregation
748− | DateHistogramAgg DateHistogramAggregation
749−```
750−
751−For convenience, \`\`\`mkAggregations\`\`\` exists, that will create an \`\`\`Aggregations\`\`\` with the aggregation provided.
752−
753−For example:
754−
755−``` {.haskell}
756− let a = mkAggregations "users" $ TermsAgg $ mkTermsAggregation "user"
757− let search = mkAggregateSearch Nothing a
758−```
759−
760−Aggregations can be added to an existing search, using the \`\`\`aggBody\`\`\` field
761−
762−``` {.haskell}
763− let search = mkSearch (Just (MatchAllQuery Nothing)) Nothing
764− let search' = search {aggBody = Just a}
765−```
766−
767−Since the \`\`\`Aggregations\`\`\` structure is just a Map Text Aggregation, M.insert can be used to add additional aggregations.
768−
769−``` {.haskell}
770− let a' = M.insert "age" (TermsAgg $ mkTermsAggregation "age") a
771−```
772−
773−#### Extracting aggregations from results
774−
775−Aggregations are part of the reply structure of every search, in the
776−form of `Maybe AggregationResults`
777−
778−``` {.haskell}
779−-- Lift decode and response body to be in the IO monad.
780−let decode' = liftM decode
781−let responseBody' = liftM responseBody
782−let reply = withBH' $ searchByIndex testIndex search
783−let response = decode' $ responseBody' reply :: IO (Maybe (SearchResult Tweet))
784−
785−-- Now that we have our response, we can extract our terms aggregation result -- which is a list of buckets.
786−
787−let terms = do { response' <- response; return $ response' >>= aggregations >>= toTerms "users" }
788−terms
789−Just (Bucket {buckets = [TermsResult {termKey = "bitemyapp", termsDocCount = 1, termsAggs = Nothing}]})
790−```
791−
792−Note that bucket aggregation results, such as the TermsResult is a member of the type class `BucketAggregation`:
793−
794−``` {.haskell}
795−class BucketAggregation a where
796− key :: a -> Text
797− docCount :: a -> Int
798− aggs :: a -> Maybe AggregationResults
799−```
800−
801−You can use the `aggs` function to get any nested results, if there
802−were any. For example, if there were a nested terms aggregation keyed
803−to "age" in a TermsResult named `termresult` , you would call `aggs
804−termresult >>= toTerms "age"`
805−
806−#### Terms Aggregation
807−
808−``` {.haskell}
809−data TermsAggregation
810− = TermsAggregation {term :: Either Text Text,
811− termInclude :: Maybe TermInclusion,
812− termExclude :: Maybe TermInclusion,
813− termOrder :: Maybe TermOrder,
814− termMinDocCount :: Maybe Int,
815− termSize :: Maybe Int,
816− termShardSize :: Maybe Int,
817− termCollectMode :: Maybe CollectionMode,
818− termExecutionHint :: Maybe ExecutionHint,
819− termAggs :: Maybe Aggregations}
820−```
821−
822−Term Aggregations have two factory functions, `mkTermsAggregation`, and
823−`mkTermsScriptAggregation`, and can be used as follows:
824−
825−``` {.haskell}
826−let ta = TermsAgg $ mkTermsAggregation "user"
827−```
828−
829−There are of course other options that can be added to a Terms Aggregation, such as the collection mode:
830−
831−``` {.haskell}
832−let ta = mkTermsAggregation "user"
833−let ta' = ta { termCollectMode = Just BreadthFirst }
834−let ta'' = TermsAgg ta'
835−```
836−
837−For more documentation on how the Terms Aggregation works, see <https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html>
838−
839−#### Date Histogram Aggregation
840−
841−``` {.haskell}
842−data DateHistogramAggregation
843− = DateHistogramAggregation {dateField :: FieldName,
844− dateInterval :: Interval,
845− dateFormat :: Maybe Text,
846− datePreZone :: Maybe Text,
847− datePostZone :: Maybe Text,
848− datePreOffset :: Maybe Text,
849− datePostOffset :: Maybe Text,
850− dateAggs :: Maybe Aggregations}
851−```
852−
853−The Date Histogram Aggregation works much the same as the Terms Aggregation.
854−
855−Relevant functions include `mkDateHistogram`, and `toDateHistogram`
856−
857−``` {.haskell}
858−let dh = DateHistogramAgg (mkDateHistogram (FieldName "postDate") Minute)
859−```
860−
861−Date histograms also accept a `FractionalInterval`:
862−
863−``` {.haskell}
864−FractionalInterval :: Float -> TimeInterval -> Interval
865−-- TimeInterval is the following:
866−data TimeInterval = Weeks | Days | Hours | Minutes | Seconds
867−```
868−
869−It can be used as follows:
870−
871−``` {.haskell}
872−let dh = DateHistogramAgg (mkDateHistogram (FieldName "postDate") (FractionalInterval 1.5 Minutes))
873−```
874−
875−The `DateHistogramResult` is defined as:
876−
877−``` {.haskell}
878−data DateHistogramResult
879− = DateHistogramResult {dateKey :: Int,
880− dateKeyStr :: Maybe Text,
881− dateDocCount :: Int,
882− dateHistogramAggs :: Maybe AggregationResults}
883−```
884−
885−It is an instance of `BucketAggregation`, and can have nested aggregations in each bucket.
886−
887−Buckets can be extracted from an `AggregationResult` using
888−`toDateHistogram name`
889−
890−For more information on the Date Histogram Aggregation, see: <https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-datehistogram-aggregation.html>
891
71
892
72
893Contributors
73Contributors
bloodhound.cabal−20
@@ -95,23 +95,3 @@ test-suite tests
95 unix-compat,
95 unix-compat,
96 network-uri
96 network-uri
97 default-language: Haskell2010
97 default-language: Haskell2010
98−
99−test-suite doctests
100− ghc-options: -threaded -Wall
101− default-language: Haskell2010
102− type: exitcode-stdio-1.0
103− main-is: doctests.hs
104− hs-source-dirs: src
105− if flag(ES5)
106− hs-source-dirs: tests/V5
107− else
108− hs-source-dirs: tests/V1
109− if impl(ghc >= 7.8)
110− build-depends: base,
111− aeson,
112− bloodhound,
113− directory,
114− doctest >= 0.10.1,
115− filepath
116− else
117− buildable: False
examples/LICENSEnew file+12
@@ -0,0 +1,12 @@
1+Copyright (c) 2014, Chris Allen
2+All rights reserved.
3+
4+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5+
6+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7+
8+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9+
10+3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11+
12+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
examples/README.mdnew file+3
@@ -0,0 +1,3 @@
1+# Bloodhound Examples
2+
3+These examples can be build via `stack build`.
examples/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
examples/Tweet.hsnew file+125
@@ -0,0 +1,125 @@
1+{-# LANGUAGE DeriveGeneric #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+module Main
4+ ( main
5+ ) where
6+
7+
8+-------------------------------------------------------------------------------
9+import Control.Monad.IO.Class (liftIO)
10+import Data.Aeson (FromJSON (..), defaultOptions,
11+ genericParseJSON, genericToJSON,
12+ object, (.=))
13+import Data.List.NonEmpty (NonEmpty (..))
14+import Data.Text (Text)
15+import Data.Time.Calendar (Day (..))
16+import Data.Time.Clock (UTCTime (..), secondsToDiffTime)
17+import qualified Data.Vector as V
18+import Database.V1.Bloodhound
19+import GHC.Generics (Generic)
20+import Network.HTTP.Client (defaultManagerSettings)
21+-------------------------------------------------------------------------------
22+
23+
24+data TweetMapping = TweetMapping deriving (Eq, Show)
25+
26+instance ToJSON TweetMapping where
27+ toJSON TweetMapping =
28+ object
29+ [ "properties" .=
30+ object ["location" .= object ["type" .= ("geo_point" :: Text)]]
31+ ]
32+
33+
34+-------------------------------------------------------------------------------
35+data Location = Location
36+ { locLat :: Double
37+ , locLon :: Double
38+ } deriving (Eq, Generic, Show)
39+
40+
41+-------------------------------------------------------------------------------
42+data Tweet = Tweet
43+ { user :: Text
44+ , postDate :: UTCTime
45+ , message :: Text
46+ , age :: Int
47+ , location :: Location
48+ } deriving (Eq, Generic, Show)
49+
50+
51+-------------------------------------------------------------------------------
52+exampleTweet :: Tweet
53+exampleTweet =
54+ Tweet
55+ { user = "bitemyapp"
56+ , postDate = UTCTime (ModifiedJulianDay 55000) (secondsToDiffTime 10)
57+ , message = "Use haskell!"
58+ , age = 10000
59+ , location = loc
60+ }
61+ where
62+ loc = Location {locLat = 40.12, locLon = (-71.34)}
63+
64+instance ToJSON Tweet where
65+ toJSON = genericToJSON defaultOptions
66+instance FromJSON Tweet where
67+ parseJSON = genericParseJSON defaultOptions
68+instance ToJSON Location where
69+ toJSON = genericToJSON defaultOptions
70+instance FromJSON Location where
71+ parseJSON = genericParseJSON defaultOptions
72+
73+
74+-------------------------------------------------------------------------------
75+main :: IO ()
76+main = runBH' $ do
77+ -- set up index
78+ _ <- createIndex indexSettings testIndex
79+ True <- indexExists testIndex
80+ _ <- putMapping testIndex testMapping TweetMapping
81+
82+ -- create a tweet
83+ resp <- indexDocument testIndex testMapping defaultIndexDocumentSettings exampleTweet (DocId "1")
84+ liftIO (print resp)
85+ -- Response {responseStatus = Status {statusCode = 201, statusMessage = "Created"}, responseVersion = HTTP/1.1, responseHeaders = [("Content-Type","application/json; charset=UTF-8"),("Content-Length","74")], responseBody = "{\"_index\":\"twitter\",\"_type\":\"tweet\",\"_id\":\"1\",\"_version\":1,\"created\":true}", responseCookieJar = CJ {expose = []}, responseClose' = ResponseClose}
86+
87+ -- bulk load
88+ let stream = V.fromList [BulkIndex testIndex testMapping (DocId "2") (toJSON exampleTweet)]
89+ _ <- bulk stream
90+ -- Bulk loads require an index refresh before new data is loaded.
91+ _ <- refreshIndex testIndex
92+
93+ -- set up some aliases
94+ let aliasName = IndexName "twitter-alias"
95+ let iAlias = IndexAlias testIndex (IndexAliasName aliasName)
96+ let aliasRouting = Nothing
97+ let aliasFiltering = Nothing
98+ let aliasCreate = IndexAliasCreate aliasRouting aliasFiltering
99+ _ <- updateIndexAliases (AddAlias iAlias aliasCreate :| [])
100+ True <- indexExists aliasName
101+
102+ -- create a template so that if we just write into an index named tweet-2017-01-02, for instance, the index will be automatically created with the given mapping. This is a great idea for any ongoing indices because it makes them much easier to manage and rotate.
103+ let idxTpl = IndexTemplate (TemplatePattern "tweet-*") (Just (IndexSettings (ShardCount 1) (ReplicaCount 1))) [toJSON TweetMapping]
104+ let templateName = TemplateName "tweet-tpl"
105+ _ <- putTemplate idxTpl templateName
106+ True <- templateExists templateName
107+
108+ -- do a search
109+ let boost = Nothing
110+ let query = TermQuery (Term "user" "bitemyapp") boost
111+ let search = mkSearch (Just query) boost
112+ _ <- searchByType testIndex testMapping search
113+
114+ -- clean up
115+ _ <- deleteTemplate templateName
116+ _ <- deleteIndex testIndex
117+ False <- indexExists testIndex
118+
119+ return ()
120+ where
121+ testServer = (Server "http://localhost:9200")
122+ runBH' = withBH defaultManagerSettings testServer
123+ testIndex = IndexName "twitter"
124+ testMapping = MappingName "tweet"
125+ indexSettings = IndexSettings (ShardCount 1) (ReplicaCount 0)
examples/package.yamlnew file+38
@@ -0,0 +1,38 @@
1+name: bloodhound-examples
2+version: '0.0.0.0'
3+category: Web
4+author: Chris Allen
5+maintainer: cma@bitemyapp.com
6+copyright: 2017, Chris Allen
7+license: BSD3
8+github: bitemyapp/bloodhound
9+extra-source-files:
10+- README.md
11+dependencies:
12+- base
13+- bloodhound
14+- time
15+- aeson
16+- text
17+- http-client
18+- vector
19+- semigroups
20+- transformers
21+ghc-options:
22+- -Wall
23+- -threaded
24+- -rtsopts
25+- -with-rtsopts=-N
26+when:
27+- condition: flag(werror)
28+ ghc-options: -Werror
29+
30+executables:
31+ tweet-example:
32+ main: Tweet.hs
33+
34+flags:
35+ werror:
36+ description: "Treat warnings as errors"
37+ manual: true
38+ default: false
src/Database/V1/Bloodhound/Client.hs−180
@@ -123,60 +123,8 @@ import Prelude hiding (filter, head)
123
123
124import Database.V1.Bloodhound.Types
124import Database.V1.Bloodhound.Types
125
125
126−-- $setup
127−-- >>> :set -XOverloadedStrings
128−-- >>> :set -XDeriveGeneric
129−-- >>> import Database.V1.Bloodhound
130−-- >>> let testServer = (Server "http://localhost:9200")
131−-- >>> let runBH' = withBH defaultManagerSettings testServer
132−-- >>> let testIndex = IndexName "twitter"
133−-- >>> let testMapping = MappingName "tweet"
134−-- >>> let defaultIndexSettings = IndexSettings (ShardCount 1) (ReplicaCount 0)
135−-- >>> data TweetMapping = TweetMapping deriving (Eq, Show)
136−-- >>> _ <- runBH' $ deleteIndex testIndex >> deleteMapping testIndex testMapping
137−-- >>> import GHC.Generics
138−-- >>> import Data.Time.Calendar (Day (..))
139−-- >>> import Data.Time.Clock (UTCTime (..), secondsToDiffTime)
140−-- >>> :{
141−--instance ToJSON TweetMapping where
142−-- toJSON TweetMapping =
143−-- object ["properties" .=
144−-- object ["location" .=
145−-- object ["type" .= ("geo_point" :: Text)]]]
146−--data Location = Location { lat :: Double
147−-- , lon :: Double } deriving (Eq, Generic, Show)
148−--data Tweet = Tweet { user :: Text
149−-- , postDate :: UTCTime
150−-- , message :: Text
151−-- , age :: Int
152−-- , location :: Location } deriving (Eq, Generic, Show)
153−--exampleTweet = Tweet { user = "bitemyapp"
154−-- , postDate = UTCTime
155−-- (ModifiedJulianDay 55000)
156−-- (secondsToDiffTime 10)
157−-- , message = "Use haskell!"
158−-- , age = 10000
159−-- , location = Location 40.12 (-71.34) }
160−--instance ToJSON Tweet where
161−-- toJSON = genericToJSON defaultOptions
162−--instance FromJSON Tweet where
163−-- parseJSON = genericParseJSON defaultOptions
164−--instance ToJSON Location where
165−-- toJSON = genericToJSON defaultOptions
166−--instance FromJSON Location where
167−-- parseJSON = genericParseJSON defaultOptions
168−--data BulkTest = BulkTest { name :: Text } deriving (Eq, Generic, Show)
169−--instance FromJSON BulkTest where
170−-- parseJSON = genericParseJSON defaultOptions
171−--instance ToJSON BulkTest where
172−-- toJSON = genericToJSON defaultOptions
173−-- :}
174−
175-- | 'mkShardCount' is a straight-forward smart constructor for 'ShardCount'
126-- | 'mkShardCount' is a straight-forward smart constructor for 'ShardCount'
176-- which rejects 'Int' values below 1 and above 1000.
127-- which rejects 'Int' values below 1 and above 1000.
177−--
178−-- >>> mkShardCount 10
179−-- Just (ShardCount 10)
180mkShardCount :: Int -> Maybe ShardCount
128mkShardCount :: Int -> Maybe ShardCount
181mkShardCount n
129mkShardCount n
182 | n < 1 = Nothing
130 | n < 1 = Nothing
@@ -185,9 +133,6 @@ mkShardCount n
185
133
186-- | 'mkReplicaCount' is a straight-forward smart constructor for 'ReplicaCount'
134-- | 'mkReplicaCount' is a straight-forward smart constructor for 'ReplicaCount'
187-- which rejects 'Int' values below 0 and above 1000.
135-- which rejects 'Int' values below 0 and above 1000.
188−--
189−-- >>> mkReplicaCount 10
190−-- Just (ReplicaCount 10)
191mkReplicaCount :: Int -> Maybe ReplicaCount
136mkReplicaCount :: Int -> Maybe ReplicaCount
192mkReplicaCount n
137mkReplicaCount n
193 | n < 0 = Nothing
138 | n < 0 = Nothing
@@ -271,10 +216,6 @@ post = dispatch NHTM.methodPost
271-- https://github.com/supki/libjenkins/blob/master/src/Jenkins/Rest/Internal.hs
216-- https://github.com/supki/libjenkins/blob/master/src/Jenkins/Rest/Internal.hs
272
217
273-- | 'getStatus' fetches the 'Status' of a 'Server'
218-- | 'getStatus' fetches the 'Status' of a 'Server'
274−--
275−-- >>> serverStatus <- runBH' getStatus
276−-- >>> fmap status (serverStatus)
277−-- Just 200
278getStatus :: MonadBH m => m (Maybe Status)
219getStatus :: MonadBH m => m (Maybe Status)
279getStatus = do
220getStatus = do
280 response <- get =<< url
221 response <- get =<< url
@@ -494,12 +435,6 @@ getNodesStats sel = parseEsResponse =<< get =<< url
494 selToSeg (NodeByAttribute (NodeAttrName a) v) = a <> ":" <> v
435 selToSeg (NodeByAttribute (NodeAttrName a) v) = a <> ":" <> v
495
436
496-- | 'createIndex' will create an index given a 'Server', 'IndexSettings', and an 'IndexName'.
437-- | 'createIndex' will create an index given a 'Server', 'IndexSettings', and an 'IndexName'.
497−--
498−-- >>> response <- runBH' $ createIndex defaultIndexSettings (IndexName "didimakeanindex")
499−-- >>> respIsTwoHunna response
500−-- True
501−-- >>> runBH' $ indexExists (IndexName "didimakeanindex")
502−-- True
503createIndex :: MonadBH m => IndexSettings -> IndexName -> m Reply
438createIndex :: MonadBH m => IndexSettings -> IndexName -> m Reply
504createIndex indexSettings (IndexName indexName) =
439createIndex indexSettings (IndexName indexName) =
505 bindM2 put url (return body)
440 bindM2 put url (return body)
@@ -508,23 +443,11 @@ createIndex indexSettings (IndexName indexName) =
508
443
509
444
510-- | 'deleteIndex' will delete an index given a 'Server', and an 'IndexName'.
445-- | 'deleteIndex' will delete an index given a 'Server', and an 'IndexName'.
511−--
512−-- >>> _ <- runBH' $ createIndex defaultIndexSettings (IndexName "didimakeanindex")
513−-- >>> response <- runBH' $ deleteIndex (IndexName "didimakeanindex")
514−-- >>> respIsTwoHunna response
515−-- True
516−-- >>> runBH' $ indexExists testIndex
517−-- False
518deleteIndex :: MonadBH m => IndexName -> m Reply
446deleteIndex :: MonadBH m => IndexName -> m Reply
519deleteIndex (IndexName indexName) =
447deleteIndex (IndexName indexName) =
520 delete =<< joinPath [indexName]
448 delete =<< joinPath [indexName]
521
449
522-- | 'updateIndexSettings' will apply a non-empty list of setting updates to an index
450-- | 'updateIndexSettings' will apply a non-empty list of setting updates to an index
523−--
524−-- >>> _ <- runBH' $ createIndex defaultIndexSettings (IndexName "unconfiguredindex")
525−-- >>> response <- runBH' $ updateIndexSettings (BlocksWrite False :| []) (IndexName "unconfiguredindex")
526−-- >>> respIsTwoHunna response
527−-- True
528updateIndexSettings :: MonadBH m => NonEmpty UpdatableIndexSetting -> IndexName -> m Reply
451updateIndexSettings :: MonadBH m => NonEmpty UpdatableIndexSetting -> IndexName -> m Reply
529updateIndexSettings updates (IndexName indexName) =
452updateIndexSettings updates (IndexName indexName) =
530 bindM2 put url (return body)
453 bindM2 put url (return body)
@@ -557,12 +480,6 @@ getIndexSettings (IndexName indexName) = do
557-- almost completely identical forcemerge API. Adding support to that
480-- almost completely identical forcemerge API. Adding support to that
558-- API would be trivial but due to the significant breaking changes,
481-- API would be trivial but due to the significant breaking changes,
559-- this library cannot currently be used with >= 2.0, so that feature was omitted.
482-- this library cannot currently be used with >= 2.0, so that feature was omitted.
560−--
561−-- >>> let ixn = IndexName "unoptimizedindex"
562−-- >>> _ <- runBH' $ deleteIndex ixn >> createIndex defaultIndexSettings ixn
563−-- >>> response <- runBH' $ optimizeIndex (IndexList (ixn :| [])) (defaultIndexOptimizationSettings { maxNumSegments = Just 1, onlyExpungeDeletes = True })
564−-- >>> respIsTwoHunna response
565−-- True
566optimizeIndex :: MonadBH m => IndexSelection -> IndexOptimizationSettings -> m Reply
483optimizeIndex :: MonadBH m => IndexSelection -> IndexOptimizationSettings -> m Reply
567optimizeIndex ixs IndexOptimizationSettings {..} =
484optimizeIndex ixs IndexOptimizationSettings {..} =
568 bindM2 post url (return body)
485 bindM2 post url (return body)
@@ -617,8 +534,6 @@ parseEsResponse reply
617
534
618-- | 'indexExists' enables you to check if an index exists. Returns 'Bool'
535-- | 'indexExists' enables you to check if an index exists. Returns 'Bool'
619-- in IO
536-- in IO
620−--
621−-- >>> exists <- runBH' $ indexExists testIndex
622indexExists :: MonadBH m => IndexName -> m Bool
537indexExists :: MonadBH m => IndexName -> m Bool
623indexExists (IndexName indexName) = do
538indexExists (IndexName indexName) = do
624 (_, exists) <- existentialQuery =<< joinPath [indexName]
539 (_, exists) <- existentialQuery =<< joinPath [indexName]
@@ -626,9 +541,6 @@ indexExists (IndexName indexName) = do
626
541
627-- | 'refreshIndex' will force a refresh on an index. You must
542-- | 'refreshIndex' will force a refresh on an index. You must
628-- do this if you want to read what you wrote.
543-- do this if you want to read what you wrote.
629−--
630−-- >>> _ <- runBH' $ createIndex defaultIndexSettings testIndex
631−-- >>> _ <- runBH' $ refreshIndex testIndex
632refreshIndex :: MonadBH m => IndexName -> m Reply
544refreshIndex :: MonadBH m => IndexName -> m Reply
633refreshIndex (IndexName indexName) =
545refreshIndex (IndexName indexName) =
634 bindM2 post url (return Nothing)
546 bindM2 post url (return Nothing)
@@ -655,15 +567,11 @@ openOrCloseIndexes oci (IndexName indexName) =
655
567
656-- | 'openIndex' opens an index given a 'Server' and an 'IndexName'. Explained in further detail at
568-- | 'openIndex' opens an index given a 'Server' and an 'IndexName'. Explained in further detail at
657-- <http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-open-close.html>
569-- <http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-open-close.html>
658−--
659−-- >>> reply <- runBH' $ openIndex testIndex
660openIndex :: MonadBH m => IndexName -> m Reply
570openIndex :: MonadBH m => IndexName -> m Reply
661openIndex = openOrCloseIndexes OpenIndex
571openIndex = openOrCloseIndexes OpenIndex
662
572
663-- | 'closeIndex' closes an index given a 'Server' and an 'IndexName'. Explained in further detail at
573-- | 'closeIndex' closes an index given a 'Server' and an 'IndexName'. Explained in further detail at
664-- <http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-open-close.html>
574-- <http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-open-close.html>
665−--
666−-- >>> reply <- runBH' $ closeIndex testIndex
667closeIndex :: MonadBH m => IndexName -> m Reply
575closeIndex :: MonadBH m => IndexName -> m Reply
668closeIndex = openOrCloseIndexes CloseIndex
576closeIndex = openOrCloseIndexes CloseIndex
669
577
@@ -684,20 +592,6 @@ listIndices =
684-- | 'updateIndexAliases' updates the server's index alias
592-- | 'updateIndexAliases' updates the server's index alias
685-- table. Operations are atomic. Explained in further detail at
593-- table. Operations are atomic. Explained in further detail at
686-- <https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html>
594-- <https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html>
687−--
688−-- >>> let src = IndexName "a-real-index"
689−-- >>> let aliasName = IndexName "an-alias"
690−-- >>> let iAlias = IndexAlias src (IndexAliasName aliasName)
691−-- >>> let aliasCreate = IndexAliasCreate Nothing Nothing
692−-- >>> _ <- runBH' $ deleteIndex src
693−-- >>> respIsTwoHunna <$> runBH' (createIndex defaultIndexSettings src)
694−-- True
695−-- >>> runBH' $ indexExists src
696−-- True
697−-- >>> respIsTwoHunna <$> runBH' (updateIndexAliases (AddAlias iAlias aliasCreate :| []))
698−-- True
699−-- >>> runBH' $ indexExists aliasName
700−-- True
701updateIndexAliases :: MonadBH m => NonEmpty IndexAliasAction -> m Reply
595updateIndexAliases :: MonadBH m => NonEmpty IndexAliasAction -> m Reply
702updateIndexAliases actions = bindM2 post url (return body)
596updateIndexAliases actions = bindM2 post url (return body)
703 where url = joinPath ["_aliases"]
597 where url = joinPath ["_aliases"]
@@ -713,9 +607,6 @@ getIndexAliases = parseEsResponse =<< get =<< url
713-- | 'putTemplate' creates a template given an 'IndexTemplate' and a 'TemplateName'.
607-- | 'putTemplate' creates a template given an 'IndexTemplate' and a 'TemplateName'.
714-- Explained in further detail at
608-- Explained in further detail at
715-- <https://www.elastic.co/guide/en/elasticsearch/reference/1.7/indices-templates.html>
609-- <https://www.elastic.co/guide/en/elasticsearch/reference/1.7/indices-templates.html>
716−--
717−-- >>> let idxTpl = IndexTemplate (TemplatePattern "tweet-*") (Just (IndexSettings (ShardCount 1) (ReplicaCount 1))) [toJSON TweetMapping]
718−-- >>> resp <- runBH' $ putTemplate idxTpl (TemplateName "tweet-tpl")
719putTemplate :: MonadBH m => IndexTemplate -> TemplateName -> m Reply
610putTemplate :: MonadBH m => IndexTemplate -> TemplateName -> m Reply
720putTemplate indexTemplate (TemplateName templateName) =
611putTemplate indexTemplate (TemplateName templateName) =
721 bindM2 put url (return body)
612 bindM2 put url (return body)
@@ -723,29 +614,18 @@ putTemplate indexTemplate (TemplateName templateName) =
723 body = Just $ encode indexTemplate
614 body = Just $ encode indexTemplate
724
615
725-- | 'templateExists' checks to see if a template exists.
616-- | 'templateExists' checks to see if a template exists.
726−--
727−-- >>> exists <- runBH' $ templateExists (TemplateName "tweet-tpl")
728templateExists :: MonadBH m => TemplateName -> m Bool
617templateExists :: MonadBH m => TemplateName -> m Bool
729templateExists (TemplateName templateName) = do
618templateExists (TemplateName templateName) = do
730 (_, exists) <- existentialQuery =<< joinPath ["_template", templateName]
619 (_, exists) <- existentialQuery =<< joinPath ["_template", templateName]
731 return exists
620 return exists
732
621
733-- | 'deleteTemplate' is an HTTP DELETE and deletes a template.
622-- | 'deleteTemplate' is an HTTP DELETE and deletes a template.
734−--
735−-- >>> let idxTpl = IndexTemplate (TemplatePattern "tweet-*") (Just (IndexSettings (ShardCount 1) (ReplicaCount 1))) [toJSON TweetMapping]
736−-- >>> _ <- runBH' $ putTemplate idxTpl (TemplateName "tweet-tpl")
737−-- >>> resp <- runBH' $ deleteTemplate (TemplateName "tweet-tpl")
738deleteTemplate :: MonadBH m => TemplateName -> m Reply
623deleteTemplate :: MonadBH m => TemplateName -> m Reply
739deleteTemplate (TemplateName templateName) =
624deleteTemplate (TemplateName templateName) =
740 delete =<< joinPath ["_template", templateName]
625 delete =<< joinPath ["_template", templateName]
741
626
742-- | 'putMapping' is an HTTP PUT and has upsert semantics. Mappings are schemas
627-- | 'putMapping' is an HTTP PUT and has upsert semantics. Mappings are schemas
743-- for documents in indexes.
628-- for documents in indexes.
744−--
745−-- >>> _ <- runBH' $ createIndex defaultIndexSettings testIndex
746−-- >>> resp <- runBH' $ putMapping testIndex testMapping TweetMapping
747−-- >>> print resp
748−-- Response {responseStatus = Status {statusCode = 200, statusMessage = "OK"}, responseVersion = HTTP/1.1, responseHeaders = [("Content-Type","application/json; charset=UTF-8"),("Content-Length","21")], responseBody = "{\"acknowledged\":true}", responseCookieJar = CJ {expose = []}, responseClose' = ResponseClose}
749putMapping :: (MonadBH m, ToJSON a) => IndexName
629putMapping :: (MonadBH m, ToJSON a) => IndexName
750 -> MappingName -> a -> m Reply
630 -> MappingName -> a -> m Reply
751putMapping (IndexName indexName) (MappingName mappingName) mapping =
631putMapping (IndexName indexName) (MappingName mappingName) mapping =
@@ -757,12 +637,6 @@ putMapping (IndexName indexName) (MappingName mappingName) mapping =
757
637
758-- | 'deleteMapping' is an HTTP DELETE and deletes a mapping for a given index.
638-- | 'deleteMapping' is an HTTP DELETE and deletes a mapping for a given index.
759-- Mappings are schemas for documents in indexes.
639-- Mappings are schemas for documents in indexes.
760−--
761−-- >>> _ <- runBH' $ createIndex defaultIndexSettings testIndex
762−-- >>> _ <- runBH' $ putMapping testIndex testMapping TweetMapping
763−-- >>> resp <- runBH' $ deleteMapping testIndex testMapping
764−-- >>> print resp
765−-- Response {responseStatus = Status {statusCode = 200, statusMessage = "OK"}, responseVersion = HTTP/1.1, responseHeaders = [("Content-Type","application/json; charset=UTF-8"),("Content-Length","21")], responseBody = "{\"acknowledged\":true}", responseCookieJar = CJ {expose = []}, responseClose' = ResponseClose}
766deleteMapping :: MonadBH m => IndexName -> MappingName -> m Reply
640deleteMapping :: MonadBH m => IndexName -> MappingName -> m Reply
767deleteMapping (IndexName indexName)
641deleteMapping (IndexName indexName)
768 (MappingName mappingName) =
642 (MappingName mappingName) =
@@ -788,10 +662,6 @@ versionCtlParams cfg =
788-- Elasticsearch. The document itself is simply something we can
662-- Elasticsearch. The document itself is simply something we can
789-- convert into a JSON 'Value'. The 'DocId' will function as the
663-- convert into a JSON 'Value'. The 'DocId' will function as the
790-- primary key for the document.
664-- primary key for the document.
791−--
792−-- >>> resp <- runBH' $ indexDocument testIndex testMapping defaultIndexDocumentSettings exampleTweet (DocId "1")
793−-- >>> print resp
794−-- Response {responseStatus = Status {statusCode = 201, statusMessage = "Created"}, responseVersion = HTTP/1.1, responseHeaders = [("Content-Type","application/json; charset=UTF-8"),("Content-Length","74")], responseBody = "{\"_index\":\"twitter\",\"_type\":\"tweet\",\"_id\":\"1\",\"_version\":1,\"created\":true}", responseCookieJar = CJ {expose = []}, responseClose' = ResponseClose}
795indexDocument :: (ToJSON doc, MonadBH m) => IndexName -> MappingName
665indexDocument :: (ToJSON doc, MonadBH m) => IndexName -> MappingName
796 -> IndexDocumentSettings -> doc -> DocId -> m Reply
666 -> IndexDocumentSettings -> doc -> DocId -> m Reply
797indexDocument (IndexName indexName)
667indexDocument (IndexName indexName)
@@ -816,8 +686,6 @@ updateDocument (IndexName indexName)
816 body = Just (encode $ object ["doc" .= toJSON patch])
686 body = Just (encode $ object ["doc" .= toJSON patch])
817
687
818-- | 'deleteDocument' is the primary way to delete a single document.
688-- | 'deleteDocument' is the primary way to delete a single document.
819−--
820−-- >>> _ <- runBH' $ deleteDocument testIndex testMapping (DocId "1")
821deleteDocument :: MonadBH m => IndexName -> MappingName
689deleteDocument :: MonadBH m => IndexName -> MappingName
822 -> DocId -> m Reply
690 -> DocId -> m Reply
823deleteDocument (IndexName indexName)
691deleteDocument (IndexName indexName)
@@ -830,10 +698,6 @@ deleteDocument (IndexName indexName)
830-- index\/update\/delete\/create operations. You pass a 'V.Vector' of 'BulkOperation's
698-- index\/update\/delete\/create operations. You pass a 'V.Vector' of 'BulkOperation's
831-- and a 'Server' to 'bulk' in order to send those operations up to your Elasticsearch
699-- and a 'Server' to 'bulk' in order to send those operations up to your Elasticsearch
832-- server to be performed. I changed from [BulkOperation] to a Vector due to memory overhead.
700-- server to be performed. I changed from [BulkOperation] to a Vector due to memory overhead.
833−--
834−-- >>> let stream = V.fromList [BulkIndex testIndex testMapping (DocId "2") (toJSON (BulkTest "blah"))]
835−-- >>> _ <- runBH' $ bulk stream
836−-- >>> _ <- runBH' $ refreshIndex testIndex
837bulk :: MonadBH m => V.Vector BulkOperation -> m Reply
701bulk :: MonadBH m => V.Vector BulkOperation -> m Reply
838bulk bulkOps = bindM2 post url (return body)
702bulk bulkOps = bindM2 post url (return body)
839 where url = joinPath ["_bulk"]
703 where url = joinPath ["_bulk"]
@@ -841,10 +705,6 @@ bulk bulkOps = bindM2 post url (return body)
841
705
842-- | 'encodeBulkOperations' is a convenience function for dumping a vector of 'BulkOperation'
706-- | 'encodeBulkOperations' is a convenience function for dumping a vector of 'BulkOperation'
843-- into an 'L.ByteString'
707-- into an 'L.ByteString'
844−--
845−-- >>> let bulkOps = V.fromList [BulkIndex testIndex testMapping (DocId "2") (toJSON (BulkTest "blah"))]
846−-- >>> encodeBulkOperations bulkOps
847−-- "\n{\"index\":{\"_type\":\"tweet\",\"_id\":\"2\",\"_index\":\"twitter\"}}\n{\"name\":\"blah\"}\n"
848encodeBulkOperations :: V.Vector BulkOperation -> L.ByteString
708encodeBulkOperations :: V.Vector BulkOperation -> L.ByteString
849encodeBulkOperations stream = collapsed where
709encodeBulkOperations stream = collapsed where
850 blobs = fmap encodeBulkOperation stream
710 blobs = fmap encodeBulkOperation stream
@@ -863,10 +723,6 @@ mkBulkStreamValue operation indexName mappingName docId =
863
723
864-- | 'encodeBulkOperation' is a convenience function for dumping a single 'BulkOperation'
724-- | 'encodeBulkOperation' is a convenience function for dumping a single 'BulkOperation'
865-- into an 'L.ByteString'
725-- into an 'L.ByteString'
866−--
867−-- >>> let bulkOp = BulkIndex testIndex testMapping (DocId "2") (toJSON (BulkTest "blah"))
868−-- >>> encodeBulkOperation bulkOp
869−-- "{\"index\":{\"_type\":\"tweet\",\"_id\":\"2\",\"_index\":\"twitter\"}}\n{\"name\":\"blah\"}"
870encodeBulkOperation :: BulkOperation -> L.ByteString
726encodeBulkOperation :: BulkOperation -> L.ByteString
871encodeBulkOperation (BulkIndex (IndexName indexName)
727encodeBulkOperation (BulkIndex (IndexName indexName)
872 (MappingName mappingName)
728 (MappingName mappingName)
@@ -896,8 +752,6 @@ encodeBulkOperation (BulkUpdate (IndexName indexName)
896-- | 'getDocument' is a straight-forward way to fetch a single document from
752-- | 'getDocument' is a straight-forward way to fetch a single document from
897-- Elasticsearch using a 'Server', 'IndexName', 'MappingName', and a 'DocId'.
753-- Elasticsearch using a 'Server', 'IndexName', 'MappingName', and a 'DocId'.
898-- The 'DocId' is the primary key for your Elasticsearch document.
754-- The 'DocId' is the primary key for your Elasticsearch document.
899−--
900−-- >>> yourDoc <- runBH' $ getDocument testIndex testMapping (DocId "1")
901getDocument :: MonadBH m => IndexName -> MappingName
755getDocument :: MonadBH m => IndexName -> MappingName
902 -> DocId -> m Reply
756 -> DocId -> m Reply
903getDocument (IndexName indexName)
757getDocument (IndexName indexName)
@@ -906,8 +760,6 @@ getDocument (IndexName indexName)
906
760
907-- | 'documentExists' enables you to check if a document exists. Returns 'Bool'
761-- | 'documentExists' enables you to check if a document exists. Returns 'Bool'
908-- in IO
762-- in IO
909−--
910−-- >>> exists <- runBH' $ documentExists testIndex testMapping Nothing (DocId "1")
911documentExists :: MonadBH m => IndexName -> MappingName
763documentExists :: MonadBH m => IndexName -> MappingName
912 -> Maybe DocumentParent -> DocId -> m Bool
764 -> Maybe DocumentParent -> DocId -> m Bool
913documentExists (IndexName indexName) (MappingName mappingName)
765documentExists (IndexName indexName) (MappingName mappingName)
@@ -924,30 +776,18 @@ dispatchSearch url search = post url' (Just (encode search))
924
776
925-- | 'searchAll', given a 'Search', will perform that search against all indexes
777-- | 'searchAll', given a 'Search', will perform that search against all indexes
926-- on an Elasticsearch server. Try to avoid doing this if it can be helped.
778-- on an Elasticsearch server. Try to avoid doing this if it can be helped.
927−--
928−-- >>> let query = TermQuery (Term "user" "bitemyapp") Nothing
929−-- >>> let search = mkSearch (Just query) Nothing
930−-- >>> reply <- runBH' $ searchAll search
931searchAll :: MonadBH m => Search -> m Reply
779searchAll :: MonadBH m => Search -> m Reply
932searchAll = bindM2 dispatchSearch url . return
780searchAll = bindM2 dispatchSearch url . return
933 where url = joinPath ["_search"]
781 where url = joinPath ["_search"]
934
782
935-- | 'searchByIndex', given a 'Search' and an 'IndexName', will perform that search
783-- | 'searchByIndex', given a 'Search' and an 'IndexName', will perform that search
936-- against all mappings within an index on an Elasticsearch server.
784-- against all mappings within an index on an Elasticsearch server.
937−--
938−-- >>> let query = TermQuery (Term "user" "bitemyapp") Nothing
939−-- >>> let search = mkSearch (Just query) Nothing
940−-- >>> reply <- runBH' $ searchByIndex testIndex search
941searchByIndex :: MonadBH m => IndexName -> Search -> m Reply
785searchByIndex :: MonadBH m => IndexName -> Search -> m Reply
942searchByIndex (IndexName indexName) = bindM2 dispatchSearch url . return
786searchByIndex (IndexName indexName) = bindM2 dispatchSearch url . return
943 where url = joinPath [indexName, "_search"]
787 where url = joinPath [indexName, "_search"]
944
788
945-- | 'searchByType', given a 'Search', 'IndexName', and 'MappingName', will perform that
789-- | 'searchByType', given a 'Search', 'IndexName', and 'MappingName', will perform that
946-- search against a specific mapping within an index on an Elasticsearch server.
790-- search against a specific mapping within an index on an Elasticsearch server.
947−--
948−-- >>> let query = TermQuery (Term "user" "bitemyapp") Nothing
949−-- >>> let search = mkSearch (Just query) Nothing
950−-- >>> reply <- runBH' $ searchByType testIndex testMapping search
951searchByType :: MonadBH m => IndexName -> MappingName -> Search
791searchByType :: MonadBH m => IndexName -> MappingName -> Search
952 -> m Reply
792 -> m Reply
953searchByType (IndexName indexName)
793searchByType (IndexName indexName)
@@ -1020,29 +860,16 @@ scanSearch indexName mappingName search = do
1020-- to Nothing in case you only care about your 'Query' and 'Filter'. Use record update
860-- to Nothing in case you only care about your 'Query' and 'Filter'. Use record update
1021-- syntax if you want to add things like aggregations or highlights while still using
861-- syntax if you want to add things like aggregations or highlights while still using
1022-- this helper function.
862-- this helper function.
1023−--
1024−-- >>> let query = TermQuery (Term "user" "bitemyapp") Nothing
1025−-- >>> mkSearch (Just query) Nothing
1026−-- Search {queryBody = Just (TermQuery (Term {termField = "user", termValue = "bitemyapp"}) Nothing), filterBody = Nothing, sortBody = Nothing, aggBody = Nothing, highlight = Nothing, trackSortScores = False, from = From 0, size = Size 10, searchType = SearchTypeQueryThenFetch, fields = Nothing, source = Nothing}
1027mkSearch :: Maybe Query -> Maybe Filter -> Search
863mkSearch :: Maybe Query -> Maybe Filter -> Search
1028mkSearch query filter = Search query filter Nothing Nothing Nothing False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing
864mkSearch query filter = Search query filter Nothing Nothing Nothing False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing
1029
865
1030-- | 'mkAggregateSearch' is a helper function that defaults everything in a 'Search' except for
866-- | 'mkAggregateSearch' is a helper function that defaults everything in a 'Search' except for
1031-- the 'Query' and the 'Aggregation'.
867-- the 'Query' and the 'Aggregation'.
1032−--
1033−-- >>> let terms = TermsAgg $ (mkTermsAggregation "user") { termCollectMode = Just BreadthFirst }
1034−-- >>> terms
1035−-- TermsAgg (TermsAggregation {term = Left "user", termInclude = Nothing, termExclude = Nothing, termOrder = Nothing, termMinDocCount = Nothing, termSize = Nothing, termShardSize = Nothing, termCollectMode = Just BreadthFirst, termExecutionHint = Nothing, termAggs = Nothing})
1036−-- >>> let myAggregation = mkAggregateSearch Nothing $ mkAggregations "users" terms
1037mkAggregateSearch :: Maybe Query -> Aggregations -> Search
868mkAggregateSearch :: Maybe Query -> Aggregations -> Search
1038mkAggregateSearch query mkSearchAggs = Search query Nothing Nothing (Just mkSearchAggs) Nothing False (From 0) (Size 0) SearchTypeQueryThenFetch Nothing Nothing
869mkAggregateSearch query mkSearchAggs = Search query Nothing Nothing (Just mkSearchAggs) Nothing False (From 0) (Size 0) SearchTypeQueryThenFetch Nothing Nothing
1039
870
1040-- | 'mkHighlightSearch' is a helper function that defaults everything in a 'Search' except for
871-- | 'mkHighlightSearch' is a helper function that defaults everything in a 'Search' except for
1041-- the 'Query' and the 'Aggregation'.
872-- the 'Query' and the 'Aggregation'.
1042−--
1043−-- >>> let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
1044−-- >>> let testHighlight = Highlights Nothing [FieldHighlight (FieldName "message") Nothing]
1045−-- >>> let search = mkHighlightSearch (Just query) testHighlight
1046mkHighlightSearch :: Maybe Query -> Highlights -> Search
873mkHighlightSearch :: Maybe Query -> Highlights -> Search
1047mkHighlightSearch query searchHighlights = Search query Nothing Nothing Nothing (Just searchHighlights) False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing
874mkHighlightSearch query searchHighlights = Search query Nothing Nothing Nothing (Just searchHighlights) False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing
1048
875
@@ -1050,13 +877,6 @@ mkHighlightSearch query searchHighlights = Search query Nothing Nothing Nothing
1050-- and size fields for the search. The from parameter defines the offset
877-- and size fields for the search. The from parameter defines the offset
1051-- from the first result you want to fetch. The size parameter allows you to
878-- from the first result you want to fetch. The size parameter allows you to
1052-- configure the maximum amount of hits to be returned.
879-- configure the maximum amount of hits to be returned.
1053−--
1054−-- >>> let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
1055−-- >>> let search = mkSearch (Just query) Nothing
1056−-- >>> search
1057−-- 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, matchQueryBoost = Nothing})), filterBody = Nothing, sortBody = Nothing, aggBody = Nothing, highlight = Nothing, trackSortScores = False, from = From 0, size = Size 10, searchType = SearchTypeQueryThenFetch, fields = Nothing, source = Nothing}
1058−-- >>> pageSearch (From 10) (Size 100) search
1059−-- 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, matchQueryBoost = Nothing})), filterBody = Nothing, sortBody = Nothing, aggBody = Nothing, highlight = Nothing, trackSortScores = False, from = From 10, size = Size 100, searchType = SearchTypeQueryThenFetch, fields = Nothing, source = Nothing}
1060pageSearch :: From -- ^ The result offset
880pageSearch :: From -- ^ The result offset
1061 -> Size -- ^ The number of results to return
881 -> Size -- ^ The number of results to return
1062 -> Search -- ^ The current seach
882 -> Search -- ^ The current seach
stack-7.10.yaml+4−2
@@ -1,13 +1,15 @@
1−flags: {}
1+flags:
2+ bloodhound-examples:
3+ werror: true
2packages:
4packages:
3- '.'
5- '.'
6+- './examples'
4extra-deps:
7extra-deps:
5- http-client-0.5.0
8- http-client-0.5.0
6- aeson-0.11.1.0
9- aeson-0.11.1.0
7- fail-4.9.0.0
10- fail-4.9.0.0
8- http-types-0.9
11- http-types-0.9
9- attoparsec-0.13.0.1
12- attoparsec-0.13.0.1
10−- doctest-0.10.1
11- quickcheck-properties-0.1
13- quickcheck-properties-0.1
12- semigroups-0.18.0.1
14- semigroups-0.18.0.1
13- uri-bytestring-0.1.9
15- uri-bytestring-0.1.9
stack-7.8.yaml+3−1
@@ -1,15 +1,17 @@
1flags:
1flags:
2 aeson:
2 aeson:
3 old-locale: true
3 old-locale: true
4+ bloodhound-examples:
5+ werror: true
4packages:
6packages:
5- '.'
7- '.'
8+- './examples'
6extra-deps:
9extra-deps:
7- aeson-0.11.1.0
10- aeson-0.11.1.0
8- fail-4.9.0.0
11- fail-4.9.0.0
9- http-types-0.9
12- http-types-0.9
10- http-client-0.5.0
13- http-client-0.5.0
11- attoparsec-0.13.0.1
14- attoparsec-0.13.0.1
12−- doctest-0.10.1
13- quickcheck-properties-0.1
15- quickcheck-properties-0.1
14- semigroups-0.18.0.1
16- semigroups-0.18.0.1
15- tagged-0.8.3
17- tagged-0.8.3
stack.yaml+4−1
@@ -1,6 +1,9 @@
1−flags: {}
1+flags:
2+ bloodhound-examples:
3+ werror: true
2packages:
4packages:
3- '.'
5- '.'
6+- './examples'
4extra-deps:
7extra-deps:
5- quickcheck-properties-0.1
8- quickcheck-properties-0.1
6- vector-0.12.0.0
9- vector-0.12.0.0
tests/V1/doctests.hsdeleted−4
@@ -1,4 +0,0 @@
1−import Test.DocTest
2−
3−main :: IO ()
4−main = doctest ["-i src", "Database.V1.Bloodhound"]
tests/V5/doctests.hsdeleted−4
@@ -1,4 +0,0 @@
1−import Test.DocTest
2−
3−main :: IO ()
4−main = doctest ["-i src", "Database.V5.Bloodhound"]