git.iko.soy/typeable/bloodhound
on master help sign in
Merge branch 'master' of github.com:bitemyapp/bloodhound
commitb1df6e5f91158a884fb4e0ad8f424b3f4b05325c authorChris Allen <cma@bitemyapp.com> dateTue 23 May 2017, 11:31 (9 years ago) parentdb34d720bf1cb7
30 files changed+8359−1079
.gitignore+4 .travis.yml+14−16 Makefile+18−2 README.md+14−834 bloodhound.cabal+29−33 changelog.md+34 examples/LICENSE+12 examples/README.md+3 examples/Setup.hs+2 examples/Tweet.hs+125 examples/package.yaml+38 src/Database/Bloodhound.hs−10 src/Database/V1/Bloodhound.hs+10 src/Database/V1/Bloodhound/Client.hs+917 src/Database/Bloodhound/Types.hs → src/Database/V1/Bloodhound/Types.hs+150−93 src/Database/Bloodhound/Types/Class.hs → src/Database/V1/Bloodhound/Types/Class.hs+1−1 src/Database/Bloodhound/Types/Internal.hs → src/Database/V1/Bloodhound/Types/Internal.hs+1−1 src/Database/V5/Bloodhound.hs+10 src/Database/Bloodhound/Client.hs → src/Database/V5/Bloodhound/Client.hs+87−68 src/Database/V5/Bloodhound/Types.hs+5057 src/Database/V5/Bloodhound/Types/Class.hs+17 src/Database/V5/Bloodhound/Types/Internal.hs+57 stack-7.10.yaml+7−4 stack-7.8.yaml+3−1 stack-8.0.yaml+12−7 stack-8.2.yaml+44 stack.yaml+13−1 tests/tests.hs → tests/V1/tests.hs+16−4 tests/V5/tests.hs+1664 tests/doctests.hs−4
.gitignore+4
@@ -13,3 +13,7 @@ bloodhound-*-docs.tar.gz
1313bloodhound.iml
1414.idea/
1515.stack-work/
16+.hg/00changelog.i
17+.hg
18+.hgignore
19+examples/bloodhound-examples.cabal
.travis.yml+14−16
@@ -4,30 +4,29 @@ addons:
44 apt:
55 packages:
66 - libgmp-dev
7− - openjdk-6-jre
7+ - oracle-java8-installer
88
99env:
10− # - GHCVER=7.6.3 ESVER=1.6.0 # Deprecated
11− # - GHCVER=7.8.3 ESVER=1.0.3 # Deprecated
12− # - GHCVER=7.8.3 ESVER=1.1.2 # Deprecated
13− # - GHCVER=7.8 ESVER=1.2.4 # deprecated
14− - GHCVER=7.8 ESVER=1.3.6
15− - GHCVER=7.8 ESVER=1.4.1
16− - GHCVER=7.10 ESVER=1.5.2
17− - GHCVER=7.10 ESVER=1.6.0
18− - GHCVER=7.10 ESVER=1.7.2
19− - GHCVER=8.0 ESVER=1.7.2
10+ global:
11+ - JAVA_HOME=/usr/lib/jvm/java-8-oracle
12+ matrix:
13+ - GHCVER=7.8 ESVER=1.3.6 STACK_YAML=stack-7.8.yaml ESFLAG=ES1 DLINK=https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch
14+ - GHCVER=7.8 ESVER=1.4.1 STACK_YAML=stack-7.8.yaml ESFLAG=ES1 DLINK=https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch
15+ - GHCVER=7.10 ESVER=1.5.2 STACK_YAML=stack-7.10.yaml ESFLAG=ES1 DLINK=https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch
16+ - GHCVER=7.10 ESVER=1.6.0 STACK_YAML=stack-7.10.yaml ESFLAG=ES1 DLINK=https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch
17+ - GHCVER=7.10 ESVER=1.7.2 STACK_YAML=stack-7.10.yaml ESFLAG=ES1 DLINK=https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch
18+ - GHCVER=8.0 ESVER=1.7.2 STACK_YAML=stack.yaml ESFLAG=ES1 DLINK=https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch
19+ - GHCVER=8.0 ESVER=5.0.2 STACK_YAML=stack.yaml ESFLAG=ES5 DLINK=https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch ES_JAVA_OPTS="-Xms500m -Xmx750m"
2020
2121install:
2222 # stack
23− - export STACK_YAML=stack-$GHCVER.yaml
2423 - mkdir -p ~/.local/bin
2524 - travis_retry curl -L https://github.com/commercialhaskell/stack/releases/download/v1.2.0/stack-1.2.0-linux-x86_64.tar.gz | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'
2625 - export PATH=~/.local/bin:$PATH
2726 - stack --version
2827
2928 # elasticsearch
30− - wget --no-check-certificate https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-$ESVER.tar.gz
29+ - wget --no-check-certificate $DLINK-$ESVER.tar.gz
3130 - tar xzf elasticsearch-$ESVER.tar.gz
3231 # set up a repo for snapshot testing. Required in ES >= 1.6
3332 - echo "path.repo = [\"/tmp\"]" >> ./elasticsearch-$ESVER/elasticsearch.yml
@@ -36,9 +35,8 @@ install:
3635script:
3736 - stack setup
3837 - stack update
39− - stack build
40− - stack test bloodhound:doctests
41− - stack test bloodhound:tests --test-arguments="--qc-max-success 2000"
38+ - stack build -j1
39+ - stack test bloodhound:tests --test-arguments="--qc-max-success 500" --flag bloodhound:$ESFLAG
4240
4341cache:
4442 directories:
Makefile+18−2
@@ -13,11 +13,27 @@ test: echo-warn
13137.8-test: echo-warn
1414 STACK_YAML="stack-7.8.yaml" stack test
1515
16+7.10-build:
17+ STACK_YAML="stack-7.10.yaml" stack build
18+
19+7.10-test: echo-warn
20+ STACK_YAML="stack-7.10.yaml" stack test
21+
22+7.10-test-ES1:
23+ STACK_YAML="stack-7.10.yaml" stack test bloodhound:tests --test-arguments="--qc-max-success 500" --flag bloodhound:ES1
24+
25+7.10-test-ES5:
26+ STACK_YAML="stack-7.10.yaml" stack test bloodhound:tests --test-arguments="--qc-max-success 500" --flag bloodhound:ES5
27+
16288.0-build:
1729 STACK_YAML="stack-8.0.yaml" stack build
1830
19−8.0-test: echo-warn
20− STACK_YAML="stack-8.0.yaml" stack test
31+8.2-build:
32+ STACK_YAML="stack-8.2.yaml" stack build
33+
34+module-touch:
35+ touch src/Database/V1/Bloodhound/Types.hs
36+ touch src/Database/V5/Bloodhound/Types.hs
2137
2238ghci:
2339 stack ghci
README.md+14−834
@@ -21,9 +21,19 @@ Endorsements
2121Version compatibility
2222---------------------
2323
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.
2536
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.
2737
2838Stability
2939---------
@@ -41,7 +51,7 @@ Steps to run the tests locally:
4151 3. In your local Bloodhound directory, run `stack setup && stack build`
4252 4. Start the desired version of ElasticSearch at `localhost:9200`, which should be the default.
4353 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`.
4555
4656
4757Hackage page and Haddock documentation
@@ -57,837 +67,7 @@ It's not using Bloodhound, but if you need an introduction to or overview of Ela
5767Examples
5868========
5969
60−Index Operations
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>
70+See the [examples](htts://github.com/bitemyapp/bloodhound/tree/master/examples) directory for example code.
89171
89272
89373Contributors
bloodhound.cabal+29−33
@@ -1,5 +1,5 @@
11name: bloodhound
2−version: 0.12.0.0
2+version: 0.14.0.0
33synopsis: ElasticSearch client library for Haskell
44description: ElasticSearch made awesome for Haskell hackers
55homepage: https://github.com/bitemyapp/bloodhound
@@ -16,23 +16,35 @@ extra-source-files:
1616 README.md
1717 changelog.md
1818
19−
2019source-repository head
2120 type: git
2221 location: https://github.com/bitemyapp/bloodhound.git
2322
23+Flag ES1
24+ Description: Run the test suite against an Elasticsearch 1.x server
25+ Default: True
26+
27+Flag ES5
28+ Description: Run the test suite against an Elasticsearch 5.x server
29+ Default: False
30+
2431library
25− ghc-options: -Wall
26− exposed-modules: Database.Bloodhound
27− Database.Bloodhound.Client
28− Database.Bloodhound.Types
29− Database.Bloodhound.Types.Class
30− Database.Bloodhound.Types.Internal
32+ ghc-options: -Wall
33+ exposed-modules: Database.V1.Bloodhound
34+ Database.V1.Bloodhound.Client
35+ Database.V1.Bloodhound.Types
36+ Database.V1.Bloodhound.Types.Class
37+ Database.V1.Bloodhound.Types.Internal
38+ Database.V5.Bloodhound
39+ Database.V5.Bloodhound.Client
40+ Database.V5.Bloodhound.Types
41+ Database.V5.Bloodhound.Types.Class
42+ Database.V5.Bloodhound.Types.Internal
3143 hs-source-dirs: src
3244 build-depends: base >= 4.3 && <5,
3345 bytestring >= 0.10.0 && <0.11,
3446 containers >= 0.5.0.0 && <0.6,
35− aeson >= 0.11.1 && <1.1,
47+ aeson >= 0.11.1 && <1.3,
3648 http-client >= 0.4.30 && <0.6,
3749 network-uri >= 2.6 && <2.7,
3850 semigroups >= 0.15 && <0.19,
@@ -41,7 +53,7 @@ library
4153 mtl >= 1.0 && <2.3,
4254 transformers >= 0.2 && <0.6,
4355 http-types >= 0.8 && <0.10,
44− vector >= 0.10.9 && <0.12,
56+ vector >= 0.10.9 && <0.13,
4557 scientific >= 0.3.0.0 && <0.4.0.0,
4658 exceptions,
4759 data-default-class,
@@ -55,14 +67,18 @@ test-suite tests
5567 ghc-options: -Wall -fno-warn-orphans
5668 type: exitcode-stdio-1.0
5769 main-is: tests.hs
58− hs-source-dirs: tests
70+ if flag(ES5)
71+ hs-source-dirs: tests/V5
72+ else
73+ hs-source-dirs: tests/V1
74+
5975 build-depends: base,
6076 bloodhound,
6177 bytestring,
6278 http-client,
6379 http-types,
6480 containers,
65− hspec >= 1.8 && <2.4,
81+ hspec >= 1.8,
6682 text,
6783 time,
6884 aeson,
@@ -76,26 +92,6 @@ test-suite tests
7692 errors,
7793 exceptions,
7894 temporary,
79− unix,
95+ unix-compat,
8096 network-uri
8197 default-language: Haskell2010
82−
83−test-suite doctests
84− ghc-options: -threaded -Wall
85− default-language: Haskell2010
86− type: exitcode-stdio-1.0
87− main-is: doctests.hs
88− hs-source-dirs: tests, src
89− if impl(ghc >= 7.8)
90− build-depends: base,
91− aeson,
92− bloodhound,
93− directory,
94− doctest >= 0.10.1,
95− filepath
96− else
97− buildable: False
98−
99−Source-Repository head
100− Type: git
101− Location: git://github.com/bitemyapp/bloodhound.git
changelog.md+34
@@ -1,3 +1,37 @@
1+0.14.0.0
2+===================
3+
4+- @bitemyapp
5+ - Aeson 1.2
6+
7+- @shmish111
8+ - #177 Added stats aggregation
9+
10+- @justinwhite
11+ - #176 Fixed typo
12+
13+- @WraithM
14+ - #175 Sorted version of getInitialScroll for V5 client
15+
16+0.13.0.0
17+===================
18+
19+- @michaelxavier
20+ - #174 doctests are bad
21+ - #171 Added Semigroup instances
22+ - #165 Aeson 1.1
23+
24+- @bermanjosh
25+ - #168 Sub-aggregation support
26+ - #167 Limit QuickCheck to 500 checks
27+
28+0.12.1.0
29+===================
30+* @michaelxavier
31+ - #162 Allow Aeson 1.1
32+* @bermanjosh
33+ - #159 Fix bug with tie breaker json instance for MultiMatchQuery
34+
1350.12.0.0
236===================
337This release contains several new APIs, documentation fixes, and some minor type updates, and steps towards ES 2.0 support. Thanks to all of our contributors!
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/Bloodhound.hsdeleted−10
@@ -1,10 +0,0 @@
1−module Database.Bloodhound
2− ( -- module Data.Aeson.Types
3− -- ,
4− module Database.Bloodhound.Client
5− , module Database.Bloodhound.Types
6− ) where
7−
8−-- import Data.Aeson.Types
9−import Database.Bloodhound.Client
10−import Database.Bloodhound.Types
src/Database/V1/Bloodhound.hsnew file+10
@@ -0,0 +1,10 @@
1+module Database.V1.Bloodhound
2+ ( -- module Data.Aeson.Types
3+ -- ,
4+ module Database.V1.Bloodhound.Client
5+ , module Database.V1.Bloodhound.Types
6+ ) where
7+
8+-- import Data.Aeson.Types
9+import Database.V1.Bloodhound.Client
10+import Database.V1.Bloodhound.Types
src/Database/V1/Bloodhound/Client.hsnew file+917
@@ -0,0 +1,917 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE RecordWildCards #-}
3+{-# LANGUAGE TupleSections #-}
4+
5+-------------------------------------------------------------------------------
6+-- |
7+-- Module : Database.Bloodhound.Client
8+-- Copyright : (C) 2014 Chris Allen
9+-- License : BSD-style (see the file LICENSE)
10+-- Maintainer : Chris Allen <cma@bitemyapp.com
11+-- Stability : provisional
12+-- Portability : OverloadedStrings
13+--
14+-- Client side functions for talking to Elasticsearch servers.
15+--
16+-------------------------------------------------------------------------------
17+
18+module Database.V1.Bloodhound.Client
19+ ( -- * Bloodhound client functions
20+ -- | The examples in this module assume the following code has been run.
21+ -- The :{ and :} will only work in GHCi. You'll only need the data types
22+ -- and typeclass instances for the functions that make use of them.
23+
24+ -- $setup
25+ withBH
26+ -- ** Indices
27+ , createIndex
28+ , deleteIndex
29+ , updateIndexSettings
30+ , getIndexSettings
31+ , optimizeIndex
32+ , indexExists
33+ , openIndex
34+ , closeIndex
35+ , listIndices
36+ , waitForYellowIndex
37+ -- *** Index Aliases
38+ , updateIndexAliases
39+ , getIndexAliases
40+ -- *** Index Templates
41+ , putTemplate
42+ , templateExists
43+ , deleteTemplate
44+ -- ** Mapping
45+ , putMapping
46+ , deleteMapping
47+ -- ** Documents
48+ , indexDocument
49+ , updateDocument
50+ , getDocument
51+ , documentExists
52+ , deleteDocument
53+ -- ** Searching
54+ , searchAll
55+ , searchByIndex
56+ , searchByType
57+ , scanSearch
58+ , getInitialScroll
59+ , advanceScroll
60+ , refreshIndex
61+ , mkSearch
62+ , mkAggregateSearch
63+ , mkHighlightSearch
64+ , bulk
65+ , pageSearch
66+ , mkShardCount
67+ , mkReplicaCount
68+ , getStatus
69+ -- ** Snapshot/Restore
70+ -- *** Snapshot Repos
71+ , getSnapshotRepos
72+ , updateSnapshotRepo
73+ , verifySnapshotRepo
74+ , deleteSnapshotRepo
75+ -- *** Snapshots
76+ , createSnapshot
77+ , getSnapshots
78+ , deleteSnapshot
79+ -- *** Restoring Snapshots
80+ , restoreSnapshot
81+ -- ** Nodes
82+ , getNodesInfo
83+ , getNodesStats
84+ -- ** Request Utilities
85+ , encodeBulkOperations
86+ , encodeBulkOperation
87+ -- * Authentication
88+ , basicAuthHook
89+ -- * Reply-handling tools
90+ , isVersionConflict
91+ , isSuccess
92+ , isCreated
93+ , parseEsResponse
94+ )
95+ where
96+
97+import qualified Blaze.ByteString.Builder as BB
98+import Control.Applicative as A
99+import Control.Monad
100+import Control.Monad.Catch
101+import Control.Monad.IO.Class
102+import Data.Aeson
103+import Data.ByteString.Lazy.Builder
104+import qualified Data.ByteString.Lazy.Char8 as L
105+import Data.Foldable (toList)
106+import qualified Data.HashMap.Strict as HM
107+import Data.Ix
108+import qualified Data.List as LS (filter, foldl')
109+import Data.List.NonEmpty (NonEmpty (..))
110+import Data.Maybe (catMaybes, fromMaybe, isJust)
111+import Data.Monoid
112+import Data.Text (Text)
113+import qualified Data.Text as T
114+import qualified Data.Text.Encoding as T
115+import Data.Time.Clock
116+import qualified Data.Vector as V
117+import Network.HTTP.Client
118+import qualified Network.HTTP.Types.Method as NHTM
119+import qualified Network.HTTP.Types.Status as NHTS
120+import qualified Network.HTTP.Types.URI as NHTU
121+import qualified Network.URI as URI
122+import Prelude hiding (filter, head)
123+
124+import Database.V1.Bloodhound.Types
125+
126+-- | 'mkShardCount' is a straight-forward smart constructor for 'ShardCount'
127+-- which rejects 'Int' values below 1 and above 1000.
128+mkShardCount :: Int -> Maybe ShardCount
129+mkShardCount n
130+ | n < 1 = Nothing
131+ | n > 1000 = Nothing
132+ | otherwise = Just (ShardCount n)
133+
134+-- | 'mkReplicaCount' is a straight-forward smart constructor for 'ReplicaCount'
135+-- which rejects 'Int' values below 0 and above 1000.
136+mkReplicaCount :: Int -> Maybe ReplicaCount
137+mkReplicaCount n
138+ | n < 0 = Nothing
139+ | n > 1000 = Nothing -- ...
140+ | otherwise = Just (ReplicaCount n)
141+
142+emptyBody :: L.ByteString
143+emptyBody = L.pack ""
144+
145+dispatch :: MonadBH m
146+ => Method
147+ -> Text
148+ -> Maybe L.ByteString
149+ -> m Reply
150+dispatch dMethod url body = do
151+ initReq <- liftIO $ parseUrl' url
152+ reqHook <- bhRequestHook A.<$> getBHEnv
153+ let reqBody = RequestBodyLBS $ fromMaybe emptyBody body
154+ req <- liftIO $ reqHook $ setRequestIgnoreStatus $ initReq { method = dMethod
155+ , requestBody = reqBody }
156+ mgr <- bhManager <$> getBHEnv
157+ liftIO $ httpLbs req mgr
158+
159+joinPath' :: [Text] -> Text
160+joinPath' = T.intercalate "/"
161+
162+joinPath :: MonadBH m => [Text] -> m Text
163+joinPath ps = do
164+ Server s <- bhServer <$> getBHEnv
165+ return $ joinPath' (s:ps)
166+
167+appendSearchTypeParam :: Text -> SearchType -> Text
168+appendSearchTypeParam originalUrl st = addQuery params originalUrl
169+ where stText = "search_type"
170+ params
171+ | st == SearchTypeDfsQueryThenFetch = [(stText, Just "dfs_query_then_fetch")]
172+ | st == SearchTypeCount = [(stText, Just "count")]
173+ | st == SearchTypeScan = [(stText, Just "scan"), ("scroll", Just "1m")]
174+ | st == SearchTypeQueryAndFetch = [(stText, Just "query_and_fetch")]
175+ | st == SearchTypeDfsQueryAndFetch = [(stText, Just "dfs_query_and_fetch")]
176+ -- used to catch 'SearchTypeQueryThenFetch', which is also the default
177+ | otherwise = [(stText, Just "query_then_fetch")]
178+
179+-- | Severely dumbed down query renderer. Assumes your data doesn't
180+-- need any encoding
181+addQuery :: [(Text, Maybe Text)] -> Text -> Text
182+addQuery q u = u <> rendered
183+ where
184+ rendered =
185+ T.decodeUtf8 $ BB.toByteString $ NHTU.renderQueryText prependQuestionMark q
186+ prependQuestionMark = True
187+
188+bindM2 :: (Applicative m, Monad m) => (a -> b -> m c) -> m a -> m b -> m c
189+bindM2 f ma mb = join (f <$> ma <*> mb)
190+
191+-- | Convenience function that sets up a manager and BHEnv and runs
192+-- the given set of bloodhound operations. Connections will be
193+-- pipelined automatically in accordance with the given manager
194+-- settings in IO. If you've got your own monad transformer stack, you
195+-- should use 'runBH' directly.
196+withBH :: ManagerSettings -> Server -> BH IO a -> IO a
197+withBH ms s f = do
198+ mgr <- newManager ms
199+ let env = mkBHEnv s mgr
200+ runBH env f
201+
202+-- Shortcut functions for HTTP methods
203+delete :: MonadBH m => Text -> m Reply
204+delete = flip (dispatch NHTM.methodDelete) Nothing
205+get :: MonadBH m => Text -> m Reply
206+get = flip (dispatch NHTM.methodGet) Nothing
207+head :: MonadBH m => Text -> m Reply
208+head = flip (dispatch NHTM.methodHead) Nothing
209+put :: MonadBH m => Text -> Maybe L.ByteString -> m Reply
210+put = dispatch NHTM.methodPut
211+post :: MonadBH m => Text -> Maybe L.ByteString -> m Reply
212+post = dispatch NHTM.methodPost
213+
214+-- indexDocument s ix name doc = put (root </> s </> ix </> name </> doc) (Just encode doc)
215+-- http://hackage.haskell.org/package/http-client-lens-0.1.0/docs/Network-HTTP-Client-Lens.html
216+-- https://github.com/supki/libjenkins/blob/master/src/Jenkins/Rest/Internal.hs
217+
218+-- | 'getStatus' fetches the 'Status' of a 'Server'
219+getStatus :: MonadBH m => m (Maybe Status)
220+getStatus = do
221+ response <- get =<< url
222+ return $ decode (responseBody response)
223+ where url = joinPath []
224+
225+-- | 'getSnapshotRepos' gets the definitions of a subset of the
226+-- defined snapshot repos.
227+getSnapshotRepos
228+ :: ( MonadBH m
229+ , MonadThrow m
230+ )
231+ => SnapshotRepoSelection
232+ -> m (Either EsError [GenericSnapshotRepo])
233+getSnapshotRepos sel = fmap (fmap unGSRs) . parseEsResponse =<< get =<< url
234+ where
235+ url = joinPath ["_snapshot", selectorSeg]
236+ selectorSeg = case sel of
237+ AllSnapshotRepos -> "_all"
238+ SnapshotRepoList (p :| ps) -> T.intercalate "," (renderPat <$> (p:ps))
239+ renderPat (RepoPattern t) = t
240+ renderPat (ExactRepo (SnapshotRepoName t)) = t
241+
242+
243+-- | Wrapper to extract the list of 'GenericSnapshotRepo' in the
244+-- format they're returned in
245+newtype GSRs = GSRs { unGSRs :: [GenericSnapshotRepo] }
246+
247+
248+instance FromJSON GSRs where
249+ parseJSON = withObject "Collection of GenericSnapshotRepo" parse
250+ where
251+ parse = fmap GSRs . mapM (uncurry go) . HM.toList
252+ go rawName = withObject "GenericSnapshotRepo" $ \o -> do
253+ GenericSnapshotRepo (SnapshotRepoName rawName) <$> o .: "type"
254+ <*> o .: "settings"
255+
256+
257+-- | Create or update a snapshot repo
258+updateSnapshotRepo
259+ :: ( MonadBH m
260+ , SnapshotRepo repo
261+ )
262+ => SnapshotRepoUpdateSettings
263+ -- ^ Use 'defaultSnapshotRepoUpdateSettings' if unsure
264+ -> repo
265+ -> m Reply
266+updateSnapshotRepo SnapshotRepoUpdateSettings {..} repo =
267+ bindM2 put url (return (Just body))
268+ where
269+ url = addQuery params <$> joinPath ["_snapshot", snapshotRepoName gSnapshotRepoName]
270+ params
271+ | repoUpdateVerify = []
272+ | otherwise = [("verify", Just "false")]
273+ body = encode $ object [ "type" .= gSnapshotRepoType
274+ , "settings" .= gSnapshotRepoSettings
275+ ]
276+ GenericSnapshotRepo {..} = toGSnapshotRepo repo
277+
278+
279+
280+-- | Verify if a snapshot repo is working. __NOTE:__ this API did not
281+-- make it into ElasticSearch until 1.4. If you use an older version,
282+-- you will get an error here.
283+verifySnapshotRepo
284+ :: ( MonadBH m
285+ , MonadThrow m
286+ )
287+ => SnapshotRepoName
288+ -> m (Either EsError SnapshotVerification)
289+verifySnapshotRepo (SnapshotRepoName n) =
290+ parseEsResponse =<< bindM2 post url (return Nothing)
291+ where
292+ url = joinPath ["_snapshot", n, "_verify"]
293+
294+
295+deleteSnapshotRepo :: MonadBH m => SnapshotRepoName -> m Reply
296+deleteSnapshotRepo (SnapshotRepoName n) = delete =<< url
297+ where
298+ url = joinPath ["_snapshot", n]
299+
300+
301+-- | Create and start a snapshot
302+createSnapshot
303+ :: (MonadBH m)
304+ => SnapshotRepoName
305+ -> SnapshotName
306+ -> SnapshotCreateSettings
307+ -> m Reply
308+createSnapshot (SnapshotRepoName repoName)
309+ (SnapshotName snapName)
310+ SnapshotCreateSettings {..} =
311+ bindM2 put url (return (Just body))
312+ where
313+ url = addQuery params <$> joinPath ["_snapshot", repoName, snapName]
314+ params = [("wait_for_completion", Just (boolQP snapWaitForCompletion))]
315+ body = encode $ object prs
316+ prs = catMaybes [ ("indices" .=) . indexSelectionName <$> snapIndices
317+ , Just ("ignore_unavailable" .= snapIgnoreUnavailable)
318+ , Just ("ignore_global_state" .= snapIncludeGlobalState)
319+ , Just ("partial" .= snapPartial)
320+ ]
321+
322+
323+indexSelectionName :: IndexSelection -> Text
324+indexSelectionName AllIndexes = "_all"
325+indexSelectionName (IndexList (i :| is)) = T.intercalate "," (renderIndex <$> (i:is))
326+ where
327+ renderIndex (IndexName n) = n
328+
329+
330+-- | Get info about known snapshots given a pattern and repo name.
331+getSnapshots
332+ :: ( MonadBH m
333+ , MonadThrow m
334+ )
335+ => SnapshotRepoName
336+ -> SnapshotSelection
337+ -> m (Either EsError [SnapshotInfo])
338+getSnapshots (SnapshotRepoName repoName) sel =
339+ fmap (fmap unSIs) . parseEsResponse =<< get =<< url
340+ where
341+ url = joinPath ["_snapshot", repoName, snapPath]
342+ snapPath = case sel of
343+ AllSnapshots -> "_all"
344+ SnapshotList (s :| ss) -> T.intercalate "," (renderPath <$> (s:ss))
345+ renderPath (SnapPattern t) = t
346+ renderPath (ExactSnap (SnapshotName t)) = t
347+
348+
349+newtype SIs = SIs { unSIs :: [SnapshotInfo] }
350+
351+
352+instance FromJSON SIs where
353+ parseJSON = withObject "Collection of SnapshotInfo" parse
354+ where
355+ parse o = SIs <$> o .: "snapshots"
356+
357+
358+-- | Delete a snapshot. Cancels if it is running.
359+deleteSnapshot :: MonadBH m => SnapshotRepoName -> SnapshotName -> m Reply
360+deleteSnapshot (SnapshotRepoName repoName) (SnapshotName snapName) =
361+ delete =<< url
362+ where
363+ url = joinPath ["_snapshot", repoName, snapName]
364+
365+
366+-- | Restore a snapshot to the cluster See
367+-- <https://www.elastic.co/guide/en/elasticsearch/reference/1.7/modules-snapshots.html#_restore>
368+-- for more details.
369+restoreSnapshot
370+ :: MonadBH m
371+ => SnapshotRepoName
372+ -> SnapshotName
373+ -> SnapshotRestoreSettings
374+ -- ^ Start with 'defaultSnapshotRestoreSettings' and customize
375+ -- from there for reasonable defaults.
376+ -> m Reply
377+restoreSnapshot (SnapshotRepoName repoName)
378+ (SnapshotName snapName)
379+ SnapshotRestoreSettings {..} = bindM2 post url (return (Just body))
380+ where
381+ url = addQuery params <$> joinPath ["_snapshot", repoName, snapName, "_restore"]
382+ params = [("wait_for_completion", Just (boolQP snapRestoreWaitForCompletion))]
383+ body = encode (object prs)
384+
385+
386+ prs = catMaybes [ ("indices" .=) . indexSelectionName <$> snapRestoreIndices
387+ , Just ("ignore_unavailable" .= snapRestoreIgnoreUnavailable)
388+ , Just ("include_global_state" .= snapRestoreIncludeGlobalState)
389+ , ("rename_pattern" .=) <$> snapRestoreRenamePattern
390+ , ("rename_replacement" .=) . renderTokens <$> snapRestoreRenameReplacement
391+ , Just ("include_aliases" .= snapRestoreIncludeAliases)
392+ , ("index_settings" .= ) <$> snapRestoreIndexSettingsOverrides
393+ , ("ignore_index_settings" .= ) <$> snapRestoreIgnoreIndexSettings
394+ ]
395+ renderTokens (t :| ts) = mconcat (renderToken <$> (t:ts))
396+ renderToken (RRTLit t) = t
397+ renderToken RRSubWholeMatch = "$0"
398+ renderToken (RRSubGroup g) = T.pack (show (rrGroupRefNum g))
399+
400+
401+getNodesInfo
402+ :: ( MonadBH m
403+ , MonadThrow m
404+ )
405+ => NodeSelection
406+ -> m (Either EsError NodesInfo)
407+getNodesInfo sel = parseEsResponse =<< get =<< url
408+ where
409+ url = joinPath ["_nodes", selectionSeg]
410+ selectionSeg = case sel of
411+ LocalNode -> "_local"
412+ NodeList (l :| ls) -> T.intercalate "," (selToSeg <$> (l:ls))
413+ AllNodes -> "_all"
414+ selToSeg (NodeByName (NodeName n)) = n
415+ selToSeg (NodeByFullNodeId (FullNodeId i)) = i
416+ selToSeg (NodeByHost (Server s)) = s
417+ selToSeg (NodeByAttribute (NodeAttrName a) v) = a <> ":" <> v
418+
419+getNodesStats
420+ :: ( MonadBH m
421+ , MonadThrow m
422+ )
423+ => NodeSelection
424+ -> m (Either EsError NodesStats)
425+getNodesStats sel = parseEsResponse =<< get =<< url
426+ where
427+ url = joinPath ["_nodes", selectionSeg, "stats"]
428+ selectionSeg = case sel of
429+ LocalNode -> "_local"
430+ NodeList (l :| ls) -> T.intercalate "," (selToSeg <$> (l:ls))
431+ AllNodes -> "_all"
432+ selToSeg (NodeByName (NodeName n)) = n
433+ selToSeg (NodeByFullNodeId (FullNodeId i)) = i
434+ selToSeg (NodeByHost (Server s)) = s
435+ selToSeg (NodeByAttribute (NodeAttrName a) v) = a <> ":" <> v
436+
437+-- | 'createIndex' will create an index given a 'Server', 'IndexSettings', and an 'IndexName'.
438+createIndex :: MonadBH m => IndexSettings -> IndexName -> m Reply
439+createIndex indexSettings (IndexName indexName) =
440+ bindM2 put url (return body)
441+ where url = joinPath [indexName]
442+ body = Just $ encode indexSettings
443+
444+
445+-- | 'deleteIndex' will delete an index given a 'Server', and an 'IndexName'.
446+deleteIndex :: MonadBH m => IndexName -> m Reply
447+deleteIndex (IndexName indexName) =
448+ delete =<< joinPath [indexName]
449+
450+-- | 'updateIndexSettings' will apply a non-empty list of setting updates to an index
451+updateIndexSettings :: MonadBH m => NonEmpty UpdatableIndexSetting -> IndexName -> m Reply
452+updateIndexSettings updates (IndexName indexName) =
453+ bindM2 put url (return body)
454+ where url = joinPath [indexName, "_settings"]
455+ body = Just (encode jsonBody)
456+ jsonBody = Object (deepMerge [u | Object u <- toJSON <$> toList updates])
457+
458+
459+getIndexSettings :: (MonadBH m, MonadThrow m) => IndexName
460+ -> m (Either EsError IndexSettingsSummary)
461+getIndexSettings (IndexName indexName) = do
462+ parseEsResponse =<< get =<< url
463+ where url = joinPath [indexName, "_settings"]
464+
465+
466+-- | 'optimizeIndex' will optimize a single index, list of indexes or
467+-- all indexes. Note that this call will block until finishing but
468+-- will continue even if the request times out. Concurrent requests to
469+-- optimize an index while another is performing will block until the
470+-- previous one finishes. For more information see
471+-- <https://www.elastic.co/guide/en/elasticsearch/reference/1.7/indices-optimize.html>. Nothing
472+-- worthwhile comes back in the reply body, so matching on the status
473+-- should suffice.
474+--
475+-- 'optimizeIndex' with a maxNumSegments of 1 and onlyExpungeDeletes
476+-- to True is the main way to release disk space back to the OS being
477+-- held by deleted documents.
478+--
479+-- Note that this API was deprecated in ElasticSearch 2.1 for the
480+-- almost completely identical forcemerge API. Adding support to that
481+-- API would be trivial but due to the significant breaking changes,
482+-- this library cannot currently be used with >= 2.0, so that feature was omitted.
483+optimizeIndex :: MonadBH m => IndexSelection -> IndexOptimizationSettings -> m Reply
484+optimizeIndex ixs IndexOptimizationSettings {..} =
485+ bindM2 post url (return body)
486+ where url = addQuery params <$> joinPath [indexName, "_optimize"]
487+ params = catMaybes [ ("max_num_segments",) . Just . showText <$> maxNumSegments
488+ , Just ("only_expunge_deletes", Just (boolQP onlyExpungeDeletes))
489+ , Just ("flush", Just (boolQP flushAfterOptimize))
490+ ]
491+ indexName = indexSelectionName ixs
492+ body = Nothing
493+
494+
495+deepMerge :: [Object] -> Object
496+deepMerge = LS.foldl' go mempty
497+ where go acc = LS.foldl' go' acc . HM.toList
498+ go' acc (k, v) = HM.insertWith merge k v acc
499+ merge (Object a) (Object b) = Object (deepMerge [a, b])
500+ merge _ b = b
501+
502+
503+statusCodeIs :: (Int, Int) -> Reply -> Bool
504+statusCodeIs r resp = inRange r $ NHTS.statusCode (responseStatus resp)
505+
506+respIsTwoHunna :: Reply -> Bool
507+respIsTwoHunna = statusCodeIs (200, 299)
508+
509+existentialQuery :: MonadBH m => Text -> m (Reply, Bool)
510+existentialQuery url = do
511+ reply <- head url
512+ return (reply, respIsTwoHunna reply)
513+
514+
515+-- | Tries to parse a response body as the expected type @a@ and
516+-- failing that tries to parse it as an EsError. All well-formed, JSON
517+-- responses from elasticsearch should fall into these two
518+-- categories. If they don't, a 'EsProtocolException' will be
519+-- thrown. If you encounter this, please report the full body it
520+-- reports along with your ElasticSearch verison.
521+parseEsResponse :: (MonadThrow m, FromJSON a) => Reply
522+ -> m (Either EsError a)
523+parseEsResponse reply
524+ | respIsTwoHunna reply = case eitherDecode body of
525+ Right a -> return (Right a)
526+ Left _ -> tryParseError
527+ | otherwise = tryParseError
528+ where body = responseBody reply
529+ tryParseError = case eitherDecode body of
530+ Right e -> return (Left e)
531+ -- this case should not be possible
532+ Left _ -> explode
533+ explode = throwM (EsProtocolException body)
534+
535+-- | 'indexExists' enables you to check if an index exists. Returns 'Bool'
536+-- in IO
537+indexExists :: MonadBH m => IndexName -> m Bool
538+indexExists (IndexName indexName) = do
539+ (_, exists) <- existentialQuery =<< joinPath [indexName]
540+ return exists
541+
542+-- | 'refreshIndex' will force a refresh on an index. You must
543+-- do this if you want to read what you wrote.
544+refreshIndex :: MonadBH m => IndexName -> m Reply
545+refreshIndex (IndexName indexName) =
546+ bindM2 post url (return Nothing)
547+ where url = joinPath [indexName, "_refresh"]
548+
549+-- | Block until the index becomes available for indexing
550+-- documents. This is useful for integration tests in which
551+-- indices are rapidly created and deleted.
552+waitForYellowIndex :: MonadBH m => IndexName -> m Reply
553+waitForYellowIndex (IndexName indexName) = get =<< url
554+ where url = addQuery q <$> joinPath ["_cluster","health",indexName]
555+ q = [("wait_for_status",Just "yellow"),("timeout",Just "10s")]
556+
557+stringifyOCIndex :: OpenCloseIndex -> Text
558+stringifyOCIndex oci = case oci of
559+ OpenIndex -> "_open"
560+ CloseIndex -> "_close"
561+
562+openOrCloseIndexes :: MonadBH m => OpenCloseIndex -> IndexName -> m Reply
563+openOrCloseIndexes oci (IndexName indexName) =
564+ bindM2 post url (return Nothing)
565+ where ociString = stringifyOCIndex oci
566+ url = joinPath [indexName, ociString]
567+
568+-- | 'openIndex' opens an index given a 'Server' and an 'IndexName'. Explained in further detail at
569+-- <http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-open-close.html>
570+openIndex :: MonadBH m => IndexName -> m Reply
571+openIndex = openOrCloseIndexes OpenIndex
572+
573+-- | 'closeIndex' closes an index given a 'Server' and an 'IndexName'. Explained in further detail at
574+-- <http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-open-close.html>
575+closeIndex :: MonadBH m => IndexName -> m Reply
576+closeIndex = openOrCloseIndexes CloseIndex
577+
578+-- | 'listIndices' returns a list of all index names on a given 'Server'
579+listIndices :: (MonadThrow m, MonadBH m) => m [IndexName]
580+listIndices =
581+ parse . responseBody =<< get =<< url
582+ where
583+ url = joinPath ["_cat/indices?v"]
584+ -- parses the tabular format the indices api provides
585+ parse body = case T.lines (T.decodeUtf8 (L.toStrict body)) of
586+ (hdr:rows) -> let ks = T.words hdr
587+ keyedRows = [ HM.fromList (zip ks (T.words row)) | row <- rows ]
588+ names = catMaybes (HM.lookup "index" <$> keyedRows)
589+ in return (IndexName <$> names)
590+ [] -> throwM (EsProtocolException body)
591+
592+-- | 'updateIndexAliases' updates the server's index alias
593+-- table. Operations are atomic. Explained in further detail at
594+-- <https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html>
595+updateIndexAliases :: MonadBH m => NonEmpty IndexAliasAction -> m Reply
596+updateIndexAliases actions = bindM2 post url (return body)
597+ where url = joinPath ["_aliases"]
598+ body = Just (encode bodyJSON)
599+ bodyJSON = object [ "actions" .= toList actions]
600+
601+-- | Get all aliases configured on the server.
602+getIndexAliases :: (MonadBH m, MonadThrow m)
603+ => m (Either EsError IndexAliasesSummary)
604+getIndexAliases = parseEsResponse =<< get =<< url
605+ where url = joinPath ["_aliases"]
606+
607+-- | 'putTemplate' creates a template given an 'IndexTemplate' and a 'TemplateName'.
608+-- Explained in further detail at
609+-- <https://www.elastic.co/guide/en/elasticsearch/reference/1.7/indices-templates.html>
610+putTemplate :: MonadBH m => IndexTemplate -> TemplateName -> m Reply
611+putTemplate indexTemplate (TemplateName templateName) =
612+ bindM2 put url (return body)
613+ where url = joinPath ["_template", templateName]
614+ body = Just $ encode indexTemplate
615+
616+-- | 'templateExists' checks to see if a template exists.
617+templateExists :: MonadBH m => TemplateName -> m Bool
618+templateExists (TemplateName templateName) = do
619+ (_, exists) <- existentialQuery =<< joinPath ["_template", templateName]
620+ return exists
621+
622+-- | 'deleteTemplate' is an HTTP DELETE and deletes a template.
623+deleteTemplate :: MonadBH m => TemplateName -> m Reply
624+deleteTemplate (TemplateName templateName) =
625+ delete =<< joinPath ["_template", templateName]
626+
627+-- | 'putMapping' is an HTTP PUT and has upsert semantics. Mappings are schemas
628+-- for documents in indexes.
629+putMapping :: (MonadBH m, ToJSON a) => IndexName
630+ -> MappingName -> a -> m Reply
631+putMapping (IndexName indexName) (MappingName mappingName) mapping =
632+ bindM2 put url (return body)
633+ where url = joinPath [indexName, "_mapping", mappingName]
634+ -- "_mapping" and mappingName above were originally transposed
635+ -- erroneously. The correct API call is: "/INDEX/_mapping/MAPPING_NAME"
636+ body = Just $ encode mapping
637+
638+-- | 'deleteMapping' is an HTTP DELETE and deletes a mapping for a given index.
639+-- Mappings are schemas for documents in indexes.
640+deleteMapping :: MonadBH m => IndexName -> MappingName -> m Reply
641+deleteMapping (IndexName indexName)
642+ (MappingName mappingName) =
643+ -- "_mapping" and mappingName below were originally transposed
644+ -- erroneously. The correct API call is: "/INDEX/_mapping/MAPPING_NAME"
645+ delete =<< joinPath [indexName, "_mapping", mappingName]
646+
647+versionCtlParams :: IndexDocumentSettings -> [(Text, Maybe Text)]
648+versionCtlParams cfg =
649+ case idsVersionControl cfg of
650+ NoVersionControl -> []
651+ InternalVersion v -> versionParams v "internal"
652+ ExternalGT (ExternalDocVersion v) -> versionParams v "external_gt"
653+ ExternalGTE (ExternalDocVersion v) -> versionParams v "external_gte"
654+ ForceVersion (ExternalDocVersion v) -> versionParams v "force"
655+ where
656+ vt = showText . docVersionNumber
657+ versionParams v t = [ ("version", Just $ vt v)
658+ , ("version_type", Just t)
659+ ]
660+
661+-- | 'indexDocument' is the primary way to save a single document in
662+-- Elasticsearch. The document itself is simply something we can
663+-- convert into a JSON 'Value'. The 'DocId' will function as the
664+-- primary key for the document.
665+indexDocument :: (ToJSON doc, MonadBH m) => IndexName -> MappingName
666+ -> IndexDocumentSettings -> doc -> DocId -> m Reply
667+indexDocument (IndexName indexName)
668+ (MappingName mappingName) cfg document (DocId docId) =
669+ bindM2 put url (return body)
670+ where url = addQuery params <$> joinPath [indexName, mappingName, docId]
671+ parentParams = case idsParent cfg of
672+ Nothing -> []
673+ Just (DocumentParent (DocId p)) -> [ ("parent", Just p) ]
674+ params = versionCtlParams cfg ++ parentParams
675+ body = Just (encode document)
676+
677+-- | 'updateDocument' provides a way to perform an partial update of a
678+-- an already indexed document.
679+updateDocument :: (ToJSON patch, MonadBH m) => IndexName -> MappingName
680+ -> IndexDocumentSettings -> patch -> DocId -> m Reply
681+updateDocument (IndexName indexName)
682+ (MappingName mappingName) cfg patch (DocId docId) =
683+ bindM2 post url (return body)
684+ where url = addQuery (versionCtlParams cfg) <$>
685+ joinPath [indexName, mappingName, docId, "_update"]
686+ body = Just (encode $ object ["doc" .= toJSON patch])
687+
688+-- | 'deleteDocument' is the primary way to delete a single document.
689+deleteDocument :: MonadBH m => IndexName -> MappingName
690+ -> DocId -> m Reply
691+deleteDocument (IndexName indexName)
692+ (MappingName mappingName) (DocId docId) =
693+ delete =<< joinPath [indexName, mappingName, docId]
694+
695+-- | 'bulk' uses
696+-- <http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-bulk.html Elasticsearch's bulk API>
697+-- to perform bulk operations. The 'BulkOperation' data type encodes the
698+-- index\/update\/delete\/create operations. You pass a 'V.Vector' of 'BulkOperation's
699+-- and a 'Server' to 'bulk' in order to send those operations up to your Elasticsearch
700+-- server to be performed. I changed from [BulkOperation] to a Vector due to memory overhead.
701+bulk :: MonadBH m => V.Vector BulkOperation -> m Reply
702+bulk bulkOps = bindM2 post url (return body)
703+ where url = joinPath ["_bulk"]
704+ body = Just $ encodeBulkOperations bulkOps
705+
706+-- | 'encodeBulkOperations' is a convenience function for dumping a vector of 'BulkOperation'
707+-- into an 'L.ByteString'
708+encodeBulkOperations :: V.Vector BulkOperation -> L.ByteString
709+encodeBulkOperations stream = collapsed where
710+ blobs = fmap encodeBulkOperation stream
711+ mashedTaters = mash (mempty :: Builder) blobs
712+ collapsed = toLazyByteString $ mappend mashedTaters (byteString "\n")
713+
714+mash :: Builder -> V.Vector L.ByteString -> Builder
715+mash = V.foldl' (\b x -> b `mappend` (byteString "\n") `mappend` (lazyByteString x))
716+
717+mkBulkStreamValue :: Text -> Text -> Text -> Text -> Value
718+mkBulkStreamValue operation indexName mappingName docId =
719+ object [operation .=
720+ object [ "_index" .= indexName
721+ , "_type" .= mappingName
722+ , "_id" .= docId]]
723+
724+-- | 'encodeBulkOperation' is a convenience function for dumping a single 'BulkOperation'
725+-- into an 'L.ByteString'
726+encodeBulkOperation :: BulkOperation -> L.ByteString
727+encodeBulkOperation (BulkIndex (IndexName indexName)
728+ (MappingName mappingName)
729+ (DocId docId) value) = blob
730+ where metadata = mkBulkStreamValue "index" indexName mappingName docId
731+ blob = encode metadata `mappend` "\n" `mappend` encode value
732+
733+encodeBulkOperation (BulkCreate (IndexName indexName)
734+ (MappingName mappingName)
735+ (DocId docId) value) = blob
736+ where metadata = mkBulkStreamValue "create" indexName mappingName docId
737+ blob = encode metadata `mappend` "\n" `mappend` encode value
738+
739+encodeBulkOperation (BulkDelete (IndexName indexName)
740+ (MappingName mappingName)
741+ (DocId docId)) = blob
742+ where metadata = mkBulkStreamValue "delete" indexName mappingName docId
743+ blob = encode metadata
744+
745+encodeBulkOperation (BulkUpdate (IndexName indexName)
746+ (MappingName mappingName)
747+ (DocId docId) value) = blob
748+ where metadata = mkBulkStreamValue "update" indexName mappingName docId
749+ doc = object ["doc" .= value]
750+ blob = encode metadata `mappend` "\n" `mappend` encode doc
751+
752+-- | 'getDocument' is a straight-forward way to fetch a single document from
753+-- Elasticsearch using a 'Server', 'IndexName', 'MappingName', and a 'DocId'.
754+-- The 'DocId' is the primary key for your Elasticsearch document.
755+getDocument :: MonadBH m => IndexName -> MappingName
756+ -> DocId -> m Reply
757+getDocument (IndexName indexName)
758+ (MappingName mappingName) (DocId docId) =
759+ get =<< joinPath [indexName, mappingName, docId]
760+
761+-- | 'documentExists' enables you to check if a document exists. Returns 'Bool'
762+-- in IO
763+documentExists :: MonadBH m => IndexName -> MappingName
764+ -> Maybe DocumentParent -> DocId -> m Bool
765+documentExists (IndexName indexName) (MappingName mappingName)
766+ parent (DocId docId) = do
767+ (_, exists) <- existentialQuery =<< url
768+ return exists
769+ where url = addQuery params <$> joinPath [indexName, mappingName, docId]
770+ parentParam = fmap (\(DocumentParent (DocId p)) -> p) parent
771+ params = LS.filter (\(_, v) -> isJust v) [("parent", parentParam)]
772+
773+dispatchSearch :: MonadBH m => Text -> Search -> m Reply
774+dispatchSearch url search = post url' (Just (encode search))
775+ where url' = appendSearchTypeParam url (searchType search)
776+
777+-- | 'searchAll', given a 'Search', will perform that search against all indexes
778+-- on an Elasticsearch server. Try to avoid doing this if it can be helped.
779+searchAll :: MonadBH m => Search -> m Reply
780+searchAll = bindM2 dispatchSearch url . return
781+ where url = joinPath ["_search"]
782+
783+-- | 'searchByIndex', given a 'Search' and an 'IndexName', will perform that search
784+-- against all mappings within an index on an Elasticsearch server.
785+searchByIndex :: MonadBH m => IndexName -> Search -> m Reply
786+searchByIndex (IndexName indexName) = bindM2 dispatchSearch url . return
787+ where url = joinPath [indexName, "_search"]
788+
789+-- | 'searchByType', given a 'Search', 'IndexName', and 'MappingName', will perform that
790+-- search against a specific mapping within an index on an Elasticsearch server.
791+searchByType :: MonadBH m => IndexName -> MappingName -> Search
792+ -> m Reply
793+searchByType (IndexName indexName)
794+ (MappingName mappingName) = bindM2 dispatchSearch url . return
795+ where url = joinPath [indexName, mappingName, "_search"]
796+
797+-- | For a given search, request a scroll for efficient streaming of
798+-- search results. Note that the search is put into 'SearchTypeScan'
799+-- mode and thus results will not be sorted. Combine this with
800+-- 'advanceScroll' to efficiently stream through the full result set
801+getInitialScroll :: MonadBH m => IndexName -> MappingName -> Search -> m (Maybe ScrollId)
802+getInitialScroll (IndexName indexName) (MappingName mappingName) search = do
803+ let url = joinPath [indexName, mappingName, "_search"]
804+ search' = search { searchType = SearchTypeScan }
805+ resp' <- bindM2 dispatchSearch url (return search')
806+ let msr = decode' $ responseBody resp' :: Maybe (SearchResult ())
807+ msid = maybe Nothing scrollId msr
808+ return msid
809+
810+scroll' :: (FromJSON a, MonadBH m, MonadThrow m) => Maybe ScrollId -> m ([Hit a], Maybe ScrollId)
811+scroll' Nothing = return ([], Nothing)
812+scroll' (Just sid) = do
813+ res <- advanceScroll sid 60
814+ case res of
815+ Right SearchResult {..} -> return (hits searchHits, scrollId)
816+ Left _ -> return ([], Nothing)
817+
818+-- | Use the given scroll to fetch the next page of documents. If there are no
819+-- further pages, 'SearchResult.searchHits.hits' will be '[]'.
820+advanceScroll
821+ :: ( FromJSON a
822+ , MonadBH m
823+ , MonadThrow m
824+ )
825+ => ScrollId
826+ -> NominalDiffTime
827+ -- ^ How long should the snapshot of data be kept around? This timeout is updated every time 'advanceScroll' is used, so don't feel the need to set it to the entire duration of your search processing. Note that durations < 1s will be rounded up. Also note that 'NominalDiffTime' is an instance of Num so literals like 60 will be interpreted as seconds. 60s is a reasonable default.
828+ -> m (Either EsError (SearchResult a))
829+advanceScroll (ScrollId sid) scroll = do
830+ url <- joinPath ["_search/scroll?scroll=" <> scrollTime]
831+ parseEsResponse =<< post url (Just . L.fromStrict $ T.encodeUtf8 sid)
832+ where scrollTime = showText secs <> "s"
833+ secs :: Integer
834+ secs = round scroll
835+
836+simpleAccumulator :: (FromJSON a, MonadBH m, MonadThrow m) => [Hit a] -> ([Hit a], Maybe ScrollId) -> m ([Hit a], Maybe ScrollId)
837+simpleAccumulator oldHits (newHits, Nothing) = return (oldHits ++ newHits, Nothing)
838+simpleAccumulator oldHits ([], _) = return (oldHits, Nothing)
839+simpleAccumulator oldHits (newHits, msid) = do
840+ (newHits', msid') <- scroll' msid
841+ simpleAccumulator (oldHits ++ newHits) (newHits', msid')
842+
843+-- | 'scanSearch' uses the 'scan&scroll' API of elastic,
844+-- for a given 'IndexName' and 'MappingName'. Note that this will
845+-- consume the entire search result set and will be doing O(n) list
846+-- appends so this may not be suitable for large result sets. In that
847+-- case, 'getInitialScroll' and 'advanceScroll' are good low level
848+-- tools. You should be able to hook them up trivially to conduit,
849+-- pipes, or your favorite streaming IO abstraction of choice. Note
850+-- that ordering on the search would destroy performance and thus is
851+-- ignored.
852+scanSearch :: (FromJSON a, MonadBH m, MonadThrow m) => IndexName -> MappingName -> Search -> m [Hit a]
853+scanSearch indexName mappingName search = do
854+ msid <- getInitialScroll indexName mappingName search
855+ (hits, msid') <- scroll' msid
856+ (totalHits, _) <- simpleAccumulator [] (hits, msid')
857+ return totalHits
858+
859+-- | 'mkSearch' is a helper function for defaulting additional fields of a 'Search'
860+-- to Nothing in case you only care about your 'Query' and 'Filter'. Use record update
861+-- syntax if you want to add things like aggregations or highlights while still using
862+-- this helper function.
863+mkSearch :: Maybe Query -> Maybe Filter -> Search
864+mkSearch query filter = Search query filter Nothing Nothing Nothing False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing
865+
866+-- | 'mkAggregateSearch' is a helper function that defaults everything in a 'Search' except for
867+-- the 'Query' and the 'Aggregation'.
868+mkAggregateSearch :: Maybe Query -> Aggregations -> Search
869+mkAggregateSearch query mkSearchAggs = Search query Nothing Nothing (Just mkSearchAggs) Nothing False (From 0) (Size 0) SearchTypeQueryThenFetch Nothing Nothing
870+
871+-- | 'mkHighlightSearch' is a helper function that defaults everything in a 'Search' except for
872+-- the 'Query' and the 'Aggregation'.
873+mkHighlightSearch :: Maybe Query -> Highlights -> Search
874+mkHighlightSearch query searchHighlights = Search query Nothing Nothing Nothing (Just searchHighlights) False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing
875+
876+-- | 'pageSearch' is a helper function that takes a search and assigns the from
877+-- and size fields for the search. The from parameter defines the offset
878+-- from the first result you want to fetch. The size parameter allows you to
879+-- configure the maximum amount of hits to be returned.
880+pageSearch :: From -- ^ The result offset
881+ -> Size -- ^ The number of results to return
882+ -> Search -- ^ The current seach
883+ -> Search -- ^ The paged search
884+pageSearch resultOffset pageSize search = search { from = resultOffset, size = pageSize }
885+
886+parseUrl' :: MonadThrow m => Text -> m Request
887+parseUrl' t = parseRequest (URI.escapeURIString URI.isAllowedInURI (T.unpack t))
888+
889+-- | Was there an optimistic concurrency control conflict when
890+-- indexing a document?
891+isVersionConflict :: Reply -> Bool
892+isVersionConflict = statusCheck (== 409)
893+
894+isSuccess :: Reply -> Bool
895+isSuccess = statusCheck (inRange (200, 299))
896+
897+isCreated :: Reply -> Bool
898+isCreated = statusCheck (== 201)
899+
900+statusCheck :: (Int -> Bool) -> Reply -> Bool
901+statusCheck prd = prd . NHTS.statusCode . responseStatus
902+
903+-- | This is a hook that can be set via the 'bhRequestHook' function
904+-- that will authenticate all requests using an HTTP Basic
905+-- Authentication header. Note that it is *strongly* recommended that
906+-- this option only be used over an SSL connection.
907+--
908+-- >> (mkBHEnv myServer myManager) { bhRequestHook = basicAuthHook (EsUsername "myuser") (EsPassword "mypass") }
909+basicAuthHook :: Monad m => EsUsername -> EsPassword -> Request -> m Request
910+basicAuthHook (EsUsername u) (EsPassword p) = return . applyBasicAuth u' p'
911+ where u' = T.encodeUtf8 u
912+ p' = T.encodeUtf8 p
913+
914+
915+boolQP :: Bool -> Text
916+boolQP True = "true"
917+boolQP False = "false"
src/Database/Bloodhound/Types.hs → src/Database/V1/Bloodhound/Types.hsrenamed+150−93
@@ -29,7 +29,7 @@
2929
3030
3131
32−module Database.Bloodhound.Types
32+module Database.V1.Bloodhound.Types
3333 ( defaultCache
3434 , defaultIndexSettings
3535 , defaultIndexDocumentSettings
@@ -51,6 +51,7 @@ module Database.Bloodhound.Types
5151 , toMissing
5252 , toTerms
5353 , toDateHistogram
54+ , toTopHits
5455 , omitNulls
5556 , BH(..)
5657 , runBH
@@ -340,6 +341,7 @@ module Database.Bloodhound.Types
340341 , DateMathAnchor(..)
341342 , DateMathModifier(..)
342343 , DateMathUnit(..)
344+ , TopHitsAggregation(..)
343345
344346 , Highlights(..)
345347 , FieldHighlight(..)
@@ -357,53 +359,59 @@ module Database.Bloodhound.Types
357359 , TermsResult(..)
358360 , DateHistogramResult(..)
359361 , DateRangeResult(..)
362+ , TopHitResult(..)
360363
361364 , EsUsername(..)
362365 , EsPassword(..)
363366 ) where
364367
365−import Control.Applicative as A
366−import Control.Arrow (first)
368+import Control.Applicative as A
369+import Control.Arrow (first)
367370import Control.Monad.Catch
368371import Control.Monad.Except
369−import Control.Monad.Reader
370−import Control.Monad.State
371−import Control.Monad.Writer
372+import Control.Monad.Reader (MonadReader (..),
373+ ReaderT (..))
374+import Control.Monad.State (MonadState)
375+import Control.Monad.Writer (MonadWriter)
372376import Data.Aeson
373−import Data.Aeson.Types (Pair, Parser, emptyObject,
374− parseEither, parseMaybe)
375−import qualified Data.ByteString.Lazy.Char8 as L
377+import Data.Aeson.Types (Pair, Parser,
378+ emptyObject,
379+ parseEither, parseMaybe)
380+import qualified Data.ByteString.Lazy.Char8 as L
376381import Data.Char
377−import Data.Hashable (Hashable)
378−import qualified Data.HashMap.Strict as HM
379−import Data.List (foldl', intercalate, nub)
380−import Data.List.NonEmpty (NonEmpty (..), toList)
381−import qualified Data.Map.Strict as M
382+import Data.Hashable (Hashable)
383+import qualified Data.HashMap.Strict as HM
384+import Data.List (foldl', intercalate,
385+ nub)
386+import Data.List.NonEmpty (NonEmpty (..), toList)
387+import qualified Data.Map.Strict as M
382388import Data.Maybe
383−import Data.Scientific (Scientific)
384−import Data.Text (Text)
385−import qualified Data.Text as T
389+import Data.Scientific (Scientific)
390+import Data.Semigroup
391+import Data.Text (Text)
392+import qualified Data.Text as T
386393import Data.Time.Calendar
387−import Data.Time.Clock (NominalDiffTime, UTCTime)
394+import Data.Time.Clock (NominalDiffTime,
395+ UTCTime)
388396import Data.Time.Clock.POSIX
389−import qualified Data.Traversable as DT
390−import Data.Typeable (Typeable)
391−import qualified Data.Vector as V
392−import qualified Data.Version as Vers
397+import qualified Data.Traversable as DT
398+import Data.Typeable (Typeable)
399+import qualified Data.Vector as V
400+import qualified Data.Version as Vers
393401import GHC.Enum
394−import GHC.Generics (Generic)
402+import GHC.Generics (Generic)
395403import Network.HTTP.Client
396−import qualified Network.HTTP.Types.Method as NHTM
397−import qualified Text.ParserCombinators.ReadP as RP
398−import qualified Text.Read as TR
404+import qualified Network.HTTP.Types.Method as NHTM
405+import qualified Text.ParserCombinators.ReadP as RP
406+import qualified Text.Read as TR
399407
400−import Database.Bloodhound.Types.Class
401−import Database.Bloodhound.Types.Internal
408+import Database.V1.Bloodhound.Types.Class
409+import Database.V1.Bloodhound.Types.Internal
402410
403411-- $setup
404412-- >>> :set -XOverloadedStrings
405413-- >>> import Data.Aeson
406−-- >>> import Database.Bloodhound
414+-- >>> import Database.V1.Bloodhound
407415-- >>> let testServer = (Server "http://localhost:9200")
408416-- >>> let testIndex = IndexName "twitter"
409417-- >>> let testMapping = MappingName "tweet"
@@ -1644,12 +1652,12 @@ data SearchHits a =
16441652 , maxScore :: Score
16451653 , hits :: [Hit a] } deriving (Eq, Read, Show, Generic, Typeable)
16461654
1655+instance Semigroup (SearchHits a) where
1656+ (SearchHits ta ma ha) <> (SearchHits tb mb hb) = SearchHits (ta + tb) (max ma mb) (ha <> hb)
16471657
16481658instance Monoid (SearchHits a) where
16491659 mempty = SearchHits 0 Nothing mempty
1650− mappend (SearchHits ta ma ha) (SearchHits tb mb hb) =
1651− SearchHits (ta + tb) (max ma mb) (ha <> hb)
1652−
1660+ mappend = (<>)
16531661
16541662data Hit a =
16551663 Hit { hitIndex :: IndexName
@@ -1672,7 +1680,7 @@ showText = T.pack . show
16721680readMay :: Read a => String -> Maybe a
16731681readMay s = case reads s of
16741682 (a, ""):_ -> Just a
1675− _ -> Nothing
1683+ _ -> Nothing
16761684
16771685parseReadText :: Read a => Text -> Parser a
16781686parseReadText = maybe mzero return . readMay . T.unpack
@@ -1722,7 +1730,15 @@ data Aggregation = TermsAgg TermsAggregation
17221730 | ValueCountAgg ValueCountAggregation
17231731 | FilterAgg FilterAggregation
17241732 | DateRangeAgg DateRangeAggregation
1725− | MissingAgg MissingAggregation deriving (Eq, Read, Show, Generic, Typeable)
1733+ | MissingAgg MissingAggregation
1734+ | TopHitsAgg TopHitsAggregation
1735+ deriving (Eq, Read, Show, Generic, Typeable)
1736+
1737+data TopHitsAggregation = TopHitsAggregation
1738+ { taFrom :: Maybe From
1739+ , taSize :: Maybe Size
1740+ , taSort :: Maybe Sort
1741+ } deriving (Eq, Read, Show)
17261742
17271743data MissingAggregation = MissingAggregation
17281744 { maField :: Text
@@ -1863,11 +1879,11 @@ instance ToJSON Interval where
18631879 toJSON (FractionalInterval fraction interval) = toJSON $ show fraction ++ show interval
18641880
18651881instance Show TimeInterval where
1866− show Weeks = "w"
1867− show Days = "d"
1868− show Hours = "h"
1869− show Minutes = "m"
1870− show Seconds = "s"
1882+ show Weeks = "w"
1883+ show Days = "d"
1884+ show Hours = "h"
1885+ show Minutes = "m"
1886+ show Seconds = "s"
18711887
18721888instance Read TimeInterval where
18731889 readPrec = f =<< TR.get
@@ -1923,6 +1939,13 @@ instance ToJSON Aggregation where
19231939 toJSON (MissingAgg (MissingAggregation{..})) =
19241940 object ["missing" .= object ["field" .= maField]]
19251941
1942+ toJSON (TopHitsAgg (TopHitsAggregation mfrom msize msort)) =
1943+ omitNulls ["top_hits" .= omitNulls [ "size" .= msize
1944+ , "from" .= mfrom
1945+ , "sort" .= msort
1946+ ]
1947+ ]
1948+
19261949instance ToJSON DateRangeAggregation where
19271950 toJSON DateRangeAggregation {..} =
19281951 omitNulls [ "field" .= draField
@@ -1931,22 +1954,22 @@ instance ToJSON DateRangeAggregation where
19311954 ]
19321955
19331956instance ToJSON DateRangeAggRange where
1934− toJSON (DateRangeFrom e) = object [ "from" .= e ]
1935− toJSON (DateRangeTo e) = object [ "to" .= e ]
1957+ toJSON (DateRangeFrom e) = object [ "from" .= e ]
1958+ toJSON (DateRangeTo e) = object [ "to" .= e ]
19361959 toJSON (DateRangeFromAndTo f t) = object [ "from" .= f, "to" .= t ]
19371960
19381961instance ToJSON DateMathExpr where
19391962 toJSON (DateMathExpr a mods) = String (fmtA a <> mconcat (fmtMod <$> mods))
1940− where fmtA DMNow = "now"
1963+ where fmtA DMNow = "now"
19411964 fmtA (DMDate date) = (T.pack $ showGregorian date) <> "||"
1942− fmtMod (AddTime n u) = "+" <> showText n <> fmtU u
1965+ fmtMod (AddTime n u) = "+" <> showText n <> fmtU u
19431966 fmtMod (SubtractTime n u) = "-" <> showText n <> fmtU u
1944− fmtMod (RoundDownTo u) = "/" <> fmtU u
1945− fmtU DMYear = "y"
1946− fmtU DMMonth = "M"
1947− fmtU DMWeek = "w"
1948− fmtU DMDay = "d"
1949− fmtU DMHour = "h"
1967+ fmtMod (RoundDownTo u) = "/" <> fmtU u
1968+ fmtU DMYear = "y"
1969+ fmtU DMMonth = "M"
1970+ fmtU DMWeek = "w"
1971+ fmtU DMDay = "d"
1972+ fmtU DMHour = "h"
19501973 fmtU DMMinute = "m"
19511974 fmtU DMSecond = "s"
19521975
@@ -1967,6 +1990,9 @@ data BucketValue = TextValue Text
19671990
19681991data MissingResult = MissingResult { missingDocCount :: Int } deriving (Show)
19691992
1993+data TopHitResult a = TopHitResult { tarHits :: (SearchHits a)
1994+ } deriving Show
1995+
19701996data TermsResult = TermsResult { termKey :: BucketValue
19711997 , termsDocCount :: Int
19721998 , termsAggs :: Maybe AggregationResults } deriving (Read, Show)
@@ -1993,6 +2019,9 @@ toDateHistogram = toAggResult
19932019toMissing :: Text -> AggregationResults -> Maybe MissingResult
19942020toMissing = toAggResult
19952021
2022+toTopHits :: (FromJSON a) => Text -> AggregationResults -> Maybe (TopHitResult a)
2023+toTopHits = toAggResult
2024+
19962025toAggResult :: (FromJSON a) => Text -> AggregationResults -> Maybe a
19972026toAggResult t a = M.lookup t a >>= deserialize
19982027 where deserialize = parseMaybe parseJSON
@@ -2012,7 +2041,7 @@ instance BucketAggregation DateRangeResult where
20122041 docCount = dateRangeDocCount
20132042 aggs = dateRangeAggs
20142043
2015−instance (FromJSON a, BucketAggregation a) => FromJSON (Bucket a) where
2044+instance (FromJSON a) => FromJSON (Bucket a) where
20162045 parseJSON (Object v) = Bucket <$>
20172046 v .: "buckets"
20182047 parseJSON _ = mempty
@@ -2020,18 +2049,18 @@ instance (FromJSON a, BucketAggregation a) => FromJSON (Bucket a) where
20202049instance FromJSON BucketValue where
20212050 parseJSON (String t) = return $ TextValue t
20222051 parseJSON (Number s) = return $ ScientificValue s
2023− parseJSON (Bool b) = return $ BoolValue b
2024− parseJSON _ = mempty
2052+ parseJSON (Bool b) = return $ BoolValue b
2053+ parseJSON _ = mempty
20252054
20262055instance FromJSON MissingResult where
20272056 parseJSON = withObject "MissingResult" parse
20282057 where parse v = MissingResult <$> v .: "doc_count"
20292058
20302059instance FromJSON TermsResult where
2031− parseJSON (Object v) = TermsResult <$>
2060+ parseJSON (Object v) = TermsResult <$>
20322061 v .: "key" <*>
20332062 v .: "doc_count" <*>
2034− v .:? "aggregations"
2063+ (pure $ getNamedSubAgg v ["key", "doc_count"])
20352064 parseJSON _ = mempty
20362065
20372066instance FromJSON DateHistogramResult where
@@ -2039,7 +2068,11 @@ instance FromJSON DateHistogramResult where
20392068 v .: "key" <*>
20402069 v .:? "key_as_string" <*>
20412070 v .: "doc_count" <*>
2042− v .:? "aggregations"
2071+ (pure $ getNamedSubAgg v [ "key"
2072+ , "doc_count"
2073+ , "key_as_string"
2074+ ]
2075+ )
20432076 parseJSON _ = mempty
20442077
20452078instance FromJSON DateRangeResult where
@@ -2051,16 +2084,31 @@ instance FromJSON DateRangeResult where
20512084 (fmap posixMS <$> v .:? "to") <*>
20522085 v .:? "to_as_string" <*>
20532086 v .: "doc_count" <*>
2054− v .:? "aggregations"
2087+ (pure $ getNamedSubAgg v [ "key"
2088+ , "from"
2089+ , "from_as_string"
2090+ , "to"
2091+ , "to_as_string"
2092+ , "doc_count"
2093+ ]
2094+ )
20552095
20562096instance FromJSON POSIXMS where
20572097 parseJSON = withScientific "POSIXMS" (return . parse)
20582098 where parse n = let n' = truncate n :: Integer
20592099 in POSIXMS (posixSecondsToUTCTime (fromInteger (n' `div` 1000)))
20602100
2101+instance (FromJSON a) => FromJSON (TopHitResult a) where
2102+ parseJSON (Object v) = TopHitResult <$>
2103+ v .: "hits"
2104+ parseJSON _ = fail "Failure in FromJSON (TopHitResult a)"
2105+
2106+instance Semigroup Filter where
2107+ a <> b = AndFilter [a, b] defaultCache
2108+
20612109instance Monoid Filter where
20622110 mempty = IdentityFilter
2063− mappend a b = AndFilter [a, b] defaultCache
2111+ mappend = (<>)
20642112
20652113instance Seminearring Filter where
20662114 a <||> b = OrFilter [a, b] defaultCache
@@ -2250,6 +2298,15 @@ fieldTagged f o = case HM.toList o of
22502298 [(k, Object o')] -> f (FieldName k) o'
22512299 _ -> fail "Expected object with 1 field-named key"
22522300
2301+-- Try to get an AggregationResults when we don't know the
2302+-- field name. We filter out the known keys to try to minimize the noise.
2303+getNamedSubAgg :: Object -> [Text] -> Maybe AggregationResults
2304+getNamedSubAgg o knownKeys = maggRes
2305+ where unknownKeys = HM.filterWithKey (\k _ -> k `notElem` knownKeys) o
2306+ maggRes
2307+ | HM.null unknownKeys = Nothing
2308+ | otherwise = Just . M.fromList $ HM.toList unknownKeys
2309+
22532310instance ToJSON GeoPoint where
22542311 toJSON (GeoPoint (FieldName geoPointField) geoPointLatLon) =
22552312 object [ geoPointField .= geoPointLatLon ]
@@ -2421,9 +2478,9 @@ instance FromJSON Query where
24212478
24222479omitNulls :: [(Text, Value)] -> Value
24232480omitNulls = object . filter notNull where
2424− notNull (_, Null) = False
2481+ notNull (_, Null) = False
24252482 notNull (_, Array a) = (not . V.null) a
2426− notNull _ = True
2483+ notNull _ = True
24272484
24282485
24292486instance ToJSON SimpleQueryStringQuery where
@@ -2976,10 +3033,10 @@ instance FromJSON MultiMatchQuery where
29763033 <*> o .:? "lenient"
29773034
29783035instance ToJSON MultiMatchQueryType where
2979− toJSON MultiMatchBestFields = "best_fields"
2980− toJSON MultiMatchMostFields = "most_fields"
2981− toJSON MultiMatchCrossFields = "cross_fields"
2982− toJSON MultiMatchPhrase = "phrase"
3036+ toJSON MultiMatchBestFields = "best_fields"
3037+ toJSON MultiMatchMostFields = "most_fields"
3038+ toJSON MultiMatchCrossFields = "cross_fields"
3039+ toJSON MultiMatchPhrase = "phrase"
29833040 toJSON MultiMatchPhrasePrefix = "phrase_prefix"
29843041
29853042instance FromJSON MultiMatchQueryType where
@@ -2993,7 +3050,7 @@ instance FromJSON MultiMatchQueryType where
29933050
29943051instance ToJSON BooleanOperator where
29953052 toJSON And = String "and"
2996− toJSON Or = String "or"
3053+ toJSON Or = String "or"
29973054
29983055instance FromJSON BooleanOperator where
29993056 parseJSON = withText "BooleanOperator" parse
@@ -3012,7 +3069,7 @@ instance FromJSON ZeroTermsQuery where
30123069 parse q = fail ("Unexpected ZeroTermsQuery: " <> show q)
30133070
30143071instance ToJSON MatchQueryType where
3015− toJSON MatchPhrase = "phrase"
3072+ toJSON MatchPhrase = "phrase"
30163073 toJSON MatchPhrasePrefix = "phrase_prefix"
30173074
30183075instance FromJSON MatchQueryType where
@@ -3146,7 +3203,7 @@ instance FromJSON IndexSettingsSummary where
31463203 <*> (fmap (filter (not . redundant)) . parseSettings =<< o' .: "settings")
31473204 _ -> fail "Expected single-key object with index name"
31483205 redundant (NumberOfReplicas _) = True
3149− redundant _ = False
3206+ redundant _ = False
31503207
31513208-- | For some reason in several settings APIs, all leaf values get returned
31523209-- as strings. This function attepmts to recover from this for all
@@ -3156,7 +3213,7 @@ unStringlyTypeJSON (String "true") = Bool True
31563213unStringlyTypeJSON (String "false") = Bool False
31573214unStringlyTypeJSON (String "null") = Null
31583215unStringlyTypeJSON v@(String t) = case readMay (T.unpack t) of
3159− Just n -> Number n
3216+ Just n -> Number n
31603217 Nothing -> v
31613218unStringlyTypeJSON v = v
31623219
@@ -3173,7 +3230,7 @@ parseSettings o = do
31733230 return (catMaybes parses)
31743231
31753232oPath :: ToJSON a => NonEmpty Text -> a -> Value
3176−oPath (k :| []) v = object [k .= v]
3233+oPath (k :| []) v = object [k .= v]
31773234oPath (k:| (h:t)) v = object [k .= oPath (h :| t) v]
31783235
31793236attrFilterJSON :: NonEmpty NodeAttrFilter -> Value
@@ -3183,12 +3240,12 @@ attrFilterJSON fs = object [ n .= T.intercalate "," (toList vs)
31833240parseAttrFilter :: Value -> Parser (NonEmpty NodeAttrFilter)
31843241parseAttrFilter = withObject "NonEmpty NodeAttrFilter" parse
31853242 where parse o = case HM.toList o of
3186− [] -> fail "Expected non-empty list of NodeAttrFilters"
3243+ [] -> fail "Expected non-empty list of NodeAttrFilters"
31873244 x:xs -> DT.mapM (uncurry parse') (x :| xs)
31883245 parse' n = withText "Text" $ \t ->
31893246 case T.splitOn "," t of
31903247 fv:fvs -> return (NodeAttrFilter (NodeAttrName n) (fv :| fvs))
3191− [] -> fail "Expected non-empty list of filter values"
3248+ [] -> fail "Expected non-empty list of filter values"
31923249
31933250instance ToJSON ReplicaBounds where
31943251 toJSON (ReplicasBounded a b) = String (showText a <> "-" <> showText b)
@@ -3242,12 +3299,12 @@ instance ToJSON FSType where
32423299
32433300instance FromJSON FSType where
32443301 parseJSON = withText "FSType" parse
3245− where parse "simple" = pure FSSimple
3302+ where parse "simple" = pure FSSimple
32463303 parse "buffered" = pure FSBuffered
3247− parse t = fail ("Invalid FSType: " <> show t)
3304+ parse t = fail ("Invalid FSType: " <> show t)
32483305
32493306instance ToJSON CompoundFormat where
3250− toJSON (CompoundFileFormat x) = Bool x
3307+ toJSON (CompoundFileFormat x) = Bool x
32513308 toJSON (MergeSegmentVsTotalIndex x) = toJSON x
32523309
32533310instance FromJSON CompoundFormat where
@@ -3504,8 +3561,8 @@ instance ToJSON SortMode where
35043561
35053562
35063563instance ToJSON Missing where
3507− toJSON LastMissing = String "_last"
3508− toJSON FirstMissing = String "_first"
3564+ toJSON LastMissing = String "_last"
3565+ toJSON FirstMissing = String "_first"
35093566 toJSON (CustomMissing txt) = String txt
35103567
35113568
@@ -3521,7 +3578,7 @@ instance FromJSON ScoreType where
35213578 parse "avg" = pure ScoreTypeAvg
35223579 parse "sum" = pure ScoreTypeSum
35233580 parse "none" = pure ScoreTypeNone
3524− parse t = fail ("Unexpected ScoreType: " <> show t)
3581+ parse t = fail ("Unexpected ScoreType: " <> show t)
35253582
35263583instance ToJSON Distance where
35273584 toJSON (Distance dCoefficient dUnit) =
@@ -3539,7 +3596,7 @@ instance FromJSON Distance where
35393596 validForNumber '-' = True
35403597 validForNumber '.' = True
35413598 validForNumber 'e' = True
3542− validForNumber c = isNumber c
3599+ validForNumber c = isNumber c
35433600 parseCoeff "" = fail "Empty string cannot be parsed as number"
35443601 parseCoeff s = return (read (T.unpack s))
35453602
@@ -3566,7 +3623,7 @@ instance FromJSON DistanceUnit where
35663623 parse "cm" = pure Centimeters
35673624 parse "mm" = pure Millimeters
35683625 parse "nmi" = pure NauticalMiles
3569− parse u = fail ("Unrecognized DistanceUnit: " <> show u)
3626+ parse u = fail ("Unrecognized DistanceUnit: " <> show u)
35703627
35713628instance ToJSON DistanceType where
35723629 toJSON Arc = String "arc"
@@ -3575,21 +3632,21 @@ instance ToJSON DistanceType where
35753632
35763633instance FromJSON DistanceType where
35773634 parseJSON = withText "DistanceType" parse
3578− where parse "arc" = pure Arc
3635+ where parse "arc" = pure Arc
35793636 parse "sloppy_arc" = pure SloppyArc
3580− parse "plane" = pure Plane
3581− parse t = fail ("Unrecognized DistanceType: " <> show t)
3637+ parse "plane" = pure Plane
3638+ parse t = fail ("Unrecognized DistanceType: " <> show t)
35823639
35833640
35843641instance ToJSON OptimizeBbox where
3585− toJSON NoOptimizeBbox = String "none"
3642+ toJSON NoOptimizeBbox = String "none"
35863643 toJSON (OptimizeGeoFilterType gft) = toJSON gft
35873644
35883645instance FromJSON OptimizeBbox where
35893646 parseJSON v = withText "NoOptimizeBbox" parseNoOptimize v
35903647 <|> parseOptimize v
35913648 where parseNoOptimize "none" = pure NoOptimizeBbox
3592− parseNoOptimize _ = mzero
3649+ parseNoOptimize _ = mzero
35933650 parseOptimize = fmap OptimizeGeoFilterType . parseJSON
35943651
35953652instance ToJSON GeoBoundingBoxConstraint where
@@ -4382,7 +4439,7 @@ instance SnapshotRepo FsSnapshotRepo where
43824439
43834440parseRepo :: Parser a -> Either SnapshotRepoConversionError a
43844441parseRepo parser = case parseEither (const parser) () of
4385− Left e -> Left (OtherRepoConversionError (T.pack e))
4442+ Left e -> Left (OtherRepoConversionError (T.pack e))
43864443 Right a -> Right a
43874444
43884445
@@ -4908,7 +4965,7 @@ instance FromJSON NodeIndicesStats where
49084965 where
49094966 parse o = do
49104967 let (.::) mv k = case mv of
4911− Just v -> Just <$> v .: k
4968+ Just v -> Just <$> v .: k
49124969 Nothing -> pure Nothing
49134970 mRecovery <- o .:? "recovery"
49144971 mQueryCache <- o .:? "query_cache"
@@ -5159,8 +5216,8 @@ parseStringInterval s = case span isNumber s of
51595216 ("", _) -> fail "Invalid interval"
51605217 (nS, unitS) -> case (readMay nS, readMay unitS) of
51615218 (Just n, Just unit) -> return (fromInteger (n * unitNDT unit))
5162− (Nothing, _) -> fail "Invalid interval number"
5163− (_, Nothing) -> fail "Invalid interval unit"
5219+ (Nothing, _) -> fail "Invalid interval number"
5220+ (_, Nothing) -> fail "Invalid interval unit"
51645221 where
51655222 unitNDT Seconds = 1
51665223 unitNDT Minutes = 60
@@ -5179,8 +5236,8 @@ instance FromJSON ThreadPoolSize where
51795236 parseAsString = withText "ThreadPoolSize" $ \t ->
51805237 case first (readMay . T.unpack) (T.span isNumber t) of
51815238 (Just n, "k") -> return (ThreadPoolBounded (n * 1000))
5182− (Just n, "") -> return (ThreadPoolBounded n)
5183− _ -> fail ("Invalid thread pool size " <> T.unpack t)
5239+ (Just n, "") -> return (ThreadPoolBounded n)
5240+ _ -> fail ("Invalid thread pool size " <> T.unpack t)
51845241
51855242instance FromJSON ThreadPoolType where
51865243 parseJSON = withText "ThreadPoolType" parse
@@ -5195,10 +5252,10 @@ instance FromJSON NodeTransportInfo where
51955252 where
51965253 parse o = NodeTransportInfo <$> (maybe (return mempty) parseProfiles =<< o .:? "profiles")
51975254 <*> parseJSON (Object o)
5198− parseProfiles (Object o) | HM.null o = return []
5255+ parseProfiles (Object o) | HM.null o = return []
51995256 parseProfiles v@(Array _) = parseJSON v
5200− parseProfiles Null = return []
5201− parseProfiles _ = fail "Could not parse profiles"
5257+ parseProfiles Null = return []
5258+ parseProfiles _ = fail "Could not parse profiles"
52025259
52035260instance FromJSON NodeNetworkInfo where
52045261 parseJSON = withObject "NodeNetworkInfo" parse
@@ -5219,4 +5276,4 @@ newtype MaybeNA a = MaybeNA { unMaybeNA :: Maybe a }
52195276
52205277instance FromJSON a => FromJSON (MaybeNA a) where
52215278 parseJSON (String "NA") = pure $ MaybeNA Nothing
5222− parseJSON o = MaybeNA . Just <$> parseJSON o
5279+ parseJSON o = MaybeNA . Just <$> parseJSON o
src/Database/Bloodhound/Types/Class.hs → src/Database/V1/Bloodhound/Types/Class.hsrenamed+1−1
@@ -1,5 +1,5 @@
11{-# LANGUAGE CPP #-}
2−module Database.Bloodhound.Types.Class
2+module Database.V1.Bloodhound.Types.Class
33 ( Seminearring(..) )
44 where
55
src/Database/Bloodhound/Types/Internal.hs → src/Database/V1/Bloodhound/Types/Internal.hsrenamed+1−1
@@ -15,7 +15,7 @@
1515-- Internal data types for Bloodhound. These types may change without
1616-- notice so import at your own risk.
1717-------------------------------------------------------------------------------
18−module Database.Bloodhound.Types.Internal
18+module Database.V1.Bloodhound.Types.Internal
1919 ( BHEnv(..)
2020 , Server(..)
2121 , MonadBH(..)
src/Database/V5/Bloodhound.hsnew file+10
@@ -0,0 +1,10 @@
1+module Database.V5.Bloodhound
2+ ( -- module Data.Aeson.Types
3+ -- ,
4+ module Database.V5.Bloodhound.Client
5+ , module Database.V5.Bloodhound.Types
6+ ) where
7+
8+-- import Data.Aeson.Types
9+import Database.V5.Bloodhound.Client
10+import Database.V5.Bloodhound.Types
src/Database/Bloodhound/Client.hs → src/Database/V5/Bloodhound/Client.hsrenamed+87−68
@@ -7,7 +7,7 @@
77-- Module : Database.Bloodhound.Client
88-- Copyright : (C) 2014 Chris Allen
99-- License : BSD-style (see the file LICENSE)
10−-- Maintainer : Chris Allen <cma@bitemyapp.com
10+-- Maintainer : Chris Allen <cma@bitemyapp.com>
1111-- Stability : provisional
1212-- Portability : OverloadedStrings
1313--
@@ -15,7 +15,7 @@
1515--
1616-------------------------------------------------------------------------------
1717
18−module Database.Bloodhound.Client
18+module Database.V5.Bloodhound.Client
1919 ( -- * Bloodhound client functions
2020 -- | The examples in this module assume the following code has been run.
2121 -- The :{ and :} will only work in GHCi. You'll only need the data types
@@ -28,7 +28,7 @@ module Database.Bloodhound.Client
2828 , deleteIndex
2929 , updateIndexSettings
3030 , getIndexSettings
31− , optimizeIndex
31+ , forceMergeIndex
3232 , indexExists
3333 , openIndex
3434 , closeIndex
@@ -43,7 +43,6 @@ module Database.Bloodhound.Client
4343 , deleteTemplate
4444 -- ** Mapping
4545 , putMapping
46− , deleteMapping
4746 -- ** Documents
4847 , indexDocument
4948 , updateDocument
@@ -56,6 +55,7 @@ module Database.Bloodhound.Client
5655 , searchByType
5756 , scanSearch
5857 , getInitialScroll
58+ , getInitialSortedScroll
5959 , advanceScroll
6060 , refreshIndex
6161 , mkSearch
@@ -121,19 +121,20 @@ import qualified Network.HTTP.Types.URI as NHTU
121121import qualified Network.URI as URI
122122import Prelude hiding (filter, head)
123123
124−import Database.Bloodhound.Types
124+import Database.V5.Bloodhound.Types
125125
126126-- $setup
127127-- >>> :set -XOverloadedStrings
128128-- >>> :set -XDeriveGeneric
129−-- >>> import Database.Bloodhound
129+-- >>> import Database.V5.Bloodhound
130130-- >>> let testServer = (Server "http://localhost:9200")
131131-- >>> let runBH' = withBH defaultManagerSettings testServer
132132-- >>> let testIndex = IndexName "twitter"
133133-- >>> let testMapping = MappingName "tweet"
134134-- >>> let defaultIndexSettings = IndexSettings (ShardCount 1) (ReplicaCount 0)
135135-- >>> data TweetMapping = TweetMapping deriving (Eq, Show)
136−-- >>> _ <- runBH' $ deleteIndex testIndex >> deleteMapping testIndex testMapping
136+-- >>> _ <- runBH' $ deleteIndex testIndex
137+-- >>> _ <- runBH' $ deleteIndex (IndexName "didimakeanindex")
137138-- >>> import GHC.Generics
138139-- >>> import Data.Time.Calendar (Day (..))
139140-- >>> import Data.Time.Clock (UTCTime (..), secondsToDiffTime)
@@ -224,12 +225,8 @@ appendSearchTypeParam originalUrl st = addQuery params originalUrl
224225 where stText = "search_type"
225226 params
226227 | st == SearchTypeDfsQueryThenFetch = [(stText, Just "dfs_query_then_fetch")]
227− | st == SearchTypeCount = [(stText, Just "count")]
228− | st == SearchTypeScan = [(stText, Just "scan"), ("scroll", Just "1m")]
229− | st == SearchTypeQueryAndFetch = [(stText, Just "query_and_fetch")]
230− | st == SearchTypeDfsQueryAndFetch = [(stText, Just "dfs_query_and_fetch")]
231228 -- used to catch 'SearchTypeQueryThenFetch', which is also the default
232− | otherwise = [(stText, Just "query_then_fetch")]
229+ | otherwise = []
233230
234231-- | Severely dumbed down query renderer. Assumes your data doesn't
235232-- need any encoding
@@ -273,8 +270,8 @@ post = dispatch NHTM.methodPost
273270-- | 'getStatus' fetches the 'Status' of a 'Server'
274271--
275272-- >>> serverStatus <- runBH' getStatus
276−-- >>> fmap status (serverStatus)
277−-- Just 200
273+-- >>> fmap tagline (serverStatus)
274+-- Just "You Know, for Search"
278275getStatus :: MonadBH m => m (Maybe Status)
279276getStatus = do
280277 response <- get =<< url
@@ -513,7 +510,7 @@ createIndex indexSettings (IndexName indexName) =
513510-- >>> response <- runBH' $ deleteIndex (IndexName "didimakeanindex")
514511-- >>> respIsTwoHunna response
515512-- True
516−-- >>> runBH' $ indexExists testIndex
513+-- >>> runBH' $ indexExists (IndexName "didimakeanindex")
517514-- False
518515deleteIndex :: MonadBH m => IndexName -> m Reply
519516deleteIndex (IndexName indexName) =
@@ -540,33 +537,36 @@ getIndexSettings (IndexName indexName) = do
540537 where url = joinPath [indexName, "_settings"]
541538
542539
543−-- | 'optimizeIndex' will optimize a single index, list of indexes or
544−-- all indexes. Note that this call will block until finishing but
545−-- will continue even if the request times out. Concurrent requests to
546−-- optimize an index while another is performing will block until the
547−-- previous one finishes. For more information see
548−-- <https://www.elastic.co/guide/en/elasticsearch/reference/1.7/indices-optimize.html>. Nothing
540+-- | 'forceMergeIndex'
541+--
542+-- The force merge API allows to force merging of one or more indices through
543+-- an API. The merge relates to the number of segments a Lucene index holds
544+-- within each shard. The force merge operation allows to reduce the number of
545+-- segments by merging them.
546+--
547+-- This call will block until the merge is complete. If the http connection is
548+-- lost, the request will continue in the background, and any new requests will
549+-- block until the previous force merge is complete.
550+
551+-- For more information see
552+-- <https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-forcemerge.html#indices-forcemerge>.
553+-- Nothing
549554-- worthwhile comes back in the reply body, so matching on the status
550555-- should suffice.
551556--
552−-- 'optimizeIndex' with a maxNumSegments of 1 and onlyExpungeDeletes
557+-- 'forceMergeIndex' with a maxNumSegments of 1 and onlyExpungeDeletes
553558-- to True is the main way to release disk space back to the OS being
554559-- held by deleted documents.
555560--
556−-- Note that this API was deprecated in ElasticSearch 2.1 for the
557−-- almost completely identical forcemerge API. Adding support to that
558−-- 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.
560−--
561561-- >>> let ixn = IndexName "unoptimizedindex"
562562-- >>> _ <- runBH' $ deleteIndex ixn >> createIndex defaultIndexSettings ixn
563−-- >>> response <- runBH' $ optimizeIndex (IndexList (ixn :| [])) (defaultIndexOptimizationSettings { maxNumSegments = Just 1, onlyExpungeDeletes = True })
563+-- >>> response <- runBH' $ forceMergeIndex (IndexList (ixn :| [])) (defaultIndexOptimizationSettings { maxNumSegments = Just 1, onlyExpungeDeletes = True })
564564-- >>> respIsTwoHunna response
565565-- True
566−optimizeIndex :: MonadBH m => IndexSelection -> IndexOptimizationSettings -> m Reply
567−optimizeIndex ixs IndexOptimizationSettings {..} =
566+forceMergeIndex :: MonadBH m => IndexSelection -> ForceMergeIndexSettings -> m Reply
567+forceMergeIndex ixs ForceMergeIndexSettings {..} =
568568 bindM2 post url (return body)
569− where url = addQuery params <$> joinPath [indexName, "_optimize"]
569+ where url = addQuery params <$> joinPath [indexName, "_forcemerge"]
570570 params = catMaybes [ ("max_num_segments",) . Just . showText <$> maxNumSegments
571571 , Just ("only_expunge_deletes", Just (boolQP onlyExpungeDeletes))
572572 , Just ("flush", Just (boolQP flushAfterOptimize))
@@ -745,7 +745,7 @@ deleteTemplate (TemplateName templateName) =
745745-- >>> _ <- runBH' $ createIndex defaultIndexSettings testIndex
746746-- >>> resp <- runBH' $ putMapping testIndex testMapping TweetMapping
747747-- >>> 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}
748+-- Response {responseStatus = Status {statusCode = 200, statusMessage = "OK"}, responseVersion = HTTP/1.1, responseHeaders = [("content-type","application/json; charset=UTF-8"),("content-encoding","gzip"),("transfer-encoding","chunked")], responseBody = "{\"acknowledged\":true}", responseCookieJar = CJ {expose = []}, responseClose' = ResponseClose}
749749putMapping :: (MonadBH m, ToJSON a) => IndexName
750750 -> MappingName -> a -> m Reply
751751putMapping (IndexName indexName) (MappingName mappingName) mapping =
@@ -755,21 +755,6 @@ putMapping (IndexName indexName) (MappingName mappingName) mapping =
755755 -- erroneously. The correct API call is: "/INDEX/_mapping/MAPPING_NAME"
756756 body = Just $ encode mapping
757757
758−-- | 'deleteMapping' is an HTTP DELETE and deletes a mapping for a given index.
759−-- 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}
766−deleteMapping :: MonadBH m => IndexName -> MappingName -> m Reply
767−deleteMapping (IndexName indexName)
768− (MappingName mappingName) =
769− -- "_mapping" and mappingName below were originally transposed
770− -- erroneously. The correct API call is: "/INDEX/_mapping/MAPPING_NAME"
771− delete =<< joinPath [indexName, "_mapping", mappingName]
772−
773758versionCtlParams :: IndexDocumentSettings -> [(Text, Maybe Text)]
774759versionCtlParams cfg =
775760 case idsVersionControl cfg of
@@ -791,7 +776,7 @@ versionCtlParams cfg =
791776--
792777-- >>> resp <- runBH' $ indexDocument testIndex testMapping defaultIndexDocumentSettings exampleTweet (DocId "1")
793778-- >>> 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}
779+-- Response {responseStatus = Status {statusCode = 201, statusMessage = "Created"}, responseVersion = HTTP/1.1, responseHeaders = [("Location","/twitter/tweet/1"),("content-type","application/json; charset=UTF-8"),("content-encoding","gzip"),("transfer-encoding","chunked")], responseBody = "{\"_index\":\"twitter\",\"_type\":\"tweet\",\"_id\":\"1\",\"_version\":1,\"result\":\"created\",\"_shards\":{\"total\":2,\"successful\":1,\"failed\":0},\"created\":true}", responseCookieJar = CJ {expose = []}, responseClose' = ResponseClose}
795780indexDocument :: (ToJSON doc, MonadBH m) => IndexName -> MappingName
796781 -> IndexDocumentSettings -> doc -> DocId -> m Reply
797782indexDocument (IndexName indexName)
@@ -954,20 +939,40 @@ searchByType (IndexName indexName)
954939 (MappingName mappingName) = bindM2 dispatchSearch url . return
955940 where url = joinPath [indexName, mappingName, "_search"]
956941
957−-- | For a given scearch, request a scroll for efficient streaming of
942+-- | For a given search, request a scroll for efficient streaming of
958943-- search results. Note that the search is put into 'SearchTypeScan'
959944-- mode and thus results will not be sorted. Combine this with
960945-- 'advanceScroll' to efficiently stream through the full result set
961−getInitialScroll :: MonadBH m => IndexName -> MappingName -> Search -> m (Maybe ScrollId)
962−getInitialScroll (IndexName indexName) (MappingName mappingName) search = do
963− let url = joinPath [indexName, mappingName, "_search"]
964− search' = search { searchType = SearchTypeScan }
965− resp' <- bindM2 dispatchSearch url (return search')
966− let msr = decode' $ responseBody resp' :: Maybe (SearchResult ())
967− msid = maybe Nothing scrollId msr
968− return msid
969−
970−scroll' :: (FromJSON a, MonadBH m, MonadThrow m) => Maybe ScrollId -> m ([Hit a], Maybe ScrollId)
946+getInitialScroll ::
947+ (FromJSON a, MonadThrow m, MonadBH m) => IndexName ->
948+ MappingName ->
949+ Search ->
950+ m (Either EsError (SearchResult a))
951+getInitialScroll (IndexName indexName) (MappingName mappingName) search' = do
952+ let url = addQuery params <$> joinPath [indexName, mappingName, "_search"]
953+ params = [("scroll", Just "1m")]
954+ sorting = Just [DefaultSortSpec $ mkSort (FieldName "_doc") Descending]
955+ search = search' { sortBody = sorting }
956+ resp' <- bindM2 dispatchSearch url (return search)
957+ parseEsResponse resp'
958+
959+-- | For a given search, request a scroll for efficient streaming of
960+-- search results. Combine this with 'advanceScroll' to efficiently
961+-- stream through the full result set. Note that this search respects
962+-- sorting and may be less efficient than 'getInitialScroll'.
963+getInitialSortedScroll ::
964+ (FromJSON a, MonadThrow m, MonadBH m) => IndexName ->
965+ MappingName ->
966+ Search ->
967+ m (Either EsError (SearchResult a))
968+getInitialSortedScroll (IndexName indexName) (MappingName mappingName) search = do
969+ let url = addQuery params <$> joinPath [indexName, mappingName, "_search"]
970+ params = [("scroll", Just "1m")]
971+ resp' <- bindM2 dispatchSearch url (return search)
972+ parseEsResponse resp'
973+
974+scroll' :: (FromJSON a, MonadBH m, MonadThrow m) => Maybe ScrollId ->
975+ m ([Hit a], Maybe ScrollId)
971976scroll' Nothing = return ([], Nothing)
972977scroll' (Just sid) = do
973978 res <- advanceScroll sid 60
@@ -987,20 +992,29 @@ advanceScroll
987992 -- ^ How long should the snapshot of data be kept around? This timeout is updated every time 'advanceScroll' is used, so don't feel the need to set it to the entire duration of your search processing. Note that durations < 1s will be rounded up. Also note that 'NominalDiffTime' is an instance of Num so literals like 60 will be interpreted as seconds. 60s is a reasonable default.
988993 -> m (Either EsError (SearchResult a))
989994advanceScroll (ScrollId sid) scroll = do
990− url <- joinPath ["_search/scroll?scroll=" <> scrollTime]
991− parseEsResponse =<< post url (Just . L.fromStrict $ T.encodeUtf8 sid)
995+ url <- joinPath ["_search", "scroll"]
996+ resp <- post url (Just $ encode scrollObject)
997+ parseEsResponse resp
992998 where scrollTime = showText secs <> "s"
993999 secs :: Integer
9941000 secs = round scroll
995−
996−simpleAccumulator :: (FromJSON a, MonadBH m, MonadThrow m) => [Hit a] -> ([Hit a], Maybe ScrollId) -> m ([Hit a], Maybe ScrollId)
1001+
1002+ scrollObject = object [ "scroll" .= scrollTime
1003+ , "scroll_id" .= sid
1004+ ]
1005+
1006+simpleAccumulator ::
1007+ (FromJSON a, MonadBH m, MonadThrow m) =>
1008+ [Hit a] ->
1009+ ([Hit a], Maybe ScrollId) ->
1010+ m ([Hit a], Maybe ScrollId)
9971011simpleAccumulator oldHits (newHits, Nothing) = return (oldHits ++ newHits, Nothing)
9981012simpleAccumulator oldHits ([], _) = return (oldHits, Nothing)
9991013simpleAccumulator oldHits (newHits, msid) = do
10001014 (newHits', msid') <- scroll' msid
10011015 simpleAccumulator (oldHits ++ newHits) (newHits', msid')
10021016
1003−-- | 'scanSearch' uses the 'scan&scroll' API of elastic,
1017+-- | 'scanSearch' uses the 'scroll' API of elastic,
10041018-- for a given 'IndexName' and 'MappingName'. Note that this will
10051019-- consume the entire search result set and will be doing O(n) list
10061020-- appends so this may not be suitable for large result sets. In that
@@ -1009,11 +1023,16 @@ simpleAccumulator oldHits (newHits, msid) = do
10091023-- pipes, or your favorite streaming IO abstraction of choice. Note
10101024-- that ordering on the search would destroy performance and thus is
10111025-- ignored.
1012−scanSearch :: (FromJSON a, MonadBH m, MonadThrow m) => IndexName -> MappingName -> Search -> m [Hit a]
1026+scanSearch :: (FromJSON a, MonadBH m, MonadThrow m) => IndexName
1027+ -> MappingName
1028+ -> Search
1029+ -> m [Hit a]
10131030scanSearch indexName mappingName search = do
1014− msid <- getInitialScroll indexName mappingName search
1015− (hits, msid') <- scroll' msid
1016− (totalHits, _) <- simpleAccumulator [] (hits, msid')
1031+ initialSearchResult <- getInitialScroll indexName mappingName search
1032+ let (hits', josh) = case initialSearchResult of
1033+ Right SearchResult {..} -> (hits searchHits, scrollId)
1034+ Left _ -> ([], Nothing)
1035+ (totalHits, _) <- simpleAccumulator [] (hits', josh)
10171036 return totalHits
10181037
10191038-- | 'mkSearch' is a helper function for defaulting additional fields of a 'Search'
src/Database/V5/Bloodhound/Types.hsnew file+5057
@@ -0,0 +1,5057 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE FlexibleContexts #-}
4+{-# LANGUAGE FlexibleInstances #-}
5+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
6+{-# LANGUAGE MultiParamTypeClasses #-}
7+-- {-# LANGUAGE NoMonomorphismRestriction #-}
8+{-# LANGUAGE NamedFieldPuns #-}
9+{-# LANGUAGE OverloadedStrings #-}
10+{-# LANGUAGE RecordWildCards #-}
11+{-# LANGUAGE UndecidableInstances #-}
12+
13+-------------------------------------------------------------------------------
14+-- |
15+-- Module : Database.Bloodhound.Types
16+-- Copyright : (C) 2014, 2015, 2016 Chris Allen
17+-- License : BSD-style (see the file LICENSE)
18+-- Maintainer : Chris Allen <cma@bitemyapp.com
19+-- Stability : provisional
20+-- Portability : DeriveGeneric, RecordWildCards
21+--
22+-- Data types for describing actions and data structures performed to interact
23+-- with Elasticsearch. The two main buckets your queries against Elasticsearch
24+-- will fall into are 'Query's and 'Filter's. 'Filter's are more like
25+-- traditional database constraints and often have preferable performance
26+-- properties. 'Query's support human-written textual queries, such as fuzzy
27+-- queries.
28+-------------------------------------------------------------------------------
29+
30+
31+
32+module Database.V5.Bloodhound.Types
33+ ( defaultCache
34+ , defaultIndexSettings
35+ , defaultIndexDocumentSettings
36+ , mkSort
37+ , showText
38+ , unpackId
39+ , mkMatchQuery
40+ , mkMultiMatchQuery
41+ , mkBoolQuery
42+ , mkRangeQuery
43+ , mkQueryStringQuery
44+ , mkAggregations
45+ , mkTermsAggregation
46+ , mkTermsScriptAggregation
47+ , mkDateHistogram
48+ , mkCardinalityAggregation
49+ , mkDocVersion
50+ , mkStatsAggregation
51+ , mkExtendedStatsAggregation
52+ , docVersionNumber
53+ , toMissing
54+ , toTerms
55+ , toDateHistogram
56+ , toTopHits
57+ , omitNulls
58+ , BH(..)
59+ , runBH
60+ , BHEnv
61+ , bhServer
62+ , bhManager
63+ , bhRequestHook
64+ , mkBHEnv
65+ , MonadBH(..)
66+ , Version(..)
67+ , VersionNumber(..)
68+ , MaybeNA(..)
69+ , BuildHash(..)
70+ , Status(..)
71+ , Existence(..)
72+ , NullValue(..)
73+ , IndexSettings(..)
74+ , UpdatableIndexSetting(..)
75+ , IndexSettingsSummary(..)
76+ , AllocationPolicy(..)
77+ , ReplicaBounds(..)
78+ , Bytes(..)
79+ , gigabytes
80+ , megabytes
81+ , kilobytes
82+ , FSType(..)
83+ , InitialShardCount(..)
84+ , NodeAttrFilter(..)
85+ , NodeAttrName(..)
86+ , CompoundFormat(..)
87+ , IndexTemplate(..)
88+ , Server(..)
89+ , Reply
90+ , EsResult(..)
91+ , EsResultFound(..)
92+ , EsError(..)
93+ , EsProtocolException(..)
94+ , IndexAlias(..)
95+ , IndexAliasName(..)
96+ , IndexAliasAction(..)
97+ , IndexAliasCreate(..)
98+ , IndexAliasSummary(..)
99+ , IndexAliasesSummary(..)
100+ , AliasRouting(..)
101+ , SearchAliasRouting(..)
102+ , IndexAliasRouting(..)
103+ , RoutingValue(..)
104+ , DocVersion
105+ , ExternalDocVersion(..)
106+ , VersionControl(..)
107+ , DocumentParent(..)
108+ , IndexDocumentSettings(..)
109+ , Query(..)
110+ , Search(..)
111+ , SearchType(..)
112+ , SearchResult(..)
113+ , ScrollId(..)
114+ , SearchHits(..)
115+ , TrackSortScores
116+ , From(..)
117+ , Size(..)
118+ , Source(..)
119+ , PatternOrPatterns(..)
120+ , Include(..)
121+ , Exclude(..)
122+ , Pattern(..)
123+ , ShardResult(..)
124+ , Hit(..)
125+ , Filter(..)
126+ , Seminearring(..)
127+ , BoolMatch(..)
128+ , Term(..)
129+ , GeoPoint(..)
130+ , GeoBoundingBoxConstraint(..)
131+ , GeoBoundingBox(..)
132+ , GeoFilterType(..)
133+ , Distance(..)
134+ , DistanceUnit(..)
135+ , DistanceType(..)
136+ , DistanceRange(..)
137+ , OptimizeBbox(..)
138+ , LatLon(..)
139+ , RangeValue(..)
140+ , RangeExecution(..)
141+ , LessThan(..)
142+ , LessThanEq(..)
143+ , GreaterThan(..)
144+ , GreaterThanEq(..)
145+ , LessThanD(..)
146+ , LessThanEqD(..)
147+ , GreaterThanD(..)
148+ , GreaterThanEqD(..)
149+ , Regexp(..)
150+ , RegexpFlags(..)
151+ , RegexpFlag(..)
152+ , FieldName(..)
153+ , Script(..)
154+ , IndexName(..)
155+ , IndexSelection(..)
156+ , NodeSelection(..)
157+ , NodeSelector(..)
158+ , ForceMergeIndexSettings(..)
159+ , defaultForceMergeIndexSettings
160+ , TemplateName(..)
161+ , TemplatePattern(..)
162+ , MappingName(..)
163+ , DocId(..)
164+ , CacheName(..)
165+ , CacheKey(..)
166+ , BulkOperation(..)
167+ , ReplicaCount(..)
168+ , ShardCount(..)
169+ , Sort
170+ , SortMode(..)
171+ , SortOrder(..)
172+ , SortSpec(..)
173+ , DefaultSort(..)
174+ , Missing(..)
175+ , OpenCloseIndex(..)
176+ , Method
177+ , Boost(..)
178+ , MatchQuery(..)
179+ , MultiMatchQuery(..)
180+ , BoolQuery(..)
181+ , BoostingQuery(..)
182+ , CommonTermsQuery(..)
183+ , DisMaxQuery(..)
184+ , FuzzyLikeThisQuery(..)
185+ , FuzzyLikeFieldQuery(..)
186+ , FuzzyQuery(..)
187+ , HasChildQuery(..)
188+ , HasParentQuery(..)
189+ , IndicesQuery(..)
190+ , MoreLikeThisQuery(..)
191+ , MoreLikeThisFieldQuery(..)
192+ , NestedQuery(..)
193+ , PrefixQuery(..)
194+ , QueryStringQuery(..)
195+ , SimpleQueryStringQuery(..)
196+ , RangeQuery(..)
197+ , RegexpQuery(..)
198+ , QueryString(..)
199+ , BooleanOperator(..)
200+ , ZeroTermsQuery(..)
201+ , CutoffFrequency(..)
202+ , Analyzer(..)
203+ , MaxExpansions(..)
204+ , Lenient(..)
205+ , MatchQueryType(..)
206+ , MultiMatchQueryType(..)
207+ , Tiebreaker(..)
208+ , MinimumMatch(..)
209+ , DisableCoord(..)
210+ , CommonMinimumMatch(..)
211+ , MinimumMatchHighLow(..)
212+ , PrefixLength(..)
213+ , Fuzziness(..)
214+ , IgnoreTermFrequency(..)
215+ , MaxQueryTerms(..)
216+ , ScoreType(..)
217+ , Score
218+ , Cache
219+ , TypeName(..)
220+ , BoostTerms(..)
221+ , MaxWordLength(..)
222+ , MinWordLength(..)
223+ , MaxDocFrequency(..)
224+ , MinDocFrequency(..)
225+ , PhraseSlop(..)
226+ , StopWord(..)
227+ , QueryPath(..)
228+ , MinimumTermFrequency(..)
229+ , PercentMatch(..)
230+ , FieldDefinition(..)
231+ , MappingField(..)
232+ , Mapping(..)
233+ , AllowLeadingWildcard(..)
234+ , LowercaseExpanded(..)
235+ , GeneratePhraseQueries(..)
236+ , Locale(..)
237+ , AnalyzeWildcard(..)
238+ , EnablePositionIncrements(..)
239+ , SimpleQueryFlag(..)
240+ , FieldOrFields(..)
241+ , Monoid(..)
242+ , ToJSON(..)
243+ , Interval(..)
244+ , TimeInterval(..)
245+ , ExecutionHint(..)
246+ , CollectionMode(..)
247+ , TermOrder(..)
248+ , TermInclusion(..)
249+ , SnapshotRepoSelection(..)
250+ , GenericSnapshotRepo(..)
251+ , SnapshotRepo(..)
252+ , SnapshotRepoConversionError(..)
253+ , SnapshotRepoType(..)
254+ , GenericSnapshotRepoSettings(..)
255+ , SnapshotRepoUpdateSettings(..)
256+ , defaultSnapshotRepoUpdateSettings
257+ , SnapshotRepoName(..)
258+ , SnapshotRepoPattern(..)
259+ , SnapshotVerification(..)
260+ , SnapshotNodeVerification(..)
261+ , FullNodeId(..)
262+ , NodeName(..)
263+ , ClusterName(..)
264+ , NodesInfo(..)
265+ , NodesStats(..)
266+ , NodeStats(..)
267+ , NodeBreakersStats(..)
268+ , NodeBreakerStats(..)
269+ , NodeHTTPStats(..)
270+ , NodeTransportStats(..)
271+ , NodeFSStats(..)
272+ , NodeDataPathStats(..)
273+ , NodeFSTotalStats(..)
274+ , NodeNetworkStats(..)
275+ , NodeThreadPoolsStats(..)
276+ , NodeThreadPoolStats(..)
277+ , NodeJVMStats(..)
278+ , JVMBufferPoolStats(..)
279+ , JVMGCStats(..)
280+ , JVMPoolStats(..)
281+ , NodeProcessStats(..)
282+ , NodeOSStats(..)
283+ , LoadAvgs(..)
284+ , NodeIndicesStats(..)
285+ , EsAddress(..)
286+ , PluginName(..)
287+ , NodeInfo(..)
288+ , NodePluginInfo(..)
289+ , NodeHTTPInfo(..)
290+ , NodeTransportInfo(..)
291+ , BoundTransportAddress(..)
292+ , NodeNetworkInfo(..)
293+ , MacAddress(..)
294+ , NetworkInterfaceName(..)
295+ , NodeNetworkInterface(..)
296+ , NodeThreadPoolsInfo(..)
297+ , NodeThreadPoolInfo(..)
298+ , ThreadPoolSize(..)
299+ , ThreadPoolType(..)
300+ , NodeJVMInfo(..)
301+ , JVMMemoryPool(..)
302+ , JVMGCCollector(..)
303+ , JVMMemoryInfo(..)
304+ , PID(..)
305+ , NodeOSInfo(..)
306+ , CPUInfo(..)
307+ , NodeProcessInfo(..)
308+ , FsSnapshotRepo(..)
309+ , SnapshotCreateSettings(..)
310+ , defaultSnapshotCreateSettings
311+ , SnapshotSelection(..)
312+ , SnapshotPattern(..)
313+ , SnapshotInfo(..)
314+ , SnapshotShardFailure(..)
315+ , ShardId(..)
316+ , SnapshotName(..)
317+ , SnapshotState(..)
318+ , SnapshotRestoreSettings(..)
319+ , defaultSnapshotRestoreSettings
320+ , RestoreRenamePattern(..)
321+ , RestoreRenameToken(..)
322+ , RRGroupRefNum
323+ , rrGroupRefNum
324+ , mkRRGroupRefNum
325+ , RestoreIndexSettings(..)
326+
327+ , Aggregation(..)
328+ , Aggregations
329+ , AggregationResults
330+ , BucketValue(..)
331+ , Bucket(..)
332+ , BucketAggregation(..)
333+ , TermsAggregation(..)
334+ , MissingAggregation(..)
335+ , ValueCountAggregation(..)
336+ , FilterAggregation(..)
337+ , CardinalityAggregation(..)
338+ , DateHistogramAggregation(..)
339+ , DateRangeAggregation(..)
340+ , DateRangeAggRange(..)
341+ , DateMathExpr(..)
342+ , DateMathAnchor(..)
343+ , DateMathModifier(..)
344+ , DateMathUnit(..)
345+ , TopHitsAggregation(..)
346+ , StatisticsAggregation(..)
347+
348+ , Highlights(..)
349+ , FieldHighlight(..)
350+ , HighlightSettings(..)
351+ , PlainHighlight(..)
352+ , PostingsHighlight(..)
353+ , FastVectorHighlight(..)
354+ , CommonHighlight(..)
355+ , NonPostings(..)
356+ , HighlightEncoder(..)
357+ , HighlightTag(..)
358+ , HitHighlight
359+
360+ , MissingResult(..)
361+ , TermsResult(..)
362+ , DateHistogramResult(..)
363+ , DateRangeResult(..)
364+ , TopHitResult(..)
365+
366+ , EsUsername(..)
367+ , EsPassword(..)
368+ ) where
369+
370+import Control.Applicative as A
371+import Control.Arrow (first)
372+import Control.Monad.Catch
373+import Control.Monad.Except
374+import Control.Monad.Reader (MonadReader (..),
375+ ReaderT (..))
376+import Control.Monad.State (MonadState)
377+import Control.Monad.Writer (MonadWriter)
378+import Data.Aeson
379+import Data.Aeson.Types (Pair, Parser,
380+ emptyObject,
381+ parseEither, parseMaybe)
382+import qualified Data.ByteString.Lazy.Char8 as L
383+import Data.Char
384+import Data.Hashable (Hashable)
385+import qualified Data.HashMap.Strict as HM
386+import Data.List (foldl', intercalate,
387+ nub)
388+import Data.List.NonEmpty (NonEmpty (..), toList)
389+import qualified Data.Map.Strict as M
390+import Data.Maybe
391+import Data.Scientific (Scientific)
392+import Data.Semigroup
393+import Data.Text (Text)
394+import qualified Data.Text as T
395+import Data.Time.Calendar
396+import Data.Time.Clock (NominalDiffTime,
397+ UTCTime)
398+import Data.Time.Clock.POSIX
399+import qualified Data.Traversable as DT
400+import Data.Typeable (Typeable)
401+import qualified Data.Vector as V
402+import qualified Data.Version as Vers
403+import GHC.Enum
404+import GHC.Generics (Generic)
405+import Network.HTTP.Client
406+import qualified Network.HTTP.Types.Method as NHTM
407+import qualified Text.ParserCombinators.ReadP as RP
408+import qualified Text.Read as TR
409+
410+import Database.V5.Bloodhound.Types.Class
411+import Database.V5.Bloodhound.Types.Internal
412+
413+-- $setup
414+-- >>> :set -XOverloadedStrings
415+-- >>> import Data.Aeson
416+-- >>> import Database.V5.Bloodhound
417+-- >>> let testServer = (Server "http://localhost:9200")
418+-- >>> let testIndex = IndexName "twitter"
419+-- >>> let testMapping = MappingName "tweet"
420+-- >>> let defaultIndexSettings = IndexSettings (ShardCount 3) (ReplicaCount 2)
421+
422+-- defaultIndexSettings is exported by Database.Bloodhound as well
423+-- no trailing slashes in servers, library handles building the path.
424+
425+-- | Create a 'BHEnv' with all optional fields defaulted. HTTP hook
426+-- will be a noop. You can use the exported fields to customize it further, e.g.:
427+--
428+-- >> (mkBHEnv myServer myManager) { bhRequestHook = customHook }
429+mkBHEnv :: Server -> Manager -> BHEnv
430+mkBHEnv s m = BHEnv s m return
431+
432+newtype BH m a = BH {
433+ unBH :: ReaderT BHEnv m a
434+ } deriving ( Functor
435+ , A.Applicative
436+ , Monad
437+ , MonadIO
438+ , MonadState s
439+ , MonadWriter w
440+ , MonadError e
441+ , Alternative
442+ , MonadPlus
443+ , MonadFix
444+ , MonadThrow
445+ , MonadCatch
446+ , MonadMask)
447+
448+instance MonadTrans BH where
449+ lift = BH . lift
450+
451+instance (MonadReader r m) => MonadReader r (BH m) where
452+ ask = lift ask
453+ local f (BH (ReaderT m)) = BH $ ReaderT $ \r ->
454+ local f (m r)
455+
456+instance (Functor m, Applicative m, MonadIO m) => MonadBH (BH m) where
457+ getBHEnv = BH getBHEnv
458+
459+runBH :: BHEnv -> BH m a -> m a
460+runBH e f = runReaderT (unBH f) e
461+
462+{-| 'Version' is embedded in 'Status' -}
463+data Version = Version { number :: VersionNumber
464+ , build_hash :: BuildHash
465+ , build_date :: UTCTime
466+ , build_snapshot :: Bool
467+ , lucene_version :: VersionNumber } deriving (Eq, Read, Show, Generic, Typeable)
468+
469+-- | Traditional software versioning number
470+newtype VersionNumber = VersionNumber { versionNumber :: Vers.Version
471+ } deriving (Eq, Read, Show, Generic, Typeable, Ord)
472+
473+{-| 'Status' is a data type for describing the JSON body returned by
474+ Elasticsearch when you query its status. This was deprecated in 1.2.0.
475+
476+ <http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-status.html#indices-status>
477+-}
478+
479+data Status = Status { name :: Text
480+ , cluster_name :: Text
481+ , cluster_uuid :: Text
482+ , version :: Version
483+ , tagline :: Text } deriving (Eq, Read, Show, Generic)
484+
485+{-| 'IndexSettings' is used to configure the shards and replicas when you create
486+ an Elasticsearch Index.
487+
488+ <http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-create-index.html>
489+-}
490+
491+data IndexSettings =
492+ IndexSettings { indexShards :: ShardCount
493+ , indexReplicas :: ReplicaCount } deriving (Eq, Read, Show, Generic, Typeable)
494+
495+{-| 'defaultIndexSettings' is an 'IndexSettings' with 3 shards and 2 replicas. -}
496+defaultIndexSettings :: IndexSettings
497+defaultIndexSettings = IndexSettings (ShardCount 3) (ReplicaCount 2)
498+
499+
500+{-| 'ForceMergeIndexSettings' is used to configure index optimization. See
501+ <https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-forcemerge.html>
502+ for more info.
503+-}
504+data ForceMergeIndexSettings =
505+ ForceMergeIndexSettings { maxNumSegments :: Maybe Int
506+ -- ^ Number of segments to optimize to. 1 will fully optimize the index. If omitted, the default behavior is to only optimize if the server deems it necessary.
507+ , onlyExpungeDeletes :: Bool
508+ -- ^ Should the optimize process only expunge segments with deletes in them? If the purpose of the optimization is to free disk space, this should be set to True.
509+ , flushAfterOptimize :: Bool
510+ -- ^ Should a flush be performed after the optimize.
511+ } deriving (Eq, Show, Generic, Typeable)
512+
513+
514+{-| 'defaultForceMergeIndexSettings' implements the default settings that
515+ ElasticSearch uses for index optimization. 'maxNumSegments' is Nothing,
516+ 'onlyExpungeDeletes' is False, and flushAfterOptimize is True.
517+-}
518+defaultForceMergeIndexSettings :: ForceMergeIndexSettings
519+defaultForceMergeIndexSettings = ForceMergeIndexSettings Nothing False True
520+
521+{-| 'UpdatableIndexSetting' are settings which may be updated after an index is created.
522+
523+ <https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-update-settings.html>
524+-}
525+data UpdatableIndexSetting = NumberOfReplicas ReplicaCount
526+ -- ^ The number of replicas each shard has.
527+ | AutoExpandReplicas ReplicaBounds
528+ | BlocksReadOnly Bool
529+ -- ^ Set to True to have the index read only. False to allow writes and metadata changes.
530+ | BlocksRead Bool
531+ -- ^ Set to True to disable read operations against the index.
532+ | BlocksWrite Bool
533+ -- ^ Set to True to disable write operations against the index.
534+ | BlocksMetaData Bool
535+ -- ^ Set to True to disable metadata operations against the index.
536+ | RefreshInterval NominalDiffTime
537+ -- ^ The async refresh interval of a shard
538+ | IndexConcurrency Int
539+ | FailOnMergeFailure Bool
540+ | TranslogFlushThresholdOps Int
541+ -- ^ When to flush on operations.
542+ | TranslogFlushThresholdSize Bytes
543+ -- ^ When to flush based on translog (bytes) size.
544+ | TranslogFlushThresholdPeriod NominalDiffTime
545+ -- ^ When to flush based on a period of not flushing.
546+ | TranslogDisableFlush Bool
547+ -- ^ Disables flushing. Note, should be set for a short interval and then enabled.
548+ | CacheFilterMaxSize (Maybe Bytes)
549+ -- ^ The maximum size of filter cache (per segment in shard).
550+ | CacheFilterExpire (Maybe NominalDiffTime)
551+ -- ^ The expire after access time for filter cache.
552+ | GatewaySnapshotInterval NominalDiffTime
553+ -- ^ The gateway snapshot interval (only applies to shared gateways).
554+ | RoutingAllocationInclude (NonEmpty NodeAttrFilter)
555+ -- ^ A node matching any rule will be allowed to host shards from the index.
556+ | RoutingAllocationExclude (NonEmpty NodeAttrFilter)
557+ -- ^ A node matching any rule will NOT be allowed to host shards from the index.
558+ | RoutingAllocationRequire (NonEmpty NodeAttrFilter)
559+ -- ^ Only nodes matching all rules will be allowed to host shards from the index.
560+ | RoutingAllocationEnable AllocationPolicy
561+ -- ^ Enables shard allocation for a specific index.
562+ | RoutingAllocationShardsPerNode ShardCount
563+ -- ^ Controls the total number of shards (replicas and primaries) allowed to be allocated on a single node.
564+ | RecoveryInitialShards InitialShardCount
565+ -- ^ When using local gateway a particular shard is recovered only if there can be allocated quorum shards in the cluster.
566+ | GCDeletes NominalDiffTime
567+ | TTLDisablePurge Bool
568+ -- ^ Disables temporarily the purge of expired docs.
569+ | TranslogFSType FSType
570+ | IndexCompoundFormat CompoundFormat
571+ | IndexCompoundOnFlush Bool
572+ | WarmerEnabled Bool
573+ deriving (Eq, Show, Generic, Typeable)
574+
575+data AllocationPolicy = AllocAll
576+ -- ^ Allows shard allocation for all shards.
577+ | AllocPrimaries
578+ -- ^ Allows shard allocation only for primary shards.
579+ | AllocNewPrimaries
580+ -- ^ Allows shard allocation only for primary shards for new indices.
581+ | AllocNone
582+ -- ^ No shard allocation is allowed
583+ deriving (Eq, Read, Show, Generic, Typeable)
584+
585+data ReplicaBounds = ReplicasBounded Int Int
586+ | ReplicasLowerBounded Int
587+ | ReplicasUnbounded
588+ deriving (Eq, Read, Show, Generic, Typeable)
589+
590+-- | A measure of bytes used for various configurations. You may want
591+-- to use smart constructors like 'gigabytes' for larger values.
592+--
593+-- >>> gigabytes 9
594+-- Bytes 9000000000
595+--
596+-- >>> megabytes 9
597+-- Bytes 9000000
598+--
599+-- >>> kilobytes 9
600+-- Bytes 9000
601+newtype Bytes = Bytes Int deriving (Eq, Read, Show, Generic, Typeable, Ord, ToJSON, FromJSON)
602+
603+gigabytes :: Int -> Bytes
604+gigabytes n = megabytes (1000 * n)
605+
606+
607+megabytes :: Int -> Bytes
608+megabytes n = kilobytes (1000 * n)
609+
610+
611+kilobytes :: Int -> Bytes
612+kilobytes n = Bytes (1000 * n)
613+
614+
615+data FSType = FSSimple
616+ | FSBuffered deriving (Eq, Read, Show, Generic, Typeable, Ord)
617+
618+data InitialShardCount = QuorumShards
619+ | QuorumMinus1Shards
620+ | FullShards
621+ | FullMinus1Shards
622+ | ExplicitShards Int
623+ deriving (Eq, Read, Show, Generic, Typeable)
624+
625+data NodeAttrFilter = NodeAttrFilter { nodeAttrFilterName :: NodeAttrName
626+ , nodeAttrFilterValues :: NonEmpty Text}
627+ deriving (Eq, Read, Show, Generic, Ord, Typeable)
628+
629+newtype NodeAttrName = NodeAttrName Text deriving (Eq, Read, Show, Ord, Generic, Typeable)
630+
631+data CompoundFormat = CompoundFileFormat Bool
632+ | MergeSegmentVsTotalIndex Double
633+ -- ^ percentage between 0 and 1 where 0 is false, 1 is true
634+ deriving (Eq, Read, Show, Generic, Typeable)
635+
636+newtype NominalDiffTimeJSON = NominalDiffTimeJSON { ndtJSON :: NominalDiffTime }
637+
638+data IndexSettingsSummary = IndexSettingsSummary { sSummaryIndexName :: IndexName
639+ , sSummaryFixedSettings :: IndexSettings
640+ , sSummaryUpdateable :: [UpdatableIndexSetting]}
641+ deriving (Eq, Show, Generic, Typeable)
642+
643+{-| 'Reply' and 'Method' are type synonyms from 'Network.HTTP.Types.Method.Method' -}
644+type Reply = Network.HTTP.Client.Response L.ByteString
645+type Method = NHTM.Method
646+
647+{-| 'OpenCloseIndex' is a sum type for opening and closing indices.
648+
649+ <http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-open-close.html>
650+-}
651+data OpenCloseIndex = OpenIndex | CloseIndex deriving (Eq, Read, Show, Generic, Typeable)
652+
653+data FieldType = GeoPointType
654+ | GeoShapeType
655+ | FloatType
656+ | IntegerType
657+ | LongType
658+ | ShortType
659+ | ByteType deriving (Eq, Read, Show, Generic, Typeable)
660+
661+data FieldDefinition =
662+ FieldDefinition { fieldType :: FieldType } deriving (Eq, Read, Show, Generic, Typeable)
663+
664+{-| An 'IndexTemplate' defines a template that will automatically be
665+ applied to new indices created. The templates include both
666+ 'IndexSettings' and mappings, and a simple 'TemplatePattern' that
667+ controls if the template will be applied to the index created.
668+ Specify mappings as follows: @[toJSON TweetMapping, ...]@
669+
670+ https://www.elastic.co/guide/en/elasticsearch/reference/1.7/indices-templates.html
671+-}
672+data IndexTemplate =
673+ IndexTemplate { templatePattern :: TemplatePattern
674+ , templateSettings :: Maybe IndexSettings
675+ , templateMappings :: [Value]
676+ }
677+
678+data MappingField =
679+ MappingField { mappingFieldName :: FieldName
680+ , fieldDefinition :: FieldDefinition } deriving (Eq, Read, Show, Generic, Typeable)
681+
682+{-| Support for type reification of 'Mapping's is currently incomplete, for
683+ now the mapping API verbiage expects a 'ToJSON'able blob.
684+
685+ Indexes have mappings, mappings are schemas for the documents contained in the
686+ index. I'd recommend having only one mapping per index, always having a mapping,
687+ and keeping different kinds of documents separated if possible.
688+-}
689+data Mapping = Mapping { typeName :: TypeName
690+ , mappingFields :: [MappingField] } deriving (Eq, Read, Show, Generic, Typeable)
691+
692+{-| 'BulkOperation' is a sum type for expressing the four kinds of bulk
693+ operation index, create, delete, and update. 'BulkIndex' behaves like an
694+ "upsert", 'BulkCreate' will fail if a document already exists at the DocId.
695+
696+ <http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-bulk.html#docs-bulk>
697+-}
698+data BulkOperation =
699+ BulkIndex IndexName MappingName DocId Value
700+ | BulkCreate IndexName MappingName DocId Value
701+ | BulkDelete IndexName MappingName DocId
702+ | BulkUpdate IndexName MappingName DocId Value deriving (Eq, Read, Show, Generic, Typeable)
703+
704+{-| 'EsResult' describes the standard wrapper JSON document that you see in
705+ successful Elasticsearch lookups or lookups that couldn't find the document.
706+-}
707+data EsResult a = EsResult { _index :: Text
708+ , _type :: Text
709+ , _id :: Text
710+ , foundResult :: Maybe (EsResultFound a)} deriving (Eq, Read, Show, Generic, Typeable)
711+
712+{-| 'EsResultFound' contains the document and its metadata inside of an
713+ 'EsResult' when the document was successfully found.
714+-}
715+data EsResultFound a = EsResultFound { _version :: DocVersion
716+ , _source :: a } deriving (Eq, Read, Show, Generic, Typeable)
717+
718+{-| 'EsError' is the generic type that will be returned when there was a
719+ problem. If you can't parse the expected response, its a good idea to
720+ try parsing this.
721+-}
722+data EsError = EsError { errorStatus :: Int
723+ , errorMessage :: Text } deriving (Eq, Read, Show, Generic, Typeable)
724+
725+{-| 'EsProtocolException' will be thrown if Bloodhound cannot parse a response
726+returned by the ElasticSearch server. If you encounter this error, please
727+verify that your domain data types and FromJSON instances are working properly
728+(for example, the 'a' of '[Hit a]' in 'SearchResult.searchHits.hits'). If you're
729+sure that your mappings are correct, then this error may be an indication of an
730+incompatibility between Bloodhound and ElasticSearch. Please open a bug report
731+and be sure to include the exception body.
732+-}
733+data EsProtocolException = EsProtocolException { esProtoExBody :: L.ByteString }
734+ deriving (Eq, Read, Show, Generic, Typeable)
735+
736+instance Exception EsProtocolException
737+
738+data IndexAlias = IndexAlias { srcIndex :: IndexName
739+ , indexAlias :: IndexAliasName } deriving (Eq, Read, Show, Generic, Typeable)
740+
741+newtype IndexAliasName = IndexAliasName { indexAliasName :: IndexName } deriving (Eq, Read, Show, Generic, ToJSON)
742+
743+data IndexAliasAction = AddAlias IndexAlias IndexAliasCreate
744+ | RemoveAlias IndexAlias deriving (Read, Show, Eq, Generic, Typeable)
745+
746+data IndexAliasCreate = IndexAliasCreate { aliasCreateRouting :: Maybe AliasRouting
747+ , aliasCreateFilter :: Maybe Filter}
748+ deriving (Read, Show, Eq, Generic, Typeable)
749+
750+data AliasRouting = AllAliasRouting RoutingValue
751+ | GranularAliasRouting (Maybe SearchAliasRouting) (Maybe IndexAliasRouting)
752+ deriving (Read, Show, Eq, Generic, Typeable)
753+
754+newtype SearchAliasRouting = SearchAliasRouting (NonEmpty RoutingValue) deriving (Read, Show, Eq, Generic, Typeable)
755+
756+newtype IndexAliasRouting = IndexAliasRouting RoutingValue deriving (Read, Show, Eq, Generic, ToJSON, FromJSON, Typeable)
757+
758+newtype RoutingValue = RoutingValue { routingValue :: Text } deriving (Read, Show, Eq, Generic, ToJSON, FromJSON, Typeable)
759+
760+newtype IndexAliasesSummary = IndexAliasesSummary { indexAliasesSummary :: [IndexAliasSummary] } deriving (Read, Show, Eq, Generic, Typeable)
761+
762+{-| 'IndexAliasSummary' is a summary of an index alias configured for a server. -}
763+data IndexAliasSummary = IndexAliasSummary { indexAliasSummaryAlias :: IndexAlias
764+ , indexAliasSummaryCreate :: IndexAliasCreate} deriving (Read, Show, Eq, Generic, Typeable)
765+
766+{-| 'DocVersion' is an integer version number for a document between 1
767+and 9.2e+18 used for <<https://www.elastic.co/guide/en/elasticsearch/guide/current/optimistic-concurrency-control.html optimistic concurrency control>>.
768+-}
769+newtype DocVersion = DocVersion {
770+ docVersionNumber :: Int
771+ } deriving (Eq, Read, Show, Generic, Ord, ToJSON)
772+
773+-- | Smart constructor for in-range doc version
774+mkDocVersion :: Int -> Maybe DocVersion
775+mkDocVersion i
776+ | i >= (docVersionNumber minBound) && i <= (docVersionNumber maxBound) =
777+ Just $ DocVersion i
778+ | otherwise = Nothing
779+
780+
781+{-| 'ExternalDocVersion' is a convenience wrapper if your code uses its
782+own version numbers instead of ones from ES.
783+-}
784+newtype ExternalDocVersion = ExternalDocVersion DocVersion
785+ deriving (Eq, Read, Show, Generic, Ord, Bounded, Enum, ToJSON)
786+
787+{-| 'VersionControl' is specified when indexing documents as a
788+optimistic concurrency control.
789+-}
790+data VersionControl = NoVersionControl
791+ -- ^ Don't send a version. This is a pure overwrite.
792+ | InternalVersion DocVersion
793+ -- ^ Use the default ES versioning scheme. Only
794+ -- index the document if the version is the same
795+ -- as the one specified. Only applicable to
796+ -- updates, as you should be getting Version from
797+ -- a search result.
798+ | ExternalGT ExternalDocVersion
799+ -- ^ Use your own version numbering. Only index
800+ -- the document if the version is strictly higher
801+ -- OR the document doesn't exist. The given
802+ -- version will be used as the new version number
803+ -- for the stored document. N.B. All updates must
804+ -- increment this number, meaning there is some
805+ -- global, external ordering of updates.
806+ | ExternalGTE ExternalDocVersion
807+ -- ^ Use your own version numbering. Only index
808+ -- the document if the version is equal or higher
809+ -- than the stored version. Will succeed if there
810+ -- is no existing document. The given version will
811+ -- be used as the new version number for the
812+ -- stored document. Use with care, as this could
813+ -- result in data loss.
814+ | ForceVersion ExternalDocVersion
815+ -- ^ The document will always be indexed and the
816+ -- given version will be the new version. This is
817+ -- typically used for correcting errors. Use with
818+ -- care, as this could result in data loss.
819+ deriving (Read, Show, Eq, Generic, Ord)
820+
821+{-| 'DocumentParent' is used to specify a parent document.
822+-}
823+newtype DocumentParent = DocumentParent DocId
824+ deriving (Eq, Read, Show, Generic, Typeable)
825+
826+{-| 'IndexDocumentSettings' are special settings supplied when indexing
827+a document. For the best backwards compatiblity when new fields are
828+added, you should probably prefer to start with 'defaultIndexDocumentSettings'
829+-}
830+data IndexDocumentSettings =
831+ IndexDocumentSettings { idsVersionControl :: VersionControl
832+ , idsParent :: Maybe DocumentParent
833+ } deriving (Eq, Read, Show, Generic, Typeable)
834+
835+{-| Reasonable default settings. Chooses no version control and no parent.
836+-}
837+defaultIndexDocumentSettings :: IndexDocumentSettings
838+defaultIndexDocumentSettings = IndexDocumentSettings NoVersionControl Nothing
839+
840+{-| 'Sort' is a synonym for a list of 'SortSpec's. Sort behavior is order
841+ dependent with later sorts acting as tie-breakers for earlier sorts.
842+-}
843+type Sort = [SortSpec]
844+
845+{-| The two main kinds of 'SortSpec' are 'DefaultSortSpec' and
846+ 'GeoDistanceSortSpec'. The latter takes a 'SortOrder', 'GeoPoint', and
847+ 'DistanceUnit' to express "nearness" to a single geographical point as a
848+ sort specification.
849+
850+<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-sort.html#search-request-sort>
851+-}
852+data SortSpec = DefaultSortSpec DefaultSort
853+ | GeoDistanceSortSpec SortOrder GeoPoint DistanceUnit deriving (Eq, Read, Show, Generic, Typeable)
854+
855+{-| 'DefaultSort' is usually the kind of 'SortSpec' you'll want. There's a
856+ 'mkSort' convenience function for when you want to specify only the most
857+ common parameters.
858+
859+ The `ignoreUnmapped`, when `Just` field is used to set the elastic 'unmapped_type'
860+
861+<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-sort.html#search-request-sort>
862+-}
863+data DefaultSort =
864+ DefaultSort { sortFieldName :: FieldName
865+ , sortOrder :: SortOrder
866+ -- default False
867+ , ignoreUnmapped :: Maybe Text
868+ , sortMode :: Maybe SortMode
869+ , missingSort :: Maybe Missing
870+ , nestedFilter :: Maybe Filter } deriving (Eq, Read, Show, Generic, Typeable)
871+
872+{-| 'SortOrder' is 'Ascending' or 'Descending', as you might expect. These get
873+ encoded into "asc" or "desc" when turned into JSON.
874+
875+<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-sort.html#search-request-sort>
876+-}
877+data SortOrder = Ascending
878+ | Descending deriving (Eq, Read, Show, Generic, Typeable)
879+
880+{-| 'Missing' prescribes how to handle missing fields. A missing field can be
881+ sorted last, first, or using a custom value as a substitute.
882+
883+<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-sort.html#_missing_values>
884+-}
885+data Missing = LastMissing
886+ | FirstMissing
887+ | CustomMissing Text deriving (Eq, Read, Show, Generic, Typeable)
888+
889+{-| 'SortMode' prescribes how to handle sorting array/multi-valued fields.
890+
891+http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-sort.html#_sort_mode_option
892+-}
893+data SortMode = SortMin
894+ | SortMax
895+ | SortSum
896+ | SortAvg deriving (Eq, Read, Show, Generic, Typeable)
897+
898+{-| 'mkSort' defaults everything but the 'FieldName' and the 'SortOrder' so
899+ that you can concisely describe the usual kind of 'SortSpec's you want.
900+-}
901+mkSort :: FieldName -> SortOrder -> DefaultSort
902+mkSort fieldName sOrder = DefaultSort fieldName sOrder Nothing Nothing Nothing Nothing
903+
904+{-| 'Cache' is for telling ES whether it should cache a 'Filter' not.
905+ 'Query's cannot be cached.
906+-}
907+type Cache = Bool -- caching on/off
908+defaultCache :: Cache
909+defaultCache = False
910+
911+{-| 'PrefixValue' is used in 'PrefixQuery' as the main query component.
912+-}
913+-- type PrefixValue = Text
914+
915+{-| 'BooleanOperator' is the usual And/Or operators with an ES compatible
916+ JSON encoding baked in. Used all over the place.
917+-}
918+data BooleanOperator = And | Or deriving (Eq, Read, Show, Generic, Typeable)
919+
920+{-| 'ShardCount' is part of 'IndexSettings'
921+-}
922+newtype ShardCount = ShardCount Int deriving (Eq, Read, Show, Generic, ToJSON, Typeable)
923+
924+{-| 'ReplicaCount' is part of 'IndexSettings'
925+-}
926+newtype ReplicaCount = ReplicaCount Int deriving (Eq, Read, Show, Generic, ToJSON, Typeable)
927+
928+{-| 'IndexName' is used to describe which index to query/create/delete
929+-}
930+newtype IndexName = IndexName Text deriving (Eq, Generic, Read, Show, ToJSON, FromJSON, Typeable)
931+
932+{-| 'IndexSelection' is used for APIs which take a single index, a list of
933+ indexes, or the special @_all@ index.
934+-}
935+--TODO: this does not fully support <https://www.elastic.co/guide/en/elasticsearch/reference/1.7/multi-index.html multi-index syntax>. It wouldn't be too hard to implement but you'd have to add the optional parameters (ignore_unavailable, allow_no_indices, expand_wildcards) to any APIs using it. Also would be a breaking API.
936+data IndexSelection = IndexList (NonEmpty IndexName)
937+ | AllIndexes deriving (Eq, Generic, Show, Typeable)
938+
939+{-| 'NodeSelection' is used for most cluster APIs. See <https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster.html#cluster-nodes here> for more details.
940+-}
941+data NodeSelection = LocalNode
942+ -- ^ Whatever node receives this request
943+ | NodeList (NonEmpty NodeSelector)
944+ | AllNodes deriving (Eq, Generic, Show, Typeable)
945+
946+
947+-- | An exact match or pattern to identify a node. Note that All of
948+-- these options support wildcarding, so your node name, server, attr
949+-- name can all contain * characters to be a fuzzy match.
950+data NodeSelector = NodeByName NodeName
951+ | NodeByFullNodeId FullNodeId
952+ | NodeByHost Server
953+ -- ^ e.g. 10.0.0.1 or even 10.0.0.*
954+ | NodeByAttribute NodeAttrName Text
955+ -- ^ NodeAttrName can be a pattern, e.g. rack*. The value can too.
956+ deriving (Eq, Generic, Show, Typeable)
957+
958+{-| 'TemplateName' is used to describe which template to query/create/delete
959+-}
960+newtype TemplateName = TemplateName Text deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
961+
962+{-| 'TemplatePattern' represents a pattern which is matched against index names
963+-}
964+newtype TemplatePattern = TemplatePattern Text deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
965+
966+{-| 'MappingName' is part of mappings which are how ES describes and schematizes
967+ the data in the indices.
968+-}
969+newtype MappingName = MappingName Text deriving (Eq, Generic, Read, Show, ToJSON, FromJSON, Typeable)
970+
971+{-| 'DocId' is a generic wrapper value for expressing unique Document IDs.
972+ Can be set by the user or created by ES itself. Often used in client
973+ functions for poking at specific documents.
974+-}
975+newtype DocId = DocId Text deriving (Eq, Generic, Read, Show, ToJSON, FromJSON, Typeable)
976+
977+{-| 'QueryString' is used to wrap query text bodies, be they human written or not.
978+-}
979+newtype QueryString = QueryString Text deriving (Eq, Generic, Read, Show, ToJSON, FromJSON, Typeable)
980+
981+{-| 'FieldName' is used all over the place wherever a specific field within
982+ a document needs to be specified, usually in 'Query's or 'Filter's.
983+-}
984+newtype FieldName = FieldName Text deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
985+
986+
987+{-| 'Script' is often used in place of 'FieldName' to specify more
988+complex ways of extracting a value from a document.
989+-}
990+newtype Script = Script { scriptText :: Text } deriving (Eq, Read, Show, Generic, Typeable)
991+
992+{-| 'CacheName' is used in 'RegexpFilter' for describing the
993+ 'CacheKey' keyed caching behavior.
994+-}
995+newtype CacheName = CacheName Text deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
996+
997+{-| 'CacheKey' is used in 'RegexpFilter' to key regex caching.
998+-}
999+newtype CacheKey =
1000+ CacheKey Text deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1001+newtype Existence =
1002+ Existence Bool deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1003+newtype NullValue =
1004+ NullValue Bool deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1005+newtype CutoffFrequency =
1006+ CutoffFrequency Double deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1007+newtype Analyzer =
1008+ Analyzer Text deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1009+newtype MaxExpansions =
1010+ MaxExpansions Int deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1011+
1012+{-| 'Lenient', if set to true, will cause format based failures to be
1013+ ignored. I don't know what the bloody default is, Elasticsearch
1014+ documentation didn't say what it was. Let me know if you figure it out.
1015+-}
1016+newtype Lenient =
1017+ Lenient Bool deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1018+newtype Tiebreaker =
1019+ Tiebreaker Double deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1020+newtype Boost =
1021+ Boost Double deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1022+newtype BoostTerms =
1023+ BoostTerms Double deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1024+
1025+{-| 'MinimumMatch' controls how many should clauses in the bool query should
1026+ match. Can be an absolute value (2) or a percentage (30%) or a
1027+ combination of both.
1028+-}
1029+newtype MinimumMatch =
1030+ MinimumMatch Int deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1031+newtype DisableCoord =
1032+ DisableCoord Bool deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1033+newtype IgnoreTermFrequency =
1034+ IgnoreTermFrequency Bool deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1035+newtype MinimumTermFrequency =
1036+ MinimumTermFrequency Int deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1037+newtype MaxQueryTerms =
1038+ MaxQueryTerms Int deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1039+newtype Fuzziness =
1040+ Fuzziness Double deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1041+
1042+{-| 'PrefixLength' is the prefix length used in queries, defaults to 0. -}
1043+newtype PrefixLength =
1044+ PrefixLength Int deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1045+newtype TypeName =
1046+ TypeName Text deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1047+newtype PercentMatch =
1048+ PercentMatch Double deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1049+newtype StopWord =
1050+ StopWord Text deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1051+newtype QueryPath =
1052+ QueryPath Text deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1053+
1054+{-| Allowing a wildcard at the beginning of a word (eg "*ing") is particularly
1055+ heavy, because all terms in the index need to be examined, just in case
1056+ they match. Leading wildcards can be disabled by setting
1057+ 'AllowLeadingWildcard' to false. -}
1058+newtype AllowLeadingWildcard =
1059+ AllowLeadingWildcard Bool deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1060+newtype LowercaseExpanded =
1061+ LowercaseExpanded Bool deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1062+newtype EnablePositionIncrements =
1063+ EnablePositionIncrements Bool deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1064+
1065+{-| By default, wildcard terms in a query are not analyzed.
1066+ Setting 'AnalyzeWildcard' to true enables best-effort analysis.
1067+-}
1068+newtype AnalyzeWildcard = AnalyzeWildcard Bool deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1069+
1070+{-| 'GeneratePhraseQueries' defaults to false.
1071+-}
1072+newtype GeneratePhraseQueries =
1073+ GeneratePhraseQueries Bool deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1074+
1075+{-| 'Locale' is used for string conversions - defaults to ROOT.
1076+-}
1077+newtype Locale = Locale Text deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1078+newtype MaxWordLength = MaxWordLength Int deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1079+newtype MinWordLength = MinWordLength Int deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1080+
1081+{-| 'PhraseSlop' sets the default slop for phrases, 0 means exact
1082+ phrase matches. Default is 0.
1083+-}
1084+newtype PhraseSlop = PhraseSlop Int deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1085+newtype MinDocFrequency = MinDocFrequency Int deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1086+newtype MaxDocFrequency = MaxDocFrequency Int deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1087+
1088+-- | Newtype wrapper to parse ES's concerning tendency to in some APIs return a floating point number of milliseconds since epoch ಠ_ಠ
1089+newtype POSIXMS = POSIXMS { posixMS :: UTCTime }
1090+
1091+{-| 'unpackId' is a silly convenience function that gets used once.
1092+-}
1093+unpackId :: DocId -> Text
1094+unpackId (DocId docId) = docId
1095+
1096+type TrackSortScores = Bool
1097+newtype From = From Int deriving (Eq, Read, Show, Generic, ToJSON)
1098+newtype Size = Size Int deriving (Eq, Read, Show, Generic, ToJSON)
1099+
1100+data Search = Search { queryBody :: Maybe Query
1101+ , filterBody :: Maybe Filter
1102+ , sortBody :: Maybe Sort
1103+ , aggBody :: Maybe Aggregations
1104+ , highlight :: Maybe Highlights
1105+ -- default False
1106+ , trackSortScores :: TrackSortScores
1107+ , from :: From
1108+ , size :: Size
1109+ , searchType :: SearchType
1110+ , fields :: Maybe [FieldName]
1111+ , source :: Maybe Source } deriving (Eq, Read, Show, Generic, Typeable)
1112+
1113+data SearchType = SearchTypeQueryThenFetch
1114+ | SearchTypeDfsQueryThenFetch
1115+ deriving (Eq, Read, Show, Generic, Typeable)
1116+
1117+data Source =
1118+ NoSource
1119+ | SourcePatterns PatternOrPatterns
1120+ | SourceIncludeExclude Include Exclude
1121+ deriving (Read, Show, Eq, Generic, Typeable)
1122+
1123+data PatternOrPatterns = PopPattern Pattern
1124+ | PopPatterns [Pattern] deriving (Eq, Read, Show, Generic, Typeable)
1125+
1126+data Include = Include [Pattern] deriving (Eq, Read, Show, Generic, Typeable)
1127+data Exclude = Exclude [Pattern] deriving (Eq, Read, Show, Generic, Typeable)
1128+
1129+newtype Pattern = Pattern Text deriving (Eq, Read, Show, Generic, Typeable)
1130+
1131+data Highlights = Highlights { globalsettings :: Maybe HighlightSettings
1132+ , highlightFields :: [FieldHighlight]
1133+ } deriving (Read, Show, Eq, Generic, Typeable)
1134+
1135+data FieldHighlight = FieldHighlight FieldName (Maybe HighlightSettings)
1136+ deriving (Read, Show, Eq, Generic, Typeable)
1137+
1138+
1139+data HighlightSettings = Plain PlainHighlight
1140+ | Postings PostingsHighlight
1141+ | FastVector FastVectorHighlight
1142+ deriving (Read, Show, Eq, Generic, Typeable)
1143+data PlainHighlight =
1144+ PlainHighlight { plainCommon :: Maybe CommonHighlight
1145+ , plainNonPost :: Maybe NonPostings } deriving (Read, Show, Eq, Generic, Typeable)
1146+
1147+ -- This requires that index_options are set to 'offset' in the mapping.
1148+data PostingsHighlight = PostingsHighlight (Maybe CommonHighlight) deriving (Read, Show, Eq, Generic, Typeable)
1149+
1150+-- This requires that term_vector is set to 'with_positions_offsets' in the mapping.
1151+data FastVectorHighlight =
1152+ FastVectorHighlight { fvCommon :: Maybe CommonHighlight
1153+ , fvNonPostSettings :: Maybe NonPostings
1154+ , boundaryChars :: Maybe Text
1155+ , boundaryMaxScan :: Maybe Int
1156+ , fragmentOffset :: Maybe Int
1157+ , matchedFields :: [Text]
1158+ , phraseLimit :: Maybe Int
1159+ } deriving (Read, Show, Eq, Generic, Typeable)
1160+
1161+data CommonHighlight =
1162+ CommonHighlight { order :: Maybe Text
1163+ , forceSource :: Maybe Bool
1164+ , tag :: Maybe HighlightTag
1165+ , encoder :: Maybe HighlightEncoder
1166+ , noMatchSize :: Maybe Int
1167+ , highlightQuery :: Maybe Query
1168+ , requireFieldMatch :: Maybe Bool
1169+ } deriving (Read, Show, Eq, Generic, Typeable)
1170+
1171+-- Settings that are only applicable to FastVector and Plain highlighters.
1172+data NonPostings =
1173+ NonPostings { fragmentSize :: Maybe Int
1174+ , numberOfFragments :: Maybe Int} deriving (Read, Show, Eq, Generic, Typeable)
1175+
1176+data HighlightEncoder = DefaultEncoder
1177+ | HTMLEncoder
1178+ deriving (Read, Show, Eq, Generic, Typeable)
1179+
1180+-- NOTE: Should the tags use some kind of HTML type, rather than Text?
1181+data HighlightTag = TagSchema Text
1182+ | CustomTags ([Text], [Text]) -- Only uses more than the first value in the lists if fvh
1183+ deriving (Read, Show, Eq, Generic, Typeable)
1184+
1185+
1186+data Query =
1187+ TermQuery Term (Maybe Boost)
1188+ | TermsQuery Text (NonEmpty Text)
1189+ | QueryMatchQuery MatchQuery
1190+ | QueryMultiMatchQuery MultiMatchQuery
1191+ | QueryBoolQuery BoolQuery
1192+ | QueryBoostingQuery BoostingQuery
1193+ | QueryCommonTermsQuery CommonTermsQuery
1194+ | ConstantScoreQuery Query Boost
1195+ | QueryDisMaxQuery DisMaxQuery
1196+ | QueryFuzzyLikeThisQuery FuzzyLikeThisQuery
1197+ | QueryFuzzyLikeFieldQuery FuzzyLikeFieldQuery
1198+ | QueryFuzzyQuery FuzzyQuery
1199+ | QueryHasChildQuery HasChildQuery
1200+ | QueryHasParentQuery HasParentQuery
1201+ | IdsQuery MappingName [DocId]
1202+ | QueryIndicesQuery IndicesQuery
1203+ | MatchAllQuery (Maybe Boost)
1204+ | QueryMoreLikeThisQuery MoreLikeThisQuery
1205+ | QueryMoreLikeThisFieldQuery MoreLikeThisFieldQuery
1206+ | QueryNestedQuery NestedQuery
1207+ | QueryPrefixQuery PrefixQuery
1208+ | QueryQueryStringQuery QueryStringQuery
1209+ | QuerySimpleQueryStringQuery SimpleQueryStringQuery
1210+ | QueryRangeQuery RangeQuery
1211+ | QueryRegexpQuery RegexpQuery
1212+ | QueryExistsQuery FieldName
1213+ | QueryMatchNoneQuery
1214+ deriving (Eq, Read, Show, Generic, Typeable)
1215+
1216+-- | As of Elastic 2.0, 'Filters' are just 'Queries' housed in a Bool Query, and
1217+-- flagged in a different context.
1218+newtype Filter = Filter { unFilter :: Query }
1219+ deriving (Eq, Read, Show, Generic, Typeable)
1220+
1221+instance ToJSON Filter where
1222+ toJSON = toJSON . unFilter
1223+
1224+instance FromJSON Filter where
1225+ parseJSON v = Filter <$> parseJSON v
1226+
1227+data RegexpQuery =
1228+ RegexpQuery { regexpQueryField :: FieldName
1229+ , regexpQuery :: Regexp
1230+ , regexpQueryFlags :: RegexpFlags
1231+ , regexpQueryBoost :: Maybe Boost
1232+ } deriving (Eq, Read, Show, Generic, Typeable)
1233+
1234+data RangeQuery =
1235+ RangeQuery { rangeQueryField :: FieldName
1236+ , rangeQueryRange :: RangeValue
1237+ , rangeQueryBoost :: Boost } deriving (Eq, Read, Show, Generic, Typeable)
1238+
1239+mkRangeQuery :: FieldName -> RangeValue -> RangeQuery
1240+mkRangeQuery f r = RangeQuery f r (Boost 1.0)
1241+
1242+data SimpleQueryStringQuery =
1243+ SimpleQueryStringQuery
1244+ { simpleQueryStringQuery :: QueryString
1245+ , simpleQueryStringField :: Maybe FieldOrFields
1246+ , simpleQueryStringOperator :: Maybe BooleanOperator
1247+ , simpleQueryStringAnalyzer :: Maybe Analyzer
1248+ , simpleQueryStringFlags :: Maybe (NonEmpty SimpleQueryFlag)
1249+ , simpleQueryStringLowercaseExpanded :: Maybe LowercaseExpanded
1250+ , simpleQueryStringLocale :: Maybe Locale
1251+ } deriving (Eq, Read, Show, Generic, Typeable)
1252+
1253+data SimpleQueryFlag =
1254+ SimpleQueryAll
1255+ | SimpleQueryNone
1256+ | SimpleQueryAnd
1257+ | SimpleQueryOr
1258+ | SimpleQueryPrefix
1259+ | SimpleQueryPhrase
1260+ | SimpleQueryPrecedence
1261+ | SimpleQueryEscape
1262+ | SimpleQueryWhitespace
1263+ | SimpleQueryFuzzy
1264+ | SimpleQueryNear
1265+ | SimpleQuerySlop deriving (Eq, Read, Show, Generic, Typeable)
1266+
1267+-- use_dis_max and tie_breaker when fields are plural?
1268+data QueryStringQuery =
1269+ QueryStringQuery
1270+ { queryStringQuery :: QueryString
1271+ , queryStringDefaultField :: Maybe FieldName
1272+ , queryStringOperator :: Maybe BooleanOperator
1273+ , queryStringAnalyzer :: Maybe Analyzer
1274+ , queryStringAllowLeadingWildcard :: Maybe AllowLeadingWildcard
1275+ , queryStringLowercaseExpanded :: Maybe LowercaseExpanded
1276+ , queryStringEnablePositionIncrements :: Maybe EnablePositionIncrements
1277+ , queryStringFuzzyMaxExpansions :: Maybe MaxExpansions
1278+ , queryStringFuzziness :: Maybe Fuzziness
1279+ , queryStringFuzzyPrefixLength :: Maybe PrefixLength
1280+ , queryStringPhraseSlop :: Maybe PhraseSlop
1281+ , queryStringBoost :: Maybe Boost
1282+ , queryStringAnalyzeWildcard :: Maybe AnalyzeWildcard
1283+ , queryStringGeneratePhraseQueries :: Maybe GeneratePhraseQueries
1284+ , queryStringMinimumShouldMatch :: Maybe MinimumMatch
1285+ , queryStringLenient :: Maybe Lenient
1286+ , queryStringLocale :: Maybe Locale
1287+ } deriving (Eq, Read, Show, Generic, Typeable)
1288+
1289+mkQueryStringQuery :: QueryString -> QueryStringQuery
1290+mkQueryStringQuery qs =
1291+ QueryStringQuery qs Nothing Nothing
1292+ Nothing Nothing Nothing Nothing
1293+ Nothing Nothing Nothing Nothing
1294+ Nothing Nothing Nothing Nothing
1295+ Nothing Nothing
1296+
1297+data FieldOrFields = FofField FieldName
1298+ | FofFields (NonEmpty FieldName) deriving (Eq, Read, Show, Generic, Typeable)
1299+
1300+data PrefixQuery =
1301+ PrefixQuery
1302+ { prefixQueryField :: FieldName
1303+ , prefixQueryPrefixValue :: Text
1304+ , prefixQueryBoost :: Maybe Boost } deriving (Eq, Read, Show, Generic, Typeable)
1305+
1306+data NestedQuery =
1307+ NestedQuery
1308+ { nestedQueryPath :: QueryPath
1309+ , nestedQueryScoreType :: ScoreType
1310+ , nestedQuery :: Query } deriving (Eq, Read, Show, Generic, Typeable)
1311+
1312+data MoreLikeThisFieldQuery =
1313+ MoreLikeThisFieldQuery
1314+ { moreLikeThisFieldText :: Text
1315+ , moreLikeThisFieldFields :: FieldName
1316+ -- default 0.3 (30%)
1317+ , moreLikeThisFieldPercentMatch :: Maybe PercentMatch
1318+ , moreLikeThisFieldMinimumTermFreq :: Maybe MinimumTermFrequency
1319+ , moreLikeThisFieldMaxQueryTerms :: Maybe MaxQueryTerms
1320+ , moreLikeThisFieldStopWords :: Maybe (NonEmpty StopWord)
1321+ , moreLikeThisFieldMinDocFrequency :: Maybe MinDocFrequency
1322+ , moreLikeThisFieldMaxDocFrequency :: Maybe MaxDocFrequency
1323+ , moreLikeThisFieldMinWordLength :: Maybe MinWordLength
1324+ , moreLikeThisFieldMaxWordLength :: Maybe MaxWordLength
1325+ , moreLikeThisFieldBoostTerms :: Maybe BoostTerms
1326+ , moreLikeThisFieldBoost :: Maybe Boost
1327+ , moreLikeThisFieldAnalyzer :: Maybe Analyzer
1328+ } deriving (Eq, Read, Show, Generic, Typeable)
1329+
1330+data MoreLikeThisQuery =
1331+ MoreLikeThisQuery
1332+ { moreLikeThisText :: Text
1333+ , moreLikeThisFields :: Maybe (NonEmpty FieldName)
1334+ -- default 0.3 (30%)
1335+ , moreLikeThisPercentMatch :: Maybe PercentMatch
1336+ , moreLikeThisMinimumTermFreq :: Maybe MinimumTermFrequency
1337+ , moreLikeThisMaxQueryTerms :: Maybe MaxQueryTerms
1338+ , moreLikeThisStopWords :: Maybe (NonEmpty StopWord)
1339+ , moreLikeThisMinDocFrequency :: Maybe MinDocFrequency
1340+ , moreLikeThisMaxDocFrequency :: Maybe MaxDocFrequency
1341+ , moreLikeThisMinWordLength :: Maybe MinWordLength
1342+ , moreLikeThisMaxWordLength :: Maybe MaxWordLength
1343+ , moreLikeThisBoostTerms :: Maybe BoostTerms
1344+ , moreLikeThisBoost :: Maybe Boost
1345+ , moreLikeThisAnalyzer :: Maybe Analyzer
1346+ } deriving (Eq, Read, Show, Generic, Typeable)
1347+
1348+data IndicesQuery =
1349+ IndicesQuery
1350+ { indicesQueryIndices :: [IndexName]
1351+ , indicesQuery :: Query
1352+ -- default "all"
1353+ , indicesQueryNoMatch :: Maybe Query } deriving (Eq, Read, Show, Generic, Typeable)
1354+
1355+data HasParentQuery =
1356+ HasParentQuery
1357+ { hasParentQueryType :: TypeName
1358+ , hasParentQuery :: Query
1359+ , hasParentQueryScoreType :: Maybe ScoreType } deriving (Eq, Read, Show, Generic, Typeable)
1360+
1361+data HasChildQuery =
1362+ HasChildQuery
1363+ { hasChildQueryType :: TypeName
1364+ , hasChildQuery :: Query
1365+ , hasChildQueryScoreType :: Maybe ScoreType } deriving (Eq, Read, Show, Generic, Typeable)
1366+
1367+data ScoreType =
1368+ ScoreTypeMax
1369+ | ScoreTypeSum
1370+ | ScoreTypeAvg
1371+ | ScoreTypeNone deriving (Eq, Read, Show, Generic, Typeable)
1372+
1373+data FuzzyQuery =
1374+ FuzzyQuery { fuzzyQueryField :: FieldName
1375+ , fuzzyQueryValue :: Text
1376+ , fuzzyQueryPrefixLength :: PrefixLength
1377+ , fuzzyQueryMaxExpansions :: MaxExpansions
1378+ , fuzzyQueryFuzziness :: Fuzziness
1379+ , fuzzyQueryBoost :: Maybe Boost
1380+ } deriving (Eq, Read, Show, Generic, Typeable)
1381+
1382+data FuzzyLikeFieldQuery =
1383+ FuzzyLikeFieldQuery
1384+ { fuzzyLikeField :: FieldName
1385+ -- anaphora is good for the soul.
1386+ , fuzzyLikeFieldText :: Text
1387+ , fuzzyLikeFieldMaxQueryTerms :: MaxQueryTerms
1388+ , fuzzyLikeFieldIgnoreTermFrequency :: IgnoreTermFrequency
1389+ , fuzzyLikeFieldFuzziness :: Fuzziness
1390+ , fuzzyLikeFieldPrefixLength :: PrefixLength
1391+ , fuzzyLikeFieldBoost :: Boost
1392+ , fuzzyLikeFieldAnalyzer :: Maybe Analyzer
1393+ } deriving (Eq, Read, Show, Generic, Typeable)
1394+
1395+data FuzzyLikeThisQuery =
1396+ FuzzyLikeThisQuery
1397+ { fuzzyLikeFields :: [FieldName]
1398+ , fuzzyLikeText :: Text
1399+ , fuzzyLikeMaxQueryTerms :: MaxQueryTerms
1400+ , fuzzyLikeIgnoreTermFrequency :: IgnoreTermFrequency
1401+ , fuzzyLikeFuzziness :: Fuzziness
1402+ , fuzzyLikePrefixLength :: PrefixLength
1403+ , fuzzyLikeBoost :: Boost
1404+ , fuzzyLikeAnalyzer :: Maybe Analyzer
1405+ } deriving (Eq, Read, Show, Generic, Typeable)
1406+
1407+data DisMaxQuery =
1408+ DisMaxQuery { disMaxQueries :: [Query]
1409+ -- default 0.0
1410+ , disMaxTiebreaker :: Tiebreaker
1411+ , disMaxBoost :: Maybe Boost
1412+ } deriving (Eq, Read, Show, Generic, Typeable)
1413+
1414+data MatchQuery =
1415+ MatchQuery { matchQueryField :: FieldName
1416+ , matchQueryQueryString :: QueryString
1417+ , matchQueryOperator :: BooleanOperator
1418+ , matchQueryZeroTerms :: ZeroTermsQuery
1419+ , matchQueryCutoffFrequency :: Maybe CutoffFrequency
1420+ , matchQueryMatchType :: Maybe MatchQueryType
1421+ , matchQueryAnalyzer :: Maybe Analyzer
1422+ , matchQueryMaxExpansions :: Maybe MaxExpansions
1423+ , matchQueryLenient :: Maybe Lenient
1424+ , matchQueryBoost :: Maybe Boost } deriving (Eq, Read, Show, Generic, Typeable)
1425+
1426+{-| 'mkMatchQuery' is a convenience function that defaults the less common parameters,
1427+ enabling you to provide only the 'FieldName' and 'QueryString' to make a 'MatchQuery'
1428+-}
1429+mkMatchQuery :: FieldName -> QueryString -> MatchQuery
1430+mkMatchQuery field query = MatchQuery field query Or ZeroTermsNone Nothing Nothing Nothing Nothing Nothing Nothing
1431+
1432+data MatchQueryType =
1433+ MatchPhrase
1434+ | MatchPhrasePrefix deriving (Eq, Read, Show, Generic, Typeable)
1435+
1436+data MultiMatchQuery =
1437+ MultiMatchQuery { multiMatchQueryFields :: [FieldName]
1438+ , multiMatchQueryString :: QueryString
1439+ , multiMatchQueryOperator :: BooleanOperator
1440+ , multiMatchQueryZeroTerms :: ZeroTermsQuery
1441+ , multiMatchQueryTiebreaker :: Maybe Tiebreaker
1442+ , multiMatchQueryType :: Maybe MultiMatchQueryType
1443+ , multiMatchQueryCutoffFrequency :: Maybe CutoffFrequency
1444+ , multiMatchQueryAnalyzer :: Maybe Analyzer
1445+ , multiMatchQueryMaxExpansions :: Maybe MaxExpansions
1446+ , multiMatchQueryLenient :: Maybe Lenient } deriving (Eq, Read, Show, Generic, Typeable)
1447+
1448+{-| 'mkMultiMatchQuery' is a convenience function that defaults the less common parameters,
1449+ enabling you to provide only the list of 'FieldName's and 'QueryString' to
1450+ make a 'MultiMatchQuery'.
1451+-}
1452+
1453+mkMultiMatchQuery :: [FieldName] -> QueryString -> MultiMatchQuery
1454+mkMultiMatchQuery matchFields query =
1455+ MultiMatchQuery matchFields query
1456+ Or ZeroTermsNone Nothing Nothing Nothing Nothing Nothing Nothing
1457+
1458+data MultiMatchQueryType =
1459+ MultiMatchBestFields
1460+ | MultiMatchMostFields
1461+ | MultiMatchCrossFields
1462+ | MultiMatchPhrase
1463+ | MultiMatchPhrasePrefix deriving (Eq, Read, Show, Generic, Typeable)
1464+
1465+data BoolQuery =
1466+ BoolQuery { boolQueryMustMatch :: [Query]
1467+ , boolQueryFilter :: [Filter]
1468+ , boolQueryMustNotMatch :: [Query]
1469+ , boolQueryShouldMatch :: [Query]
1470+ , boolQueryMinimumShouldMatch :: Maybe MinimumMatch
1471+ , boolQueryBoost :: Maybe Boost
1472+ , boolQueryDisableCoord :: Maybe DisableCoord
1473+ } deriving (Eq, Read, Show, Generic, Typeable)
1474+
1475+mkBoolQuery :: [Query] -> [Filter] -> [Query] -> [Query] -> BoolQuery
1476+mkBoolQuery must filt mustNot should =
1477+ BoolQuery must filt mustNot should Nothing Nothing Nothing
1478+
1479+data BoostingQuery =
1480+ BoostingQuery { positiveQuery :: Query
1481+ , negativeQuery :: Query
1482+ , negativeBoost :: Boost } deriving (Eq, Read, Show, Generic, Typeable)
1483+
1484+data CommonTermsQuery =
1485+ CommonTermsQuery { commonField :: FieldName
1486+ , commonQuery :: QueryString
1487+ , commonCutoffFrequency :: CutoffFrequency
1488+ , commonLowFreqOperator :: BooleanOperator
1489+ , commonHighFreqOperator :: BooleanOperator
1490+ , commonMinimumShouldMatch :: Maybe CommonMinimumMatch
1491+ , commonBoost :: Maybe Boost
1492+ , commonAnalyzer :: Maybe Analyzer
1493+ , commonDisableCoord :: Maybe DisableCoord
1494+ } deriving (Eq, Read, Show, Generic, Typeable)
1495+
1496+data CommonMinimumMatch =
1497+ CommonMinimumMatchHighLow MinimumMatchHighLow
1498+ | CommonMinimumMatch MinimumMatch
1499+ deriving (Eq, Read, Show, Generic, Typeable)
1500+
1501+data MinimumMatchHighLow =
1502+ MinimumMatchHighLow { lowFreq :: MinimumMatch
1503+ , highFreq :: MinimumMatch } deriving (Eq, Read, Show, Generic, Typeable)
1504+
1505+data ZeroTermsQuery = ZeroTermsNone
1506+ | ZeroTermsAll deriving (Eq, Read, Show, Generic, Typeable)
1507+
1508+data RangeExecution = RangeExecutionIndex
1509+ | RangeExecutionFielddata deriving (Eq, Read, Show, Generic, Typeable)
1510+
1511+newtype Regexp = Regexp Text deriving (Eq, Read, Show, Generic, Typeable, FromJSON)
1512+
1513+data RegexpFlags = AllRegexpFlags
1514+ | NoRegexpFlags
1515+ | SomeRegexpFlags (NonEmpty RegexpFlag) deriving (Eq, Read, Show, Generic, Typeable)
1516+
1517+data RegexpFlag = AnyString
1518+ | Automaton
1519+ | Complement
1520+ | Empty
1521+ | Intersection
1522+ | Interval deriving (Eq, Read, Show, Generic, Typeable)
1523+
1524+newtype LessThan = LessThan Double deriving (Eq, Read, Show, Generic, Typeable)
1525+newtype LessThanEq = LessThanEq Double deriving (Eq, Read, Show, Generic, Typeable)
1526+newtype GreaterThan = GreaterThan Double deriving (Eq, Read, Show, Generic, Typeable)
1527+newtype GreaterThanEq = GreaterThanEq Double deriving (Eq, Read, Show, Generic, Typeable)
1528+
1529+newtype LessThanD = LessThanD UTCTime deriving (Eq, Read, Show, Generic, Typeable)
1530+newtype LessThanEqD = LessThanEqD UTCTime deriving (Eq, Read, Show, Generic, Typeable)
1531+newtype GreaterThanD = GreaterThanD UTCTime deriving (Eq, Read, Show, Generic, Typeable)
1532+newtype GreaterThanEqD = GreaterThanEqD UTCTime deriving (Eq, Read, Show, Generic, Typeable)
1533+
1534+data RangeValue = RangeDateLte LessThanEqD
1535+ | RangeDateLt LessThanD
1536+ | RangeDateGte GreaterThanEqD
1537+ | RangeDateGt GreaterThanD
1538+ | RangeDateGtLt GreaterThanD LessThanD
1539+ | RangeDateGteLte GreaterThanEqD LessThanEqD
1540+ | RangeDateGteLt GreaterThanEqD LessThanD
1541+ | RangeDateGtLte GreaterThanD LessThanEqD
1542+ | RangeDoubleLte LessThanEq
1543+ | RangeDoubleLt LessThan
1544+ | RangeDoubleGte GreaterThanEq
1545+ | RangeDoubleGt GreaterThan
1546+ | RangeDoubleGtLt GreaterThan LessThan
1547+ | RangeDoubleGteLte GreaterThanEq LessThanEq
1548+ | RangeDoubleGteLt GreaterThanEq LessThan
1549+ | RangeDoubleGtLte GreaterThan LessThanEq
1550+ deriving (Eq, Read, Show, Generic, Typeable)
1551+
1552+rangeValueToPair :: RangeValue -> [Pair]
1553+rangeValueToPair rv = case rv of
1554+ RangeDateLte (LessThanEqD t) -> ["lte" .= t]
1555+ RangeDateGte (GreaterThanEqD t) -> ["gte" .= t]
1556+ RangeDateLt (LessThanD t) -> ["lt" .= t]
1557+ RangeDateGt (GreaterThanD t) -> ["gt" .= t]
1558+ RangeDateGteLte (GreaterThanEqD l) (LessThanEqD g) -> ["gte" .= l, "lte" .= g]
1559+ RangeDateGtLte (GreaterThanD l) (LessThanEqD g) -> ["gt" .= l, "lte" .= g]
1560+ RangeDateGteLt (GreaterThanEqD l) (LessThanD g) -> ["gte" .= l, "lt" .= g]
1561+ RangeDateGtLt (GreaterThanD l) (LessThanD g) -> ["gt" .= l, "lt" .= g]
1562+ RangeDoubleLte (LessThanEq t) -> ["lte" .= t]
1563+ RangeDoubleGte (GreaterThanEq t) -> ["gte" .= t]
1564+ RangeDoubleLt (LessThan t) -> ["lt" .= t]
1565+ RangeDoubleGt (GreaterThan t) -> ["gt" .= t]
1566+ RangeDoubleGteLte (GreaterThanEq l) (LessThanEq g) -> ["gte" .= l, "lte" .= g]
1567+ RangeDoubleGtLte (GreaterThan l) (LessThanEq g) -> ["gt" .= l, "lte" .= g]
1568+ RangeDoubleGteLt (GreaterThanEq l) (LessThan g) -> ["gte" .= l, "lt" .= g]
1569+ RangeDoubleGtLt (GreaterThan l) (LessThan g) -> ["gt" .= l, "lt" .= g]
1570+
1571+data Term = Term { termField :: Text
1572+ , termValue :: Text } deriving (Eq, Read, Show, Generic, Typeable)
1573+
1574+data BoolMatch = MustMatch Term Cache
1575+ | MustNotMatch Term Cache
1576+ | ShouldMatch [Term] Cache deriving (Eq, Read, Show, Generic, Typeable)
1577+
1578+-- "memory" or "indexed"
1579+data GeoFilterType = GeoFilterMemory
1580+ | GeoFilterIndexed deriving (Eq, Read, Show, Generic, Typeable)
1581+
1582+data LatLon = LatLon { lat :: Double
1583+ , lon :: Double } deriving (Eq, Read, Show, Generic, Typeable)
1584+
1585+data GeoBoundingBox =
1586+ GeoBoundingBox { topLeft :: LatLon
1587+ , bottomRight :: LatLon } deriving (Eq, Read, Show, Generic, Typeable)
1588+
1589+data GeoBoundingBoxConstraint =
1590+ GeoBoundingBoxConstraint { geoBBField :: FieldName
1591+ , constraintBox :: GeoBoundingBox
1592+ , bbConstraintcache :: Cache
1593+ , geoType :: GeoFilterType
1594+ } deriving (Eq, Read, Show, Generic, Typeable)
1595+
1596+data GeoPoint =
1597+ GeoPoint { geoField :: FieldName
1598+ , latLon :: LatLon} deriving (Eq, Read, Show, Generic, Typeable)
1599+
1600+data DistanceUnit = Miles
1601+ | Yards
1602+ | Feet
1603+ | Inches
1604+ | Kilometers
1605+ | Meters
1606+ | Centimeters
1607+ | Millimeters
1608+ | NauticalMiles deriving (Eq, Read, Show, Generic, Typeable)
1609+
1610+data DistanceType = Arc
1611+ | SloppyArc -- doesn't exist <1.0
1612+ | Plane deriving (Eq, Read, Show, Generic, Typeable)
1613+
1614+data OptimizeBbox = OptimizeGeoFilterType GeoFilterType
1615+ | NoOptimizeBbox deriving (Eq, Read, Show, Generic, Typeable)
1616+
1617+data Distance =
1618+ Distance { coefficient :: Double
1619+ , unit :: DistanceUnit } deriving (Eq, Read, Show, Generic, Typeable)
1620+
1621+data DistanceRange =
1622+ DistanceRange { distanceFrom :: Distance
1623+ , distanceTo :: Distance } deriving (Eq, Read, Show, Generic, Typeable)
1624+
1625+data SearchResult a =
1626+ SearchResult { took :: Int
1627+ , timedOut :: Bool
1628+ , shards :: ShardResult
1629+ , searchHits :: SearchHits a
1630+ , aggregations :: Maybe AggregationResults
1631+ , scrollId :: Maybe ScrollId } deriving (Eq, Read, Show, Generic, Typeable)
1632+
1633+newtype ScrollId = ScrollId Text deriving (Eq, Read, Show, Generic, Ord, ToJSON, FromJSON)
1634+
1635+type Score = Maybe Double
1636+
1637+data SearchHits a =
1638+ SearchHits { hitsTotal :: Int
1639+ , maxScore :: Score
1640+ , hits :: [Hit a] } deriving (Eq, Read, Show, Generic, Typeable)
1641+
1642+instance Semigroup (SearchHits a) where
1643+ (SearchHits ta ma ha) <> (SearchHits tb mb hb) = SearchHits (ta + tb) (max ma mb) (ha <> hb)
1644+
1645+instance Monoid (SearchHits a) where
1646+ mempty = SearchHits 0 Nothing mempty
1647+ mappend = (<>)
1648+
1649+data Hit a =
1650+ Hit { hitIndex :: IndexName
1651+ , hitType :: MappingName
1652+ , hitDocId :: DocId
1653+ , hitScore :: Score
1654+ , hitSource :: Maybe a
1655+ , hitHighlight :: Maybe HitHighlight } deriving (Eq, Read, Show, Generic, Typeable)
1656+
1657+data ShardResult =
1658+ ShardResult { shardTotal :: Int
1659+ , shardsSuccessful :: Int
1660+ , shardsFailed :: Int } deriving (Eq, Read, Show, Generic, Typeable)
1661+
1662+type HitHighlight = M.Map Text [Text]
1663+
1664+showText :: Show a => a -> Text
1665+showText = T.pack . show
1666+
1667+readMay :: Read a => String -> Maybe a
1668+readMay s = case reads s of
1669+ (a, ""):_ -> Just a
1670+ _ -> Nothing
1671+
1672+parseReadText :: Read a => Text -> Parser a
1673+parseReadText = maybe mzero return . readMay . T.unpack
1674+
1675+type Aggregations = M.Map Text Aggregation
1676+
1677+emptyAggregations :: Aggregations
1678+emptyAggregations = M.empty
1679+
1680+mkAggregations :: Text -> Aggregation -> Aggregations
1681+mkAggregations name aggregation = M.insert name aggregation emptyAggregations
1682+
1683+data TermOrder = TermOrder{ termSortField :: Text
1684+ , termSortOrder :: SortOrder } deriving (Eq, Read, Show, Generic, Typeable)
1685+
1686+data TermInclusion = TermInclusion Text
1687+ | TermPattern Text Text deriving (Eq, Read, Show, Generic, Typeable)
1688+
1689+data CollectionMode = BreadthFirst
1690+ | DepthFirst deriving (Eq, Read, Show, Generic, Typeable)
1691+
1692+data ExecutionHint = Ordinals
1693+ | GlobalOrdinals
1694+ | GlobalOrdinalsHash
1695+ | GlobalOrdinalsLowCardinality
1696+ | Map deriving (Eq, Read, Show, Generic, Typeable)
1697+
1698+data TimeInterval = Weeks
1699+ | Days
1700+ | Hours
1701+ | Minutes
1702+ | Seconds deriving Eq
1703+
1704+data Interval = Year
1705+ | Quarter
1706+ | Month
1707+ | Week
1708+ | Day
1709+ | Hour
1710+ | Minute
1711+ | Second deriving (Eq, Read, Show, Generic, Typeable)
1712+
1713+data Aggregation = TermsAgg TermsAggregation
1714+ | CardinalityAgg CardinalityAggregation
1715+ | DateHistogramAgg DateHistogramAggregation
1716+ | ValueCountAgg ValueCountAggregation
1717+ | FilterAgg FilterAggregation
1718+ | DateRangeAgg DateRangeAggregation
1719+ | MissingAgg MissingAggregation
1720+ | TopHitsAgg TopHitsAggregation
1721+ | StatsAgg StatisticsAggregation
1722+ deriving (Eq, Read, Show, Generic, Typeable)
1723+
1724+data TopHitsAggregation = TopHitsAggregation
1725+ { taFrom :: Maybe From
1726+ , taSize :: Maybe Size
1727+ , taSort :: Maybe Sort
1728+ } deriving (Eq, Read, Show)
1729+
1730+data MissingAggregation = MissingAggregation
1731+ { maField :: Text
1732+ } deriving (Eq, Read, Show, Generic, Typeable)
1733+
1734+data TermsAggregation = TermsAggregation { term :: Either Text Text
1735+ , termInclude :: Maybe TermInclusion
1736+ , termExclude :: Maybe TermInclusion
1737+ , termOrder :: Maybe TermOrder
1738+ , termMinDocCount :: Maybe Int
1739+ , termSize :: Maybe Int
1740+ , termShardSize :: Maybe Int
1741+ , termCollectMode :: Maybe CollectionMode
1742+ , termExecutionHint :: Maybe ExecutionHint
1743+ , termAggs :: Maybe Aggregations
1744+ } deriving (Eq, Read, Show, Generic, Typeable)
1745+
1746+data CardinalityAggregation = CardinalityAggregation { cardinalityField :: FieldName,
1747+ precisionThreshold :: Maybe Int
1748+ } deriving (Eq, Read, Show, Generic, Typeable)
1749+
1750+data DateHistogramAggregation = DateHistogramAggregation { dateField :: FieldName
1751+ , dateInterval :: Interval
1752+ , dateFormat :: Maybe Text
1753+ -- pre and post deprecated in 1.5
1754+ , datePreZone :: Maybe Text
1755+ , datePostZone :: Maybe Text
1756+ , datePreOffset :: Maybe Text
1757+ , datePostOffset :: Maybe Text
1758+ , dateAggs :: Maybe Aggregations
1759+ } deriving (Eq, Read, Show, Generic, Typeable)
1760+
1761+
1762+data DateRangeAggregation = DateRangeAggregation { draField :: FieldName
1763+ , draFormat :: Maybe Text
1764+ , draRanges :: NonEmpty DateRangeAggRange
1765+ } deriving (Eq, Read, Show, Generic, Typeable)
1766+
1767+data DateRangeAggRange = DateRangeFrom DateMathExpr
1768+ | DateRangeTo DateMathExpr
1769+ | DateRangeFromAndTo DateMathExpr DateMathExpr deriving (Eq, Read, Show, Generic, Typeable)
1770+
1771+-- | See <https://www.elastic.co/guide/en/elasticsearch/reference/current/common-options.html#date-math> for more information.
1772+data DateMathExpr = DateMathExpr DateMathAnchor [DateMathModifier] deriving (Eq, Read, Show, Generic, Typeable)
1773+
1774+
1775+-- | Starting point for a date range. This along with the 'DateMathModifiers' gets you the date ES will start from.
1776+data DateMathAnchor = DMNow
1777+ | DMDate Day deriving (Eq, Read, Show, Generic, Typeable)
1778+
1779+data DateMathModifier = AddTime Int DateMathUnit
1780+ | SubtractTime Int DateMathUnit
1781+ | RoundDownTo DateMathUnit deriving (Eq, Read, Show, Generic, Typeable)
1782+
1783+data DateMathUnit = DMYear
1784+ | DMMonth
1785+ | DMWeek
1786+ | DMDay
1787+ | DMHour
1788+ | DMMinute
1789+ | DMSecond deriving (Eq, Read, Show, Generic, Typeable)
1790+
1791+-- | See <https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-valuecount-aggregation.html> for more information.
1792+data ValueCountAggregation = FieldValueCount FieldName
1793+ | ScriptValueCount Script deriving (Eq, Read, Show, Generic, Typeable)
1794+
1795+-- | Single-bucket filter aggregations. See <https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-filter-aggregation.html#search-aggregations-bucket-filter-aggregation> for more information.
1796+data FilterAggregation = FilterAggregation { faFilter :: Filter
1797+ , faAggs :: Maybe Aggregations} deriving (Eq, Read, Show, Generic, Typeable)
1798+
1799+data StatisticsAggregation = StatisticsAggregation { statsType :: StatsType
1800+ , statsField :: FieldName } deriving (Eq, Read, Show, Generic, Typeable)
1801+
1802+data StatsType
1803+ = Basic
1804+ | Extended
1805+ deriving (Eq, Read, Show, Generic, Typeable)
1806+
1807+mkTermsAggregation :: Text -> TermsAggregation
1808+mkTermsAggregation t = TermsAggregation (Left t) Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
1809+
1810+mkTermsScriptAggregation :: Text -> TermsAggregation
1811+mkTermsScriptAggregation t = TermsAggregation (Right t) Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
1812+
1813+mkDateHistogram :: FieldName -> Interval -> DateHistogramAggregation
1814+mkDateHistogram t i = DateHistogramAggregation t i Nothing Nothing Nothing Nothing Nothing Nothing
1815+
1816+mkCardinalityAggregation :: FieldName -> CardinalityAggregation
1817+mkCardinalityAggregation t = CardinalityAggregation t Nothing
1818+
1819+mkStatsAggregation :: FieldName -> StatisticsAggregation
1820+mkStatsAggregation = StatisticsAggregation Basic
1821+
1822+mkExtendedStatsAggregation :: FieldName -> StatisticsAggregation
1823+mkExtendedStatsAggregation = StatisticsAggregation Extended
1824+
1825+instance ToJSON Version where
1826+ toJSON Version {..} = object ["number" .= number
1827+ ,"build_hash" .= build_hash
1828+ ,"build_date" .= build_date
1829+ ,"build_snapshot" .= build_snapshot
1830+ ,"lucene_version" .= lucene_version]
1831+
1832+instance FromJSON Version where
1833+ parseJSON = withObject "Version" parse
1834+ where parse o = Version
1835+ <$> o .: "number"
1836+ <*> o .: "build_hash"
1837+ <*> o .: "build_date"
1838+ <*> o .: "build_snapshot"
1839+ <*> o .: "lucene_version"
1840+
1841+instance ToJSON VersionNumber where
1842+ toJSON = toJSON . Vers.showVersion . versionNumber
1843+
1844+instance FromJSON VersionNumber where
1845+ parseJSON = withText "VersionNumber" (parse . T.unpack)
1846+ where
1847+ parse s = case filter (null . snd)(RP.readP_to_S Vers.parseVersion s) of
1848+ [(v, _)] -> pure (VersionNumber v)
1849+ [] -> fail ("Invalid version string " ++ s)
1850+ xs -> fail ("Ambiguous version string " ++ s ++ " (" ++ intercalate ", " (Vers.showVersion . fst <$> xs) ++ ")")
1851+
1852+instance ToJSON TermOrder where
1853+ toJSON (TermOrder termSortField termSortOrder) = object [termSortField .= termSortOrder]
1854+
1855+instance ToJSON TermInclusion where
1856+ toJSON (TermInclusion x) = toJSON x
1857+ toJSON (TermPattern pattern flags) = omitNulls [ "pattern" .= pattern,
1858+ "flags" .= flags]
1859+
1860+instance ToJSON CollectionMode where
1861+ toJSON BreadthFirst = "breadth_first"
1862+ toJSON DepthFirst = "depth_first"
1863+
1864+instance ToJSON ExecutionHint where
1865+ toJSON Ordinals = "ordinals"
1866+ toJSON GlobalOrdinals = "global_ordinals"
1867+ toJSON GlobalOrdinalsHash = "global_ordinals_hash"
1868+ toJSON GlobalOrdinalsLowCardinality = "global_ordinals_low_cardinality"
1869+ toJSON Map = "map"
1870+
1871+instance ToJSON Interval where
1872+ toJSON Year = "year"
1873+ toJSON Quarter = "quarter"
1874+ toJSON Month = "month"
1875+ toJSON Week = "week"
1876+ toJSON Day = "day"
1877+ toJSON Hour = "hour"
1878+ toJSON Minute = "minute"
1879+ toJSON Second = "second"
1880+
1881+instance Show TimeInterval where
1882+ show Weeks = "w"
1883+ show Days = "d"
1884+ show Hours = "h"
1885+ show Minutes = "m"
1886+ show Seconds = "s"
1887+
1888+instance Read TimeInterval where
1889+ readPrec = f =<< TR.get
1890+ where
1891+ f 'w' = return Weeks
1892+ f 'd' = return Days
1893+ f 'h' = return Hours
1894+ f 'm' = return Minutes
1895+ f 's' = return Seconds
1896+ f _ = fail "TimeInterval expected one of w, d, h, m, s"
1897+
1898+instance ToJSON Aggregation where
1899+ toJSON (TermsAgg (TermsAggregation term include exclude order minDocCount size shardSize collectMode executionHint termAggs)) =
1900+ omitNulls ["terms" .= omitNulls [ toJSON' term,
1901+ "include" .= include,
1902+ "exclude" .= exclude,
1903+ "order" .= order,
1904+ "min_doc_count" .= minDocCount,
1905+ "size" .= size,
1906+ "shard_size" .= shardSize,
1907+ "collect_mode" .= collectMode,
1908+ "execution_hint" .= executionHint
1909+ ],
1910+ "aggs" .= termAggs ]
1911+ where
1912+ toJSON' x = case x of { Left y -> "field" .= y; Right y -> "script" .= y }
1913+
1914+ toJSON (CardinalityAgg (CardinalityAggregation field precisionThreshold)) =
1915+ object ["cardinality" .= omitNulls [ "field" .= field,
1916+ "precisionThreshold" .= precisionThreshold
1917+ ]
1918+ ]
1919+
1920+ toJSON (DateHistogramAgg (DateHistogramAggregation field interval format preZone postZone preOffset postOffset dateHistoAggs)) =
1921+ omitNulls ["date_histogram" .= omitNulls [ "field" .= field,
1922+ "interval" .= interval,
1923+ "format" .= format,
1924+ "pre_zone" .= preZone,
1925+ "post_zone" .= postZone,
1926+ "pre_offset" .= preOffset,
1927+ "post_offset" .= postOffset
1928+ ],
1929+ "aggs" .= dateHistoAggs ]
1930+ toJSON (ValueCountAgg a) = object ["value_count" .= v]
1931+ where v = case a of
1932+ (FieldValueCount (FieldName n)) -> object ["field" .= n]
1933+ (ScriptValueCount (Script s)) -> object ["script" .= s]
1934+ toJSON (FilterAgg (FilterAggregation filt ags)) =
1935+ omitNulls [ "filter" .= filt
1936+ , "aggs" .= ags]
1937+ toJSON (DateRangeAgg a) = object [ "date_range" .= a
1938+ ]
1939+ toJSON (MissingAgg (MissingAggregation{..})) =
1940+ object ["missing" .= object ["field" .= maField]]
1941+
1942+ toJSON (TopHitsAgg (TopHitsAggregation mfrom msize msort)) =
1943+ omitNulls ["top_hits" .= omitNulls [ "size" .= msize
1944+ , "from" .= mfrom
1945+ , "sort" .= msort
1946+ ]
1947+ ]
1948+
1949+ toJSON (StatsAgg (StatisticsAggregation typ field)) =
1950+ object [stType .= omitNulls [ "field" .= field ]]
1951+ where
1952+ stType | typ == Basic = "stats"
1953+ | otherwise = "extended_stats"
1954+
1955+instance ToJSON DateRangeAggregation where
1956+ toJSON DateRangeAggregation {..} =
1957+ omitNulls [ "field" .= draField
1958+ , "format" .= draFormat
1959+ , "ranges" .= toList draRanges
1960+ ]
1961+
1962+instance ToJSON DateRangeAggRange where
1963+ toJSON (DateRangeFrom e) = object [ "from" .= e ]
1964+ toJSON (DateRangeTo e) = object [ "to" .= e ]
1965+ toJSON (DateRangeFromAndTo f t) = object [ "from" .= f, "to" .= t ]
1966+
1967+instance ToJSON DateMathExpr where
1968+ toJSON (DateMathExpr a mods) = String (fmtA a <> mconcat (fmtMod <$> mods))
1969+ where fmtA DMNow = "now"
1970+ fmtA (DMDate date) = (T.pack $ showGregorian date) <> "||"
1971+ fmtMod (AddTime n u) = "+" <> showText n <> fmtU u
1972+ fmtMod (SubtractTime n u) = "-" <> showText n <> fmtU u
1973+ fmtMod (RoundDownTo u) = "/" <> fmtU u
1974+ fmtU DMYear = "y"
1975+ fmtU DMMonth = "M"
1976+ fmtU DMWeek = "w"
1977+ fmtU DMDay = "d"
1978+ fmtU DMHour = "h"
1979+ fmtU DMMinute = "m"
1980+ fmtU DMSecond = "s"
1981+
1982+
1983+type AggregationResults = M.Map Text Value
1984+
1985+class BucketAggregation a where
1986+ key :: a -> BucketValue
1987+ docCount :: a -> Int
1988+ aggs :: a -> Maybe AggregationResults
1989+
1990+
1991+data Bucket a = Bucket { buckets :: [a]} deriving (Read, Show)
1992+
1993+data BucketValue = TextValue Text
1994+ | ScientificValue Scientific
1995+ | BoolValue Bool deriving (Read, Show)
1996+
1997+data MissingResult = MissingResult { missingDocCount :: Int } deriving (Show)
1998+
1999+data TopHitResult a = TopHitResult { tarHits :: (SearchHits a)
2000+ } deriving Show
2001+
2002+data TermsResult = TermsResult { termKey :: BucketValue
2003+ , termsDocCount :: Int
2004+ , termsAggs :: Maybe AggregationResults } deriving (Read, Show)
2005+
2006+data DateHistogramResult = DateHistogramResult { dateKey :: Int
2007+ , dateKeyStr :: Maybe Text
2008+ , dateDocCount :: Int
2009+ , dateHistogramAggs :: Maybe AggregationResults } deriving (Read, Show)
2010+
2011+data DateRangeResult = DateRangeResult { dateRangeKey :: Text
2012+ , dateRangeFrom :: Maybe UTCTime
2013+ , dateRangeFromAsString :: Maybe Text
2014+ , dateRangeTo :: Maybe UTCTime
2015+ , dateRangeToAsString :: Maybe Text
2016+ , dateRangeDocCount :: Int
2017+ , dateRangeAggs :: Maybe AggregationResults } deriving (Read, Show, Eq, Generic, Typeable)
2018+
2019+toTerms :: Text -> AggregationResults -> Maybe (Bucket TermsResult)
2020+toTerms = toAggResult
2021+
2022+toDateHistogram :: Text -> AggregationResults -> Maybe (Bucket DateHistogramResult)
2023+toDateHistogram = toAggResult
2024+
2025+toMissing :: Text -> AggregationResults -> Maybe MissingResult
2026+toMissing = toAggResult
2027+
2028+toTopHits :: (FromJSON a) => Text -> AggregationResults -> Maybe (TopHitResult a)
2029+toTopHits = toAggResult
2030+
2031+toAggResult :: (FromJSON a) => Text -> AggregationResults -> Maybe a
2032+toAggResult t a = M.lookup t a >>= deserialize
2033+ where deserialize = parseMaybe parseJSON
2034+
2035+instance BucketAggregation TermsResult where
2036+ key = termKey
2037+ docCount = termsDocCount
2038+ aggs = termsAggs
2039+
2040+instance BucketAggregation DateHistogramResult where
2041+ key = TextValue . showText . dateKey
2042+ docCount = dateDocCount
2043+ aggs = dateHistogramAggs
2044+
2045+instance BucketAggregation DateRangeResult where
2046+ key = TextValue . dateRangeKey
2047+ docCount = dateRangeDocCount
2048+ aggs = dateRangeAggs
2049+
2050+instance (FromJSON a, BucketAggregation a) => FromJSON (Bucket a) where
2051+ parseJSON (Object v) = Bucket <$>
2052+ v .: "buckets"
2053+ parseJSON _ = mempty
2054+
2055+instance FromJSON BucketValue where
2056+ parseJSON (String t) = return $ TextValue t
2057+ parseJSON (Number s) = return $ ScientificValue s
2058+ parseJSON (Bool b) = return $ BoolValue b
2059+ parseJSON _ = mempty
2060+
2061+instance FromJSON MissingResult where
2062+ parseJSON = withObject "MissingResult" parse
2063+ where parse v = MissingResult <$> v .: "doc_count"
2064+
2065+instance FromJSON TermsResult where
2066+ parseJSON (Object v) = TermsResult <$>
2067+ v .: "key" <*>
2068+ v .: "doc_count" <*>
2069+ (pure $ getNamedSubAgg v ["key", "doc_count"])
2070+ parseJSON _ = mempty
2071+
2072+instance FromJSON DateHistogramResult where
2073+ parseJSON (Object v) = DateHistogramResult <$>
2074+ v .: "key" <*>
2075+ v .:? "key_as_string" <*>
2076+ v .: "doc_count" <*>
2077+ (pure $ getNamedSubAgg v [ "key"
2078+ , "doc_count"
2079+ , "key_as_string"
2080+ ]
2081+ )
2082+ parseJSON _ = mempty
2083+
2084+instance FromJSON DateRangeResult where
2085+ parseJSON = withObject "DateRangeResult" parse
2086+ where parse v = DateRangeResult <$>
2087+ v .: "key" <*>
2088+ (fmap posixMS <$> v .:? "from") <*>
2089+ v .:? "from_as_string" <*>
2090+ (fmap posixMS <$> v .:? "to") <*>
2091+ v .:? "to_as_string" <*>
2092+ v .: "doc_count" <*>
2093+ (pure $ getNamedSubAgg v [ "key"
2094+ , "from"
2095+ , "from_as_string"
2096+ , "to"
2097+ , "to_as_string"
2098+ , "doc_count"
2099+ ]
2100+ )
2101+
2102+instance FromJSON POSIXMS where
2103+ parseJSON = withScientific "POSIXMS" (return . parse)
2104+ where parse n = let n' = truncate n :: Integer
2105+ in POSIXMS (posixSecondsToUTCTime (fromInteger (n' `div` 1000)))
2106+
2107+instance (FromJSON a) => FromJSON (TopHitResult a) where
2108+ parseJSON (Object v) = TopHitResult <$>
2109+ v .: "hits"
2110+ parseJSON _ = fail "Failure in FromJSON (TopHitResult a)"
2111+
2112+fieldTagged :: Monad m => (FieldName -> Object -> m a) -> Object -> m a
2113+fieldTagged f o = case HM.toList o of
2114+ [(k, Object o')] -> f (FieldName k) o'
2115+ _ -> fail "Expected object with 1 field-named key"
2116+
2117+-- Try to get an AggregationResults when we don't know the
2118+-- field name. We filter out the known keys to try to minimize the noise.
2119+getNamedSubAgg :: Object -> [Text] -> Maybe AggregationResults
2120+getNamedSubAgg o knownKeys = maggRes
2121+ where unknownKeys = HM.filterWithKey (\k _ -> k `notElem` knownKeys) o
2122+ maggRes
2123+ | HM.null unknownKeys = Nothing
2124+ | otherwise = Just . M.fromList $ HM.toList unknownKeys
2125+
2126+instance ToJSON GeoPoint where
2127+ toJSON (GeoPoint (FieldName geoPointField) geoPointLatLon) =
2128+ object [ geoPointField .= geoPointLatLon ]
2129+
2130+
2131+instance ToJSON Query where
2132+ toJSON (TermQuery (Term termQueryField termQueryValue) boost) =
2133+ object [ "term" .=
2134+ object [termQueryField .= object merged]]
2135+ where
2136+ base = [ "value" .= termQueryValue ]
2137+ boosted = maybe [] (return . ("boost" .=)) boost
2138+ merged = mappend base boosted
2139+
2140+ toJSON (TermsQuery fieldName terms) =
2141+ object [ "terms" .= object conjoined ]
2142+ where conjoined = [fieldName .= terms]
2143+
2144+ toJSON (IdsQuery idsQueryMappingName docIds) =
2145+ object [ "ids" .= object conjoined ]
2146+ where conjoined = [ "type" .= idsQueryMappingName
2147+ , "values" .= fmap toJSON docIds ]
2148+
2149+ toJSON (QueryQueryStringQuery qQueryStringQuery) =
2150+ object [ "query_string" .= qQueryStringQuery ]
2151+
2152+ toJSON (QueryMatchQuery matchQuery) =
2153+ object [ "match" .= matchQuery ]
2154+
2155+ toJSON (QueryMultiMatchQuery multiMatchQuery) =
2156+ toJSON multiMatchQuery
2157+
2158+ toJSON (QueryBoolQuery boolQuery) =
2159+ object [ "bool" .= boolQuery ]
2160+
2161+ toJSON (QueryBoostingQuery boostingQuery) =
2162+ object [ "boosting" .= boostingQuery ]
2163+
2164+ toJSON (QueryCommonTermsQuery commonTermsQuery) =
2165+ object [ "common" .= commonTermsQuery ]
2166+
2167+ toJSON (ConstantScoreQuery query boost) =
2168+ object ["constant_score" .= object ["query" .= query
2169+ , "boost" .= boost]]
2170+
2171+ toJSON (QueryDisMaxQuery disMaxQuery) =
2172+ object [ "dis_max" .= disMaxQuery ]
2173+
2174+ toJSON (QueryFuzzyLikeThisQuery fuzzyQuery) =
2175+ object [ "fuzzy_like_this" .= fuzzyQuery ]
2176+
2177+ toJSON (QueryFuzzyLikeFieldQuery fuzzyFieldQuery) =
2178+ object [ "fuzzy_like_this_field" .= fuzzyFieldQuery ]
2179+
2180+ toJSON (QueryFuzzyQuery fuzzyQuery) =
2181+ object [ "fuzzy" .= fuzzyQuery ]
2182+
2183+ toJSON (QueryHasChildQuery childQuery) =
2184+ object [ "has_child" .= childQuery ]
2185+
2186+ toJSON (QueryHasParentQuery parentQuery) =
2187+ object [ "has_parent" .= parentQuery ]
2188+
2189+ toJSON (QueryIndicesQuery qIndicesQuery) =
2190+ object [ "indices" .= qIndicesQuery ]
2191+
2192+ toJSON (MatchAllQuery boost) =
2193+ object [ "match_all" .= omitNulls [ "boost" .= boost ] ]
2194+
2195+ toJSON (QueryMoreLikeThisQuery query) =
2196+ object [ "more_like_this" .= query ]
2197+
2198+ toJSON (QueryMoreLikeThisFieldQuery query) =
2199+ object [ "more_like_this_field" .= query ]
2200+
2201+ toJSON (QueryNestedQuery query) =
2202+ object [ "nested" .= query ]
2203+
2204+ toJSON (QueryPrefixQuery query) =
2205+ object [ "prefix" .= query ]
2206+
2207+ toJSON (QueryRangeQuery query) =
2208+ object [ "range" .= query ]
2209+
2210+ toJSON (QueryRegexpQuery query) =
2211+ object [ "regexp" .= query ]
2212+
2213+ toJSON (QuerySimpleQueryStringQuery query) =
2214+ object [ "simple_query_string" .= query ]
2215+
2216+ toJSON (QueryExistsQuery (FieldName fieldName)) =
2217+ object ["exists" .= object
2218+ ["field" .= fieldName]
2219+ ]
2220+ toJSON QueryMatchNoneQuery =
2221+ object ["match_none" .= object []]
2222+
2223+instance FromJSON Query where
2224+ parseJSON v = withObject "Query" parse v
2225+ where parse o = termQuery `taggedWith` "term"
2226+ <|> termsQuery `taggedWith` "terms"
2227+ <|> idsQuery `taggedWith` "ids"
2228+ <|> queryQueryStringQuery `taggedWith` "query_string"
2229+ <|> queryMatchQuery `taggedWith` "match"
2230+ <|> queryMultiMatchQuery
2231+ <|> queryBoolQuery `taggedWith` "bool"
2232+ <|> queryBoostingQuery `taggedWith` "boosting"
2233+ <|> queryCommonTermsQuery `taggedWith` "common"
2234+ <|> constantScoreQuery `taggedWith` "constant_score"
2235+ <|> queryDisMaxQuery `taggedWith` "dis_max"
2236+ <|> queryFuzzyLikeThisQuery `taggedWith` "fuzzy_like_this"
2237+ <|> queryFuzzyLikeFieldQuery `taggedWith` "fuzzy_like_this_field"
2238+ <|> queryFuzzyQuery `taggedWith` "fuzzy"
2239+ <|> queryHasChildQuery `taggedWith` "has_child"
2240+ <|> queryHasParentQuery `taggedWith` "has_parent"
2241+ <|> queryIndicesQuery `taggedWith` "indices"
2242+ <|> matchAllQuery `taggedWith` "match_all"
2243+ <|> queryMoreLikeThisQuery `taggedWith` "more_like_this"
2244+ <|> queryMoreLikeThisFieldQuery `taggedWith` "more_like_this_field"
2245+ <|> queryNestedQuery `taggedWith` "nested"
2246+ <|> queryPrefixQuery `taggedWith` "prefix"
2247+ <|> queryRangeQuery `taggedWith` "range"
2248+ <|> queryRegexpQuery `taggedWith` "regexp"
2249+ <|> querySimpleQueryStringQuery `taggedWith` "simple_query_string"
2250+ where taggedWith parser k = parser =<< o .: k
2251+ termQuery = fieldTagged $ \(FieldName fn) o ->
2252+ TermQuery <$> (Term fn <$> o .: "value") <*> o .:? "boost"
2253+ termsQuery o = case HM.toList o of
2254+ [(fn, vs)] -> do vals <- parseJSON vs
2255+ case vals of
2256+ x:xs -> return (TermsQuery fn (x :| xs))
2257+ _ -> fail "Expected non empty list of values"
2258+ _ -> fail "Expected object with 1 field-named key"
2259+ idsQuery o = IdsQuery <$> o .: "type"
2260+ <*> o .: "values"
2261+ queryQueryStringQuery = pure . QueryQueryStringQuery
2262+ queryMatchQuery = pure . QueryMatchQuery
2263+ queryMultiMatchQuery = QueryMultiMatchQuery <$> parseJSON v
2264+ queryBoolQuery = pure . QueryBoolQuery
2265+ queryBoostingQuery = pure . QueryBoostingQuery
2266+ queryCommonTermsQuery = pure . QueryCommonTermsQuery
2267+ constantScoreQuery o = case HM.lookup "query" o of
2268+ Just x -> ConstantScoreQuery <$> parseJSON x
2269+ <*> o .: "boost"
2270+ _ -> fail "Does not appear to be a ConstantScoreQuery"
2271+ queryDisMaxQuery = pure . QueryDisMaxQuery
2272+ queryFuzzyLikeThisQuery = pure . QueryFuzzyLikeThisQuery
2273+ queryFuzzyLikeFieldQuery = pure . QueryFuzzyLikeFieldQuery
2274+ queryFuzzyQuery = pure . QueryFuzzyQuery
2275+ queryHasChildQuery = pure . QueryHasChildQuery
2276+ queryHasParentQuery = pure . QueryHasParentQuery
2277+ queryIndicesQuery = pure . QueryIndicesQuery
2278+ matchAllQuery o = MatchAllQuery <$> o .:? "boost"
2279+ queryMoreLikeThisQuery = pure . QueryMoreLikeThisQuery
2280+ queryMoreLikeThisFieldQuery = pure . QueryMoreLikeThisFieldQuery
2281+ queryNestedQuery = pure . QueryNestedQuery
2282+ queryPrefixQuery = pure . QueryPrefixQuery
2283+ queryRangeQuery = pure . QueryRangeQuery
2284+ queryRegexpQuery = pure . QueryRegexpQuery
2285+ querySimpleQueryStringQuery = pure . QuerySimpleQueryStringQuery
2286+ -- queryExistsQuery o = QueryExistsQuery <$> o .: "field"
2287+
2288+
2289+omitNulls :: [(Text, Value)] -> Value
2290+omitNulls = object . filter notNull where
2291+ notNull (_, Null) = False
2292+ notNull (_, Array a) = (not . V.null) a
2293+ notNull _ = True
2294+
2295+instance ToJSON SimpleQueryStringQuery where
2296+ toJSON SimpleQueryStringQuery {..} =
2297+ omitNulls (base ++ maybeAdd)
2298+ where base = [ "query" .= simpleQueryStringQuery ]
2299+ maybeAdd = [ "fields" .= simpleQueryStringField
2300+ , "default_operator" .= simpleQueryStringOperator
2301+ , "analyzer" .= simpleQueryStringAnalyzer
2302+ , "flags" .= simpleQueryStringFlags
2303+ , "lowercase_expanded_terms" .= simpleQueryStringLowercaseExpanded
2304+ , "locale" .= simpleQueryStringLocale ]
2305+
2306+instance FromJSON SimpleQueryStringQuery where
2307+ parseJSON = withObject "SimpleQueryStringQuery" parse
2308+ where parse o = SimpleQueryStringQuery <$> o .: "query"
2309+ <*> o .:? "fields"
2310+ <*> o .:? "default_operator"
2311+ <*> o .:? "analyzer"
2312+ <*> (parseFlags <$> o .:? "flags")
2313+ <*> o .:? "lowercase_expanded_terms"
2314+ <*> o .:? "locale"
2315+ parseFlags (Just (x:xs)) = Just (x :| xs)
2316+ parseFlags _ = Nothing
2317+
2318+instance ToJSON FieldOrFields where
2319+ toJSON (FofField fieldName) =
2320+ toJSON fieldName
2321+ toJSON (FofFields fieldNames) =
2322+ toJSON fieldNames
2323+
2324+instance FromJSON FieldOrFields where
2325+ parseJSON v = FofField <$> parseJSON v
2326+ <|> FofFields <$> (parseNEJSON =<< parseJSON v)
2327+
2328+instance ToJSON SimpleQueryFlag where
2329+ toJSON SimpleQueryAll = "ALL"
2330+ toJSON SimpleQueryNone = "NONE"
2331+ toJSON SimpleQueryAnd = "AND"
2332+ toJSON SimpleQueryOr = "OR"
2333+ toJSON SimpleQueryPrefix = "PREFIX"
2334+ toJSON SimpleQueryPhrase = "PHRASE"
2335+ toJSON SimpleQueryPrecedence = "PRECEDENCE"
2336+ toJSON SimpleQueryEscape = "ESCAPE"
2337+ toJSON SimpleQueryWhitespace = "WHITESPACE"
2338+ toJSON SimpleQueryFuzzy = "FUZZY"
2339+ toJSON SimpleQueryNear = "NEAR"
2340+ toJSON SimpleQuerySlop = "SLOP"
2341+
2342+instance FromJSON SimpleQueryFlag where
2343+ parseJSON = withText "SimpleQueryFlag" parse
2344+ where parse "ALL" = pure SimpleQueryAll
2345+ parse "NONE" = pure SimpleQueryNone
2346+ parse "AND" = pure SimpleQueryAnd
2347+ parse "OR" = pure SimpleQueryOr
2348+ parse "PREFIX" = pure SimpleQueryPrefix
2349+ parse "PHRASE" = pure SimpleQueryPhrase
2350+ parse "PRECEDENCE" = pure SimpleQueryPrecedence
2351+ parse "ESCAPE" = pure SimpleQueryEscape
2352+ parse "WHITESPACE" = pure SimpleQueryWhitespace
2353+ parse "FUZZY" = pure SimpleQueryFuzzy
2354+ parse "NEAR" = pure SimpleQueryNear
2355+ parse "SLOP" = pure SimpleQuerySlop
2356+ parse f = fail ("Unexpected SimpleQueryFlag: " <> show f)
2357+
2358+instance ToJSON RegexpQuery where
2359+ toJSON (RegexpQuery (FieldName rqQueryField)
2360+ (Regexp regexpQueryQuery) rqQueryFlags
2361+ rqQueryBoost) =
2362+ object [ rqQueryField .= omitNulls base ]
2363+ where base = [ "value" .= regexpQueryQuery
2364+ , "flags" .= rqQueryFlags
2365+ , "boost" .= rqQueryBoost ]
2366+
2367+instance FromJSON RegexpQuery where
2368+ parseJSON = withObject "RegexpQuery" parse
2369+ where parse = fieldTagged $ \fn o ->
2370+ RegexpQuery fn
2371+ <$> o .: "value"
2372+ <*> o .: "flags"
2373+ <*> o .:? "boost"
2374+
2375+instance ToJSON QueryStringQuery where
2376+ toJSON (QueryStringQuery qsQueryString
2377+ qsDefaultField qsOperator
2378+ qsAnalyzer qsAllowWildcard
2379+ qsLowercaseExpanded qsEnablePositionIncrements
2380+ qsFuzzyMaxExpansions qsFuzziness
2381+ qsFuzzyPrefixLength qsPhraseSlop
2382+ qsBoost qsAnalyzeWildcard
2383+ qsGeneratePhraseQueries qsMinimumShouldMatch
2384+ qsLenient qsLocale) =
2385+ omitNulls base
2386+ where
2387+ base = [ "query" .= qsQueryString
2388+ , "default_field" .= qsDefaultField
2389+ , "default_operator" .= qsOperator
2390+ , "analyzer" .= qsAnalyzer
2391+ , "allow_leading_wildcard" .= qsAllowWildcard
2392+ , "lowercase_expanded_terms" .= qsLowercaseExpanded
2393+ , "enable_position_increments" .= qsEnablePositionIncrements
2394+ , "fuzzy_max_expansions" .= qsFuzzyMaxExpansions
2395+ , "fuzziness" .= qsFuzziness
2396+ , "fuzzy_prefix_length" .= qsFuzzyPrefixLength
2397+ , "phrase_slop" .= qsPhraseSlop
2398+ , "boost" .= qsBoost
2399+ , "analyze_wildcard" .= qsAnalyzeWildcard
2400+ , "auto_generate_phrase_queries" .= qsGeneratePhraseQueries
2401+ , "minimum_should_match" .= qsMinimumShouldMatch
2402+ , "lenient" .= qsLenient
2403+ , "locale" .= qsLocale ]
2404+
2405+instance FromJSON QueryStringQuery where
2406+ parseJSON = withObject "QueryStringQuery" parse
2407+ where parse o = QueryStringQuery
2408+ <$> o .: "query"
2409+ <*> o .:? "default_field"
2410+ <*> o .:? "default_operator"
2411+ <*> o .:? "analyzer"
2412+ <*> o .:? "allow_leading_wildcard"
2413+ <*> o .:? "lowercase_expanded_terms"
2414+ <*> o .:? "enable_position_increments"
2415+ <*> o .:? "fuzzy_max_expansions"
2416+ <*> o .:? "fuzziness"
2417+ <*> o .:? "fuzzy_prefix_length"
2418+ <*> o .:? "phrase_slop"
2419+ <*> o .:? "boost"
2420+ <*> o .:? "analyze_wildcard"
2421+ <*> o .:? "auto_generate_phrase_queries"
2422+ <*> o .:? "minimum_should_match"
2423+ <*> o .:? "lenient"
2424+ <*> o .:? "locale"
2425+
2426+instance ToJSON RangeQuery where
2427+ toJSON (RangeQuery (FieldName fieldName) range boost) =
2428+ object [ fieldName .= object conjoined ]
2429+ where conjoined = [ "boost" .= boost ] ++ (rangeValueToPair range)
2430+
2431+instance FromJSON RangeQuery where
2432+ parseJSON = withObject "RangeQuery" parse
2433+ where parse = fieldTagged $ \fn o ->
2434+ RangeQuery fn
2435+ <$> parseJSON (Object o)
2436+ <*> o .: "boost"
2437+
2438+instance FromJSON RangeValue where
2439+ parseJSON = withObject "RangeValue" parse
2440+ where parse o = parseDate o
2441+ <|> parseDouble o
2442+ parseDate o = do lt <- o .:? "lt"
2443+ lte <- o .:? "lte"
2444+ gt <- o .:? "gt"
2445+ gte <- o .:? "gte"
2446+ case (lt, lte, gt, gte) of
2447+ (Just a, _, Just b, _) -> return (RangeDateGtLt (GreaterThanD b) (LessThanD a))
2448+ (Just a, _, _, Just b)-> return (RangeDateGteLt (GreaterThanEqD b) (LessThanD a))
2449+ (_, Just a, Just b, _)-> return (RangeDateGtLte (GreaterThanD b) (LessThanEqD a))
2450+ (_, Just a, _, Just b)-> return (RangeDateGteLte (GreaterThanEqD b) (LessThanEqD a))
2451+ (_, _, Just a, _)-> return (RangeDateGt (GreaterThanD a))
2452+ (Just a, _, _, _)-> return (RangeDateLt (LessThanD a))
2453+ (_, _, _, Just a)-> return (RangeDateGte (GreaterThanEqD a))
2454+ (_, Just a, _, _)-> return (RangeDateLte (LessThanEqD a))
2455+ (Nothing, Nothing, Nothing, Nothing) -> mzero
2456+ parseDouble o = do lt <- o .:? "lt"
2457+ lte <- o .:? "lte"
2458+ gt <- o .:? "gt"
2459+ gte <- o .:? "gte"
2460+ case (lt, lte, gt, gte) of
2461+ (Just a, _, Just b, _) -> return (RangeDoubleGtLt (GreaterThan b) (LessThan a))
2462+ (Just a, _, _, Just b)-> return (RangeDoubleGteLt (GreaterThanEq b) (LessThan a))
2463+ (_, Just a, Just b, _)-> return (RangeDoubleGtLte (GreaterThan b) (LessThanEq a))
2464+ (_, Just a, _, Just b)-> return (RangeDoubleGteLte (GreaterThanEq b) (LessThanEq a))
2465+ (_, _, Just a, _)-> return (RangeDoubleGt (GreaterThan a))
2466+ (Just a, _, _, _)-> return (RangeDoubleLt (LessThan a))
2467+ (_, _, _, Just a)-> return (RangeDoubleGte (GreaterThanEq a))
2468+ (_, Just a, _, _)-> return (RangeDoubleLte (LessThanEq a))
2469+ (Nothing, Nothing, Nothing, Nothing) -> mzero
2470+
2471+instance ToJSON PrefixQuery where
2472+ toJSON (PrefixQuery (FieldName fieldName) queryValue boost) =
2473+ object [ fieldName .= omitNulls base ]
2474+ where base = [ "value" .= queryValue
2475+ , "boost" .= boost ]
2476+
2477+instance FromJSON PrefixQuery where
2478+ parseJSON = withObject "PrefixQuery" parse
2479+ where parse = fieldTagged $ \fn o ->
2480+ PrefixQuery fn
2481+ <$> o .: "value"
2482+ <*> o .:? "boost"
2483+
2484+instance ToJSON NestedQuery where
2485+ toJSON (NestedQuery nqPath nqScoreType nqQuery) =
2486+ object [ "path" .= nqPath
2487+ , "score_mode" .= nqScoreType
2488+ , "query" .= nqQuery ]
2489+
2490+instance FromJSON NestedQuery where
2491+ parseJSON = withObject "NestedQuery" parse
2492+ where parse o = NestedQuery
2493+ <$> o .: "path"
2494+ <*> o .: "score_mode"
2495+ <*> o .: "query"
2496+
2497+instance ToJSON MoreLikeThisFieldQuery where
2498+ toJSON (MoreLikeThisFieldQuery text (FieldName fieldName)
2499+ percent mtf mqt stopwords mindf maxdf
2500+ minwl maxwl boostTerms boost analyzer) =
2501+ object [ fieldName .= omitNulls base ]
2502+ where base = [ "like_text" .= text
2503+ , "percent_terms_to_match" .= percent
2504+ , "min_term_freq" .= mtf
2505+ , "max_query_terms" .= mqt
2506+ , "stop_words" .= stopwords
2507+ , "min_doc_freq" .= mindf
2508+ , "max_doc_freq" .= maxdf
2509+ , "min_word_length" .= minwl
2510+ , "max_word_length" .= maxwl
2511+ , "boost_terms" .= boostTerms
2512+ , "boost" .= boost
2513+ , "analyzer" .= analyzer ]
2514+
2515+instance FromJSON MoreLikeThisFieldQuery where
2516+ parseJSON = withObject "MoreLikeThisFieldQuery" parse
2517+ where parse = fieldTagged $ \fn o ->
2518+ MoreLikeThisFieldQuery
2519+ <$> o .: "like_text"
2520+ <*> pure fn
2521+ <*> o .:? "percent_terms_to_match"
2522+ <*> o .:? "min_term_freq"
2523+ <*> o .:? "max_query_terms"
2524+ -- <*> (optionalNE =<< o .:? "stop_words")
2525+ <*> o .:? "stop_words"
2526+ <*> o .:? "min_doc_freq"
2527+ <*> o .:? "max_doc_freq"
2528+ <*> o .:? "min_word_length"
2529+ <*> o .:? "max_word_length"
2530+ <*> o .:? "boost_terms"
2531+ <*> o .:? "boost"
2532+ <*> o .:? "analyzer"
2533+ -- optionalNE = maybe (pure Nothing) (fmap Just . parseNEJSON)
2534+
2535+instance ToJSON MoreLikeThisQuery where
2536+ toJSON (MoreLikeThisQuery text fields percent
2537+ mtf mqt stopwords mindf maxdf
2538+ minwl maxwl boostTerms boost analyzer) =
2539+ omitNulls base
2540+ where base = [ "like_text" .= text
2541+ , "fields" .= fields
2542+ , "percent_terms_to_match" .= percent
2543+ , "min_term_freq" .= mtf
2544+ , "max_query_terms" .= mqt
2545+ , "stop_words" .= stopwords
2546+ , "min_doc_freq" .= mindf
2547+ , "max_doc_freq" .= maxdf
2548+ , "min_word_length" .= minwl
2549+ , "max_word_length" .= maxwl
2550+ , "boost_terms" .= boostTerms
2551+ , "boost" .= boost
2552+ , "analyzer" .= analyzer ]
2553+
2554+instance FromJSON MoreLikeThisQuery where
2555+ parseJSON = withObject "MoreLikeThisQuery" parse
2556+ where parse o = MoreLikeThisQuery
2557+ <$> o .: "like_text"
2558+ -- <*> (optionalNE =<< o .:? "fields")
2559+ <*> o .:? "fields"
2560+ <*> o .:? "percent_terms_to_match"
2561+ <*> o .:? "min_term_freq"
2562+ <*> o .:? "max_query_terms"
2563+ -- <*> (optionalNE =<< o .:? "stop_words")
2564+ <*> o .:? "stop_words"
2565+ <*> o .:? "min_doc_freq"
2566+ <*> o .:? "max_doc_freq"
2567+ <*> o .:? "min_word_length"
2568+ <*> o .:? "max_word_length"
2569+ <*> o .:? "boost_terms"
2570+ <*> o .:? "boost"
2571+ <*> o .:? "analyzer"
2572+ -- optionalNE = maybe (pure Nothing) (fmap Just . parseNEJSON)
2573+
2574+instance ToJSON IndicesQuery where
2575+ toJSON (IndicesQuery indices query noMatch) =
2576+ omitNulls [ "indices" .= indices
2577+ , "no_match_query" .= noMatch
2578+ , "query" .= query ]
2579+
2580+instance FromJSON IndicesQuery where
2581+ parseJSON = withObject "IndicesQuery" parse
2582+ where parse o = IndicesQuery
2583+ <$> o .:? "indices" .!= []
2584+ <*> o .: "query"
2585+ <*> o .:? "no_match_query"
2586+
2587+instance ToJSON HasParentQuery where
2588+ toJSON (HasParentQuery queryType query scoreType) =
2589+ omitNulls [ "parent_type" .= queryType
2590+ , "score_type" .= scoreType
2591+ , "query" .= query ]
2592+
2593+instance FromJSON HasParentQuery where
2594+ parseJSON = withObject "HasParentQuery" parse
2595+ where parse o = HasParentQuery
2596+ <$> o .: "parent_type"
2597+ <*> o .: "query"
2598+ <*> o .:? "score_type"
2599+
2600+instance ToJSON HasChildQuery where
2601+ toJSON (HasChildQuery queryType query scoreType) =
2602+ omitNulls [ "query" .= query
2603+ , "score_type" .= scoreType
2604+ , "type" .= queryType ]
2605+
2606+instance FromJSON HasChildQuery where
2607+ parseJSON = withObject "HasChildQuery" parse
2608+ where parse o = HasChildQuery
2609+ <$> o .: "type"
2610+ <*> o .: "query"
2611+ <*> o .:? "score_type"
2612+
2613+instance ToJSON FuzzyQuery where
2614+ toJSON (FuzzyQuery (FieldName fieldName) queryText
2615+ prefixLength maxEx fuzziness boost) =
2616+ object [ fieldName .= omitNulls base ]
2617+ where base = [ "value" .= queryText
2618+ , "fuzziness" .= fuzziness
2619+ , "prefix_length" .= prefixLength
2620+ , "boost" .= boost
2621+ , "max_expansions" .= maxEx ]
2622+
2623+instance FromJSON FuzzyQuery where
2624+ parseJSON = withObject "FuzzyQuery" parse
2625+ where parse = fieldTagged $ \fn o ->
2626+ FuzzyQuery fn
2627+ <$> o .: "value"
2628+ <*> o .: "prefix_length"
2629+ <*> o .: "max_expansions"
2630+ <*> o .: "fuzziness"
2631+ <*> o .:? "boost"
2632+
2633+instance ToJSON FuzzyLikeFieldQuery where
2634+ toJSON (FuzzyLikeFieldQuery (FieldName fieldName)
2635+ fieldText maxTerms ignoreFreq fuzziness prefixLength
2636+ boost analyzer) =
2637+ object [ fieldName .=
2638+ omitNulls [ "like_text" .= fieldText
2639+ , "max_query_terms" .= maxTerms
2640+ , "ignore_tf" .= ignoreFreq
2641+ , "fuzziness" .= fuzziness
2642+ , "prefix_length" .= prefixLength
2643+ , "analyzer" .= analyzer
2644+ , "boost" .= boost ]]
2645+
2646+instance FromJSON FuzzyLikeFieldQuery where
2647+ parseJSON = withObject "FuzzyLikeFieldQuery" parse
2648+ where parse = fieldTagged $ \fn o ->
2649+ FuzzyLikeFieldQuery fn
2650+ <$> o .: "like_text"
2651+ <*> o .: "max_query_terms"
2652+ <*> o .: "ignore_tf"
2653+ <*> o .: "fuzziness"
2654+ <*> o .: "prefix_length"
2655+ <*> o .: "boost"
2656+ <*> o .:? "analyzer"
2657+
2658+instance ToJSON FuzzyLikeThisQuery where
2659+ toJSON (FuzzyLikeThisQuery fields text maxTerms
2660+ ignoreFreq fuzziness prefixLength boost analyzer) =
2661+ omitNulls base
2662+ where base = [ "fields" .= fields
2663+ , "like_text" .= text
2664+ , "max_query_terms" .= maxTerms
2665+ , "ignore_tf" .= ignoreFreq
2666+ , "fuzziness" .= fuzziness
2667+ , "prefix_length" .= prefixLength
2668+ , "analyzer" .= analyzer
2669+ , "boost" .= boost ]
2670+
2671+instance FromJSON FuzzyLikeThisQuery where
2672+ parseJSON = withObject "FuzzyLikeThisQuery" parse
2673+ where parse o = FuzzyLikeThisQuery
2674+ <$> o .:? "fields" .!= []
2675+ <*> o .: "like_text"
2676+ <*> o .: "max_query_terms"
2677+ <*> o .: "ignore_tf"
2678+ <*> o .: "fuzziness"
2679+ <*> o .: "prefix_length"
2680+ <*> o .: "boost"
2681+ <*> o .:? "analyzer"
2682+
2683+instance ToJSON DisMaxQuery where
2684+ toJSON (DisMaxQuery queries tiebreaker boost) =
2685+ omitNulls base
2686+ where base = [ "queries" .= queries
2687+ , "boost" .= boost
2688+ , "tie_breaker" .= tiebreaker ]
2689+
2690+instance FromJSON DisMaxQuery where
2691+ parseJSON = withObject "DisMaxQuery" parse
2692+ where parse o = DisMaxQuery
2693+ <$> o .:? "queries" .!= []
2694+ <*> o .: "tie_breaker"
2695+ <*> o .:? "boost"
2696+
2697+instance ToJSON CommonTermsQuery where
2698+ toJSON (CommonTermsQuery (FieldName fieldName)
2699+ (QueryString query) cf lfo hfo msm
2700+ boost analyzer disableCoord) =
2701+ object [fieldName .= omitNulls base ]
2702+ where base = [ "query" .= query
2703+ , "cutoff_frequency" .= cf
2704+ , "low_freq_operator" .= lfo
2705+ , "minimum_should_match" .= msm
2706+ , "boost" .= boost
2707+ , "analyzer" .= analyzer
2708+ , "disable_coord" .= disableCoord
2709+ , "high_freq_operator" .= hfo ]
2710+
2711+instance FromJSON CommonTermsQuery where
2712+ parseJSON = withObject "CommonTermsQuery" parse
2713+ where parse = fieldTagged $ \fn o ->
2714+ CommonTermsQuery fn
2715+ <$> o .: "query"
2716+ <*> o .: "cutoff_frequency"
2717+ <*> o .: "low_freq_operator"
2718+ <*> o .: "high_freq_operator"
2719+ <*> o .:? "minimum_should_match"
2720+ <*> o .:? "boost"
2721+ <*> o .:? "analyzer"
2722+ <*> o .:? "disable_coord"
2723+
2724+instance ToJSON CommonMinimumMatch where
2725+ toJSON (CommonMinimumMatch mm) = toJSON mm
2726+ toJSON (CommonMinimumMatchHighLow (MinimumMatchHighLow lowF highF)) =
2727+ object [ "low_freq" .= lowF
2728+ , "high_freq" .= highF ]
2729+
2730+instance FromJSON CommonMinimumMatch where
2731+ parseJSON v = parseMinimum v
2732+ <|> parseMinimumHighLow v
2733+ where parseMinimum = fmap CommonMinimumMatch . parseJSON
2734+ parseMinimumHighLow = fmap CommonMinimumMatchHighLow . withObject "CommonMinimumMatchHighLow" (\o ->
2735+ MinimumMatchHighLow
2736+ <$> o .: "low_freq"
2737+ <*> o .: "high_freq")
2738+
2739+
2740+instance ToJSON BoostingQuery where
2741+ toJSON (BoostingQuery bqPositiveQuery bqNegativeQuery bqNegativeBoost) =
2742+ object [ "positive" .= bqPositiveQuery
2743+ , "negative" .= bqNegativeQuery
2744+ , "negative_boost" .= bqNegativeBoost ]
2745+
2746+instance FromJSON BoostingQuery where
2747+ parseJSON = withObject "BoostingQuery" parse
2748+ where parse o = BoostingQuery
2749+ <$> o .: "positive"
2750+ <*> o .: "negative"
2751+ <*> o .: "negative_boost"
2752+
2753+instance ToJSON BoolQuery where
2754+ toJSON (BoolQuery mustM filterM' notM shouldM bqMin boost disableCoord) =
2755+ omitNulls base
2756+ where base = [ "must" .= mustM
2757+ , "filter" .= filterM'
2758+ , "must_not" .= notM
2759+ , "should" .= shouldM
2760+ , "minimum_should_match" .= bqMin
2761+ , "boost" .= boost
2762+ , "disable_coord" .= disableCoord ]
2763+
2764+instance FromJSON BoolQuery where
2765+ parseJSON = withObject "BoolQuery" parse
2766+ where parse o = BoolQuery
2767+ <$> o .:? "must" .!= []
2768+ <*> o .:? "filter" .!= []
2769+ <*> o .:? "must_not" .!= []
2770+ <*> o .:? "should" .!= []
2771+ <*> o .:? "minimum_should_match"
2772+ <*> o .:? "boost"
2773+ <*> o .:? "disable_coord"
2774+
2775+instance ToJSON MatchQuery where
2776+ toJSON (MatchQuery (FieldName fieldName)
2777+ (QueryString mqQueryString) booleanOperator
2778+ zeroTermsQuery cutoffFrequency matchQueryType
2779+ analyzer maxExpansions lenient boost) =
2780+ object [ fieldName .= omitNulls base ]
2781+ where base = [ "query" .= mqQueryString
2782+ , "operator" .= booleanOperator
2783+ , "zero_terms_query" .= zeroTermsQuery
2784+ , "cutoff_frequency" .= cutoffFrequency
2785+ , "type" .= matchQueryType
2786+ , "analyzer" .= analyzer
2787+ , "max_expansions" .= maxExpansions
2788+ , "lenient" .= lenient
2789+ , "boost" .= boost ]
2790+
2791+instance FromJSON MatchQuery where
2792+ parseJSON = withObject "MatchQuery" parse
2793+ where parse = fieldTagged $ \fn o ->
2794+ MatchQuery fn
2795+ <$> o .: "query"
2796+ <*> o .: "operator"
2797+ <*> o .: "zero_terms_query"
2798+ <*> o .:? "cutoff_frequency"
2799+ <*> o .:? "type"
2800+ <*> o .:? "analyzer"
2801+ <*> o .:? "max_expansions"
2802+ <*> o .:? "lenient"
2803+ <*> o .:? "boost"
2804+
2805+instance ToJSON MultiMatchQuery where
2806+ toJSON (MultiMatchQuery fields (QueryString query) boolOp
2807+ ztQ tb mmqt cf analyzer maxEx lenient) =
2808+ object ["multi_match" .= omitNulls base]
2809+ where base = [ "fields" .= fmap toJSON fields
2810+ , "query" .= query
2811+ , "operator" .= boolOp
2812+ , "zero_terms_query" .= ztQ
2813+ , "tiebreaker" .= tb
2814+ , "type" .= mmqt
2815+ , "cutoff_frequency" .= cf
2816+ , "analyzer" .= analyzer
2817+ , "max_expansions" .= maxEx
2818+ , "lenient" .= lenient ]
2819+
2820+instance FromJSON MultiMatchQuery where
2821+ parseJSON = withObject "MultiMatchQuery" parse
2822+ where parse raw = do o <- raw .: "multi_match"
2823+ MultiMatchQuery
2824+ <$> o .:? "fields" .!= []
2825+ <*> o .: "query"
2826+ <*> o .: "operator"
2827+ <*> o .: "zero_terms_query"
2828+ <*> o .:? "tiebreaker"
2829+ <*> o .:? "type"
2830+ <*> o .:? "cutoff_frequency"
2831+ <*> o .:? "analyzer"
2832+ <*> o .:? "max_expansions"
2833+ <*> o .:? "lenient"
2834+
2835+instance ToJSON MultiMatchQueryType where
2836+ toJSON MultiMatchBestFields = "best_fields"
2837+ toJSON MultiMatchMostFields = "most_fields"
2838+ toJSON MultiMatchCrossFields = "cross_fields"
2839+ toJSON MultiMatchPhrase = "phrase"
2840+ toJSON MultiMatchPhrasePrefix = "phrase_prefix"
2841+
2842+instance FromJSON MultiMatchQueryType where
2843+ parseJSON = withText "MultiMatchPhrasePrefix" parse
2844+ where parse "best_fields" = pure MultiMatchBestFields
2845+ parse "most_fields" = pure MultiMatchMostFields
2846+ parse "cross_fields" = pure MultiMatchCrossFields
2847+ parse "phrase" = pure MultiMatchPhrase
2848+ parse "phrase_prefix" = pure MultiMatchPhrasePrefix
2849+ parse t = fail ("Unexpected MultiMatchPhrasePrefix: " <> show t)
2850+
2851+instance ToJSON BooleanOperator where
2852+ toJSON And = String "and"
2853+ toJSON Or = String "or"
2854+
2855+instance FromJSON BooleanOperator where
2856+ parseJSON = withText "BooleanOperator" parse
2857+ where parse "and" = pure And
2858+ parse "or" = pure Or
2859+ parse o = fail ("Unexpected BooleanOperator: " <> show o)
2860+
2861+instance ToJSON ZeroTermsQuery where
2862+ toJSON ZeroTermsNone = String "none"
2863+ toJSON ZeroTermsAll = String "all"
2864+
2865+instance FromJSON ZeroTermsQuery where
2866+ parseJSON = withText "ZeroTermsQuery" parse
2867+ where parse "none" = pure ZeroTermsNone
2868+ parse "all" = pure ZeroTermsAll
2869+ parse q = fail ("Unexpected ZeroTermsQuery: " <> show q)
2870+
2871+instance ToJSON MatchQueryType where
2872+ toJSON MatchPhrase = "phrase"
2873+ toJSON MatchPhrasePrefix = "phrase_prefix"
2874+
2875+instance FromJSON MatchQueryType where
2876+ parseJSON = withText "MatchQueryType" parse
2877+ where parse "phrase" = pure MatchPhrase
2878+ parse "phrase_prefix" = pure MatchPhrasePrefix
2879+ parse t = fail ("Unexpected MatchQueryType: " <> show t)
2880+
2881+instance FromJSON Status where
2882+ parseJSON (Object v) = Status <$>
2883+ v .: "name" <*>
2884+ v .: "cluster_name" <*>
2885+ v .: "cluster_uuid" <*>
2886+ v .: "version" <*>
2887+ v .: "tagline"
2888+ parseJSON _ = empty
2889+
2890+
2891+instance ToJSON IndexSettings where
2892+ toJSON (IndexSettings s r) = object ["settings" .=
2893+ object ["index" .=
2894+ object ["number_of_shards" .= s, "number_of_replicas" .= r]
2895+ ]
2896+ ]
2897+
2898+instance FromJSON IndexSettings where
2899+ parseJSON = withObject "IndexSettings" parse
2900+ where parse o = do s <- o .: "settings"
2901+ i <- s .: "index"
2902+ IndexSettings <$> i .: "number_of_shards"
2903+ <*> i .: "number_of_replicas"
2904+
2905+instance ToJSON UpdatableIndexSetting where
2906+ toJSON (NumberOfReplicas x) = oPath ("index" :| ["number_of_replicas"]) x
2907+ toJSON (AutoExpandReplicas x) = oPath ("index" :| ["auto_expand_replicas"]) x
2908+ toJSON (RefreshInterval x) = oPath ("index" :| ["refresh_interval"]) (NominalDiffTimeJSON x)
2909+ toJSON (IndexConcurrency x) = oPath ("index" :| ["concurrency"]) x
2910+ toJSON (FailOnMergeFailure x) = oPath ("index" :| ["fail_on_merge_failure"]) x
2911+ toJSON (TranslogFlushThresholdOps x) = oPath ("index" :| ["translog", "flush_threshold_ops"]) x
2912+ toJSON (TranslogFlushThresholdSize x) = oPath ("index" :| ["translog", "flush_threshold_size"]) x
2913+ toJSON (TranslogFlushThresholdPeriod x) = oPath ("index" :| ["translog", "flush_threshold_period"]) (NominalDiffTimeJSON x)
2914+ toJSON (TranslogDisableFlush x) = oPath ("index" :| ["translog", "disable_flush"]) x
2915+ toJSON (CacheFilterMaxSize x) = oPath ("index" :| ["cache", "filter", "max_size"]) x
2916+ toJSON (CacheFilterExpire x) = oPath ("index" :| ["cache", "filter", "expire"]) (NominalDiffTimeJSON <$> x)
2917+ toJSON (GatewaySnapshotInterval x) = oPath ("index" :| ["gateway", "snapshot_interval"]) (NominalDiffTimeJSON x)
2918+ toJSON (RoutingAllocationInclude fs) = oPath ("index" :| ["routing", "allocation", "include"]) (attrFilterJSON fs)
2919+ toJSON (RoutingAllocationExclude fs) = oPath ("index" :| ["routing", "allocation", "exclude"]) (attrFilterJSON fs)
2920+ toJSON (RoutingAllocationRequire fs) = oPath ("index" :| ["routing", "allocation", "require"]) (attrFilterJSON fs)
2921+ toJSON (RoutingAllocationEnable x) = oPath ("index" :| ["routing", "allocation", "enable"]) x
2922+ toJSON (RoutingAllocationShardsPerNode x) = oPath ("index" :| ["routing", "allocation", "total_shards_per_node"]) x
2923+ toJSON (RecoveryInitialShards x) = oPath ("index" :| ["recovery", "initial_shards"]) x
2924+ toJSON (GCDeletes x) = oPath ("index" :| ["gc_deletes"]) (NominalDiffTimeJSON x)
2925+ toJSON (TTLDisablePurge x) = oPath ("index" :| ["ttl", "disable_purge"]) x
2926+ toJSON (TranslogFSType x) = oPath ("index" :| ["translog", "fs", "type"]) x
2927+ toJSON (IndexCompoundFormat x) = oPath ("index" :| ["compound_format"]) x
2928+ toJSON (IndexCompoundOnFlush x) = oPath ("index" :| ["compound_on_flush"]) x
2929+ toJSON (WarmerEnabled x) = oPath ("index" :| ["warmer", "enabled"]) x
2930+ toJSON (BlocksReadOnly x) = oPath ("blocks" :| ["read_only"]) x
2931+ toJSON (BlocksRead x) = oPath ("blocks" :| ["read"]) x
2932+ toJSON (BlocksWrite x) = oPath ("blocks" :| ["write"]) x
2933+ toJSON (BlocksMetaData x) = oPath ("blocks" :| ["metadata"]) x
2934+
2935+instance FromJSON UpdatableIndexSetting where
2936+ parseJSON = withObject "UpdatableIndexSetting" parse
2937+ where parse o = numberOfReplicas `taggedAt` ["index", "number_of_replicas"]
2938+ <|> autoExpandReplicas `taggedAt` ["index", "auto_expand_replicas"]
2939+ <|> refreshInterval `taggedAt` ["index", "refresh_interval"]
2940+ <|> indexConcurrency `taggedAt` ["index", "concurrency"]
2941+ <|> failOnMergeFailure `taggedAt` ["index", "fail_on_merge_failure"]
2942+ <|> translogFlushThresholdOps `taggedAt` ["index", "translog", "flush_threshold_ops"]
2943+ <|> translogFlushThresholdSize `taggedAt` ["index", "translog", "flush_threshold_size"]
2944+ <|> translogFlushThresholdPeriod `taggedAt` ["index", "translog", "flush_threshold_period"]
2945+ <|> translogDisableFlush `taggedAt` ["index", "translog", "disable_flush"]
2946+ <|> cacheFilterMaxSize `taggedAt` ["index", "cache", "filter", "max_size"]
2947+ <|> cacheFilterExpire `taggedAt` ["index", "cache", "filter", "expire"]
2948+ <|> gatewaySnapshotInterval `taggedAt` ["index", "gateway", "snapshot_interval"]
2949+ <|> routingAllocationInclude `taggedAt` ["index", "routing", "allocation", "include"]
2950+ <|> routingAllocationExclude `taggedAt` ["index", "routing", "allocation", "exclude"]
2951+ <|> routingAllocationRequire `taggedAt` ["index", "routing", "allocation", "require"]
2952+ <|> routingAllocationEnable `taggedAt` ["index", "routing", "allocation", "enable"]
2953+ <|> routingAllocationShardsPerNode `taggedAt` ["index", "routing", "allocation", "total_shards_per_node"]
2954+ <|> recoveryInitialShards `taggedAt` ["index", "recovery", "initial_shards"]
2955+ <|> gcDeletes `taggedAt` ["index", "gc_deletes"]
2956+ <|> ttlDisablePurge `taggedAt` ["index", "ttl", "disable_purge"]
2957+ <|> translogFSType `taggedAt` ["index", "translog", "fs", "type"]
2958+ <|> compoundFormat `taggedAt` ["index", "compound_format"]
2959+ <|> compoundOnFlush `taggedAt` ["index", "compound_on_flush"]
2960+ <|> warmerEnabled `taggedAt` ["index", "warmer", "enabled"]
2961+ <|> blocksReadOnly `taggedAt` ["blocks", "read_only"]
2962+ <|> blocksRead `taggedAt` ["blocks", "read"]
2963+ <|> blocksWrite `taggedAt` ["blocks", "write"]
2964+ <|> blocksMetaData `taggedAt` ["blocks", "metadata"]
2965+ where taggedAt f ks = taggedAt' f (Object o) ks
2966+ taggedAt' f v [] = f =<< (parseJSON v <|> (parseJSON (unStringlyTypeJSON v)))
2967+ taggedAt' f v (k:ks) = withObject "Object" (\o -> do v' <- o .: k
2968+ taggedAt' f v' ks) v
2969+ numberOfReplicas = pure . NumberOfReplicas
2970+ autoExpandReplicas = pure . AutoExpandReplicas
2971+ refreshInterval = pure . RefreshInterval . ndtJSON
2972+ indexConcurrency = pure . IndexConcurrency
2973+ failOnMergeFailure = pure . FailOnMergeFailure
2974+ translogFlushThresholdOps = pure . TranslogFlushThresholdOps
2975+ translogFlushThresholdSize = pure . TranslogFlushThresholdSize
2976+ translogFlushThresholdPeriod = pure . TranslogFlushThresholdPeriod . ndtJSON
2977+ translogDisableFlush = pure . TranslogDisableFlush
2978+ cacheFilterMaxSize = pure . CacheFilterMaxSize
2979+ cacheFilterExpire = pure . CacheFilterExpire . fmap ndtJSON
2980+ gatewaySnapshotInterval = pure . GatewaySnapshotInterval . ndtJSON
2981+ routingAllocationInclude = fmap RoutingAllocationInclude . parseAttrFilter
2982+ routingAllocationExclude = fmap RoutingAllocationExclude . parseAttrFilter
2983+ routingAllocationRequire = fmap RoutingAllocationRequire . parseAttrFilter
2984+ routingAllocationEnable = pure . RoutingAllocationEnable
2985+ routingAllocationShardsPerNode = pure . RoutingAllocationShardsPerNode
2986+ recoveryInitialShards = pure . RecoveryInitialShards
2987+ gcDeletes = pure . GCDeletes . ndtJSON
2988+ ttlDisablePurge = pure . TTLDisablePurge
2989+ translogFSType = pure . TranslogFSType
2990+ compoundFormat = pure . IndexCompoundFormat
2991+ compoundOnFlush = pure . IndexCompoundOnFlush
2992+ warmerEnabled = pure . WarmerEnabled
2993+ blocksReadOnly = pure . BlocksReadOnly
2994+ blocksRead = pure . BlocksRead
2995+ blocksWrite = pure . BlocksWrite
2996+ blocksMetaData = pure . BlocksMetaData
2997+
2998+instance FromJSON IndexSettingsSummary where
2999+ parseJSON = withObject "IndexSettingsSummary" parse
3000+ where parse o = case HM.toList o of
3001+ [(ixn, v@(Object o'))] -> IndexSettingsSummary (IndexName ixn)
3002+ <$> parseJSON v
3003+ <*> (fmap (filter (not . redundant)) . parseSettings =<< o' .: "settings")
3004+ _ -> fail "Expected single-key object with index name"
3005+ redundant (NumberOfReplicas _) = True
3006+ redundant _ = False
3007+
3008+-- | For some reason in several settings APIs, all leaf values get returned
3009+-- as strings. This function attepmts to recover from this for all
3010+-- non-recursive JSON types. If nothing can be done, the value is left alone.
3011+unStringlyTypeJSON :: Value -> Value
3012+unStringlyTypeJSON (String "true") = Bool True
3013+unStringlyTypeJSON (String "false") = Bool False
3014+unStringlyTypeJSON (String "null") = Null
3015+unStringlyTypeJSON v@(String t) = case readMay (T.unpack t) of
3016+ Just n -> Number n
3017+ Nothing -> v
3018+unStringlyTypeJSON v = v
3019+
3020+
3021+parseSettings :: Object -> Parser [UpdatableIndexSetting]
3022+parseSettings o = do
3023+ o' <- o .: "index"
3024+ -- slice the index object into singleton hashmaps and try to parse each
3025+ parses <- forM (HM.toList o') $ \(k, v) -> do
3026+ -- blocks are now nested into the "index" key, which is not how they're serialized
3027+ let atRoot = Object (HM.singleton k v)
3028+ let atIndex = Object (HM.singleton "index" atRoot)
3029+ optional (parseJSON atRoot <|> parseJSON atIndex)
3030+ return (catMaybes parses)
3031+
3032+oPath :: ToJSON a => NonEmpty Text -> a -> Value
3033+oPath (k :| []) v = object [k .= v]
3034+oPath (k:| (h:t)) v = object [k .= oPath (h :| t) v]
3035+
3036+attrFilterJSON :: NonEmpty NodeAttrFilter -> Value
3037+attrFilterJSON fs = object [ n .= T.intercalate "," (toList vs)
3038+ | NodeAttrFilter (NodeAttrName n) vs <- toList fs]
3039+
3040+parseAttrFilter :: Value -> Parser (NonEmpty NodeAttrFilter)
3041+parseAttrFilter = withObject "NonEmpty NodeAttrFilter" parse
3042+ where parse o = case HM.toList o of
3043+ [] -> fail "Expected non-empty list of NodeAttrFilters"
3044+ x:xs -> DT.mapM (uncurry parse') (x :| xs)
3045+ parse' n = withText "Text" $ \t ->
3046+ case T.splitOn "," t of
3047+ fv:fvs -> return (NodeAttrFilter (NodeAttrName n) (fv :| fvs))
3048+ [] -> fail "Expected non-empty list of filter values"
3049+
3050+instance ToJSON ReplicaBounds where
3051+ toJSON (ReplicasBounded a b) = String (showText a <> "-" <> showText b)
3052+ toJSON (ReplicasLowerBounded a) = String (showText a <> "-all")
3053+ toJSON ReplicasUnbounded = Bool False
3054+
3055+instance FromJSON ReplicaBounds where
3056+ parseJSON v = withText "ReplicaBounds" parseText v
3057+ <|> withBool "ReplicaBounds" parseBool v
3058+ where parseText t = case T.splitOn "-" t of
3059+ [a, "all"] -> ReplicasLowerBounded <$> parseReadText a
3060+ [a, b] -> ReplicasBounded <$> parseReadText a
3061+ <*> parseReadText b
3062+ _ -> fail ("Could not parse ReplicaBounds: " <> show t)
3063+ parseBool False = pure ReplicasUnbounded
3064+ parseBool _ = fail "ReplicasUnbounded cannot be represented with True"
3065+
3066+instance ToJSON AllocationPolicy where
3067+ toJSON AllocAll = String "all"
3068+ toJSON AllocPrimaries = String "primaries"
3069+ toJSON AllocNewPrimaries = String "new_primaries"
3070+ toJSON AllocNone = String "none"
3071+
3072+instance FromJSON AllocationPolicy where
3073+ parseJSON = withText "AllocationPolicy" parse
3074+ where parse "all" = pure AllocAll
3075+ parse "primaries" = pure AllocPrimaries
3076+ parse "new_primaries" = pure AllocNewPrimaries
3077+ parse "none" = pure AllocNone
3078+ parse t = fail ("Invlaid AllocationPolicy: " <> show t)
3079+
3080+instance ToJSON InitialShardCount where
3081+ toJSON QuorumShards = String "quorum"
3082+ toJSON QuorumMinus1Shards = String "quorum-1"
3083+ toJSON FullShards = String "full"
3084+ toJSON FullMinus1Shards = String "full-1"
3085+ toJSON (ExplicitShards x) = toJSON x
3086+
3087+instance FromJSON InitialShardCount where
3088+ parseJSON v = withText "InitialShardCount" parseText v
3089+ <|> ExplicitShards <$> parseJSON v
3090+ where parseText "quorum" = pure QuorumShards
3091+ parseText "quorum-1" = pure QuorumMinus1Shards
3092+ parseText "full" = pure FullShards
3093+ parseText "full-1" = pure FullMinus1Shards
3094+ parseText _ = mzero
3095+
3096+instance ToJSON FSType where
3097+ toJSON FSSimple = "simple"
3098+ toJSON FSBuffered = "buffered"
3099+
3100+instance FromJSON FSType where
3101+ parseJSON = withText "FSType" parse
3102+ where parse "simple" = pure FSSimple
3103+ parse "buffered" = pure FSBuffered
3104+ parse t = fail ("Invalid FSType: " <> show t)
3105+
3106+instance ToJSON CompoundFormat where
3107+ toJSON (CompoundFileFormat x) = Bool x
3108+ toJSON (MergeSegmentVsTotalIndex x) = toJSON x
3109+
3110+instance FromJSON CompoundFormat where
3111+ parseJSON v = CompoundFileFormat <$> parseJSON v
3112+ <|> MergeSegmentVsTotalIndex <$> parseJSON v
3113+
3114+instance ToJSON NominalDiffTimeJSON where
3115+ toJSON (NominalDiffTimeJSON t) = String (showText (round t :: Integer) <> "s")
3116+
3117+instance FromJSON NominalDiffTimeJSON where
3118+ parseJSON = withText "NominalDiffTime" parse
3119+ where parse t = case T.takeEnd 1 t of
3120+ "s" -> NominalDiffTimeJSON . fromInteger <$> parseReadText (T.dropEnd 1 t)
3121+ _ -> fail "Invalid or missing NominalDiffTime unit (expected s)"
3122+
3123+instance ToJSON IndexTemplate where
3124+ toJSON (IndexTemplate p s m) = merge
3125+ (object [ "template" .= p
3126+ , "mappings" .= foldl' merge (object []) m
3127+ ])
3128+ (toJSON s)
3129+ where
3130+ merge (Object o1) (Object o2) = toJSON $ HM.union o1 o2
3131+ merge o Null = o
3132+ merge _ _ = undefined
3133+
3134+instance (FromJSON a) => FromJSON (EsResult a) where
3135+ parseJSON jsonVal@(Object v) = do
3136+ found <- v .:? "found" .!= False
3137+ fr <- if found
3138+ then parseJSON jsonVal
3139+ else return Nothing
3140+ EsResult <$> v .: "_index" <*>
3141+ v .: "_type" <*>
3142+ v .: "_id" <*>
3143+ pure fr
3144+ parseJSON _ = empty
3145+
3146+instance (FromJSON a) => FromJSON (EsResultFound a) where
3147+ parseJSON (Object v) = EsResultFound <$>
3148+ v .: "_version" <*>
3149+ v .: "_source"
3150+ parseJSON _ = empty
3151+
3152+instance FromJSON EsError where
3153+ parseJSON (Object v) = EsError <$>
3154+ v .: "status" <*>
3155+ (v .: "error" <|> (v .: "error" >>= (.: "reason")))
3156+ parseJSON _ = empty
3157+
3158+instance FromJSON IndexAliasesSummary where
3159+ parseJSON = withObject "IndexAliasesSummary" parse
3160+ where parse o = IndexAliasesSummary . mconcat <$> mapM (uncurry go) (HM.toList o)
3161+ go ixn = withObject "index aliases" $ \ia -> do
3162+ aliases <- ia .:? "aliases" .!= mempty
3163+ forM (HM.toList aliases) $ \(aName, v) -> do
3164+ let indexAlias = IndexAlias (IndexName ixn) (IndexAliasName (IndexName aName))
3165+ IndexAliasSummary indexAlias <$> parseJSON v
3166+
3167+
3168+instance ToJSON IndexAliasAction where
3169+ toJSON (AddAlias ia opts) = object ["add" .= (iaObj <> optsObj)]
3170+ where Object iaObj = toJSON ia
3171+ Object optsObj = toJSON opts
3172+ toJSON (RemoveAlias ia) = object ["remove" .= iaObj]
3173+ where Object iaObj = toJSON ia
3174+
3175+instance ToJSON IndexAlias where
3176+ toJSON IndexAlias {..} = object ["index" .= srcIndex
3177+ , "alias" .= indexAlias
3178+ ]
3179+
3180+instance ToJSON IndexAliasCreate where
3181+ toJSON IndexAliasCreate {..} = Object (filterObj <> routingObj)
3182+ where filterObj = maybe mempty (HM.singleton "filter" . toJSON) aliasCreateFilter
3183+ Object routingObj = maybe (Object mempty) toJSON aliasCreateRouting
3184+
3185+instance ToJSON AliasRouting where
3186+ toJSON (AllAliasRouting v) = object ["routing" .= v]
3187+ toJSON (GranularAliasRouting srch idx) = object (catMaybes prs)
3188+ where prs = [("search_routing" .=) <$> srch
3189+ ,("index_routing" .=) <$> idx]
3190+
3191+instance FromJSON AliasRouting where
3192+ parseJSON = withObject "AliasRouting" parse
3193+ where parse o = parseAll o <|> parseGranular o
3194+ parseAll o = AllAliasRouting <$> o .: "routing"
3195+ parseGranular o = do
3196+ sr <- o .:? "search_routing"
3197+ ir <- o .:? "index_routing"
3198+ if isNothing sr && isNothing ir
3199+ then fail "Both search_routing and index_routing can't be blank"
3200+ else return (GranularAliasRouting sr ir)
3201+
3202+instance FromJSON IndexAliasCreate where
3203+ parseJSON v = withObject "IndexAliasCreate" parse v
3204+ where parse o = IndexAliasCreate <$> optional (parseJSON v)
3205+ <*> o .:? "filter"
3206+
3207+instance ToJSON SearchAliasRouting where
3208+ toJSON (SearchAliasRouting rvs) = toJSON (T.intercalate "," (routingValue <$> toList rvs))
3209+
3210+instance FromJSON SearchAliasRouting where
3211+ parseJSON = withText "SearchAliasRouting" parse
3212+ where parse t = SearchAliasRouting <$> parseNEJSON (String <$> T.splitOn "," t)
3213+
3214+instance ToJSON Search where
3215+ toJSON (Search mquery sFilter sort searchAggs highlight sTrackSortScores sFrom sSize _ sFields sSource) =
3216+ omitNulls [ "query" .= query'
3217+ , "sort" .= sort
3218+ , "aggregations" .= searchAggs
3219+ , "highlight" .= highlight
3220+ , "from" .= sFrom
3221+ , "size" .= sSize
3222+ , "track_scores" .= sTrackSortScores
3223+ , "fields" .= sFields
3224+ , "_source" .= sSource]
3225+
3226+ where query' = case sFilter of
3227+ Nothing -> mquery
3228+ Just x -> Just . QueryBoolQuery $ mkBoolQuery (maybeToList mquery) [x] [] []
3229+
3230+instance ToJSON Source where
3231+ toJSON NoSource = toJSON False
3232+ toJSON (SourcePatterns patterns) = toJSON patterns
3233+ toJSON (SourceIncludeExclude incl excl) = object [ "includes" .= incl, "excludes" .= excl ]
3234+
3235+instance ToJSON PatternOrPatterns where
3236+ toJSON (PopPattern pattern) = toJSON pattern
3237+ toJSON (PopPatterns patterns) = toJSON patterns
3238+
3239+instance ToJSON Include where
3240+ toJSON (Include patterns) = toJSON patterns
3241+
3242+instance ToJSON Exclude where
3243+ toJSON (Exclude patterns) = toJSON patterns
3244+
3245+instance ToJSON Pattern where
3246+ toJSON (Pattern pattern) = toJSON pattern
3247+
3248+
3249+instance ToJSON FieldHighlight where
3250+ toJSON (FieldHighlight (FieldName fName) (Just fSettings)) =
3251+ object [ fName .= fSettings ]
3252+ toJSON (FieldHighlight (FieldName fName) Nothing) =
3253+ object [ fName .= emptyObject ]
3254+
3255+instance ToJSON Highlights where
3256+ toJSON (Highlights global fields) =
3257+ omitNulls (("fields" .= fields)
3258+ : highlightSettingsPairs global)
3259+
3260+instance ToJSON HighlightSettings where
3261+ toJSON hs = omitNulls (highlightSettingsPairs (Just hs))
3262+
3263+highlightSettingsPairs :: Maybe HighlightSettings -> [Pair]
3264+highlightSettingsPairs Nothing = []
3265+highlightSettingsPairs (Just (Plain plh)) = plainHighPairs (Just plh)
3266+highlightSettingsPairs (Just (Postings ph)) = postHighPairs (Just ph)
3267+highlightSettingsPairs (Just (FastVector fvh)) = fastVectorHighPairs (Just fvh)
3268+
3269+
3270+plainHighPairs :: Maybe PlainHighlight -> [Pair]
3271+plainHighPairs Nothing = []
3272+plainHighPairs (Just (PlainHighlight plCom plNonPost)) =
3273+ [ "type" .= String "plain"]
3274+ ++ commonHighlightPairs plCom
3275+ ++ nonPostingsToPairs plNonPost
3276+
3277+postHighPairs :: Maybe PostingsHighlight -> [Pair]
3278+postHighPairs Nothing = []
3279+postHighPairs (Just (PostingsHighlight pCom)) =
3280+ ("type" .= String "postings")
3281+ : commonHighlightPairs pCom
3282+
3283+fastVectorHighPairs :: Maybe FastVectorHighlight -> [Pair]
3284+fastVectorHighPairs Nothing = []
3285+fastVectorHighPairs (Just
3286+ (FastVectorHighlight fvCom fvNonPostSettings fvBoundChars
3287+ fvBoundMaxScan fvFragOff fvMatchedFields
3288+ fvPhraseLim)) =
3289+ [ "type" .= String "fvh"
3290+ , "boundary_chars" .= fvBoundChars
3291+ , "boundary_max_scan" .= fvBoundMaxScan
3292+ , "fragment_offset" .= fvFragOff
3293+ , "matched_fields" .= fvMatchedFields
3294+ , "phraseLimit" .= fvPhraseLim]
3295+ ++ commonHighlightPairs fvCom
3296+ ++ nonPostingsToPairs fvNonPostSettings
3297+
3298+deleteSeveral :: (Eq k, Hashable k) => [k] -> HM.HashMap k v -> HM.HashMap k v
3299+deleteSeveral ks hm = foldr HM.delete hm ks
3300+
3301+commonHighlightPairs :: Maybe CommonHighlight -> [Pair]
3302+commonHighlightPairs Nothing = []
3303+commonHighlightPairs (Just (CommonHighlight chScore chForceSource chTag chEncoder
3304+ chNoMatchSize chHighlightQuery
3305+ chRequireFieldMatch)) =
3306+ [ "order" .= chScore
3307+ , "force_source" .= chForceSource
3308+ , "encoder" .= chEncoder
3309+ , "no_match_size" .= chNoMatchSize
3310+ , "highlight_query" .= chHighlightQuery
3311+ , "require_fieldMatch" .= chRequireFieldMatch]
3312+ ++ highlightTagToPairs chTag
3313+
3314+
3315+nonPostingsToPairs :: Maybe NonPostings -> [Pair]
3316+nonPostingsToPairs Nothing = []
3317+nonPostingsToPairs (Just (NonPostings npFragSize npNumOfFrags)) =
3318+ [ "fragment_size" .= npFragSize
3319+ , "number_of_fragments" .= npNumOfFrags]
3320+
3321+parseNEJSON :: (FromJSON a) => [Value] -> Parser (NonEmpty a)
3322+parseNEJSON [] = fail "Expected non-empty list"
3323+parseNEJSON (x:xs) = DT.mapM parseJSON (x :| xs)
3324+
3325+
3326+instance ToJSON HighlightEncoder where
3327+ toJSON DefaultEncoder = String "default"
3328+ toJSON HTMLEncoder = String "html"
3329+
3330+highlightTagToPairs :: Maybe HighlightTag -> [Pair]
3331+highlightTagToPairs (Just (TagSchema _)) = [ "scheme" .= String "default"]
3332+highlightTagToPairs (Just (CustomTags (pre, post))) = [ "pre_tags" .= pre
3333+ , "post_tags" .= post]
3334+highlightTagToPairs Nothing = []
3335+
3336+instance ToJSON SortSpec where
3337+ toJSON (DefaultSortSpec
3338+ (DefaultSort (FieldName dsSortFieldName) dsSortOrder dsIgnoreUnmapped
3339+ dsSortMode dsMissingSort dsNestedFilter)) =
3340+ object [dsSortFieldName .= omitNulls base] where
3341+ base = [ "order" .= dsSortOrder
3342+ , "unmapped_type" .= dsIgnoreUnmapped
3343+ , "mode" .= dsSortMode
3344+ , "missing" .= dsMissingSort
3345+ , "nested_filter" .= dsNestedFilter ]
3346+
3347+ toJSON (GeoDistanceSortSpec gdsSortOrder (GeoPoint (FieldName field) gdsLatLon) units) =
3348+ object [ "unit" .= units
3349+ , field .= gdsLatLon
3350+ , "order" .= gdsSortOrder ]
3351+
3352+
3353+instance ToJSON SortOrder where
3354+ toJSON Ascending = String "asc"
3355+ toJSON Descending = String "desc"
3356+
3357+
3358+instance ToJSON SortMode where
3359+ toJSON SortMin = String "min"
3360+ toJSON SortMax = String "max"
3361+ toJSON SortSum = String "sum"
3362+ toJSON SortAvg = String "avg"
3363+
3364+
3365+instance ToJSON Missing where
3366+ toJSON LastMissing = String "_last"
3367+ toJSON FirstMissing = String "_first"
3368+ toJSON (CustomMissing txt) = String txt
3369+
3370+
3371+instance ToJSON ScoreType where
3372+ toJSON ScoreTypeMax = "max"
3373+ toJSON ScoreTypeAvg = "avg"
3374+ toJSON ScoreTypeSum = "sum"
3375+ toJSON ScoreTypeNone = "none"
3376+
3377+instance FromJSON ScoreType where
3378+ parseJSON = withText "ScoreType" parse
3379+ where parse "max" = pure ScoreTypeMax
3380+ parse "avg" = pure ScoreTypeAvg
3381+ parse "sum" = pure ScoreTypeSum
3382+ parse "none" = pure ScoreTypeNone
3383+ parse t = fail ("Unexpected ScoreType: " <> show t)
3384+
3385+instance ToJSON Distance where
3386+ toJSON (Distance dCoefficient dUnit) =
3387+ String boltedTogether where
3388+ coefText = showText dCoefficient
3389+ (String unitText) = toJSON dUnit
3390+ boltedTogether = mappend coefText unitText
3391+
3392+instance FromJSON Distance where
3393+ parseJSON = withText "Distance" parse
3394+ where parse t = Distance <$> parseCoeff nT
3395+ <*> parseJSON (String unitT)
3396+ where (nT, unitT) = T.span validForNumber t
3397+ -- may be a better way to do this
3398+ validForNumber '-' = True
3399+ validForNumber '.' = True
3400+ validForNumber 'e' = True
3401+ validForNumber c = isNumber c
3402+ parseCoeff "" = fail "Empty string cannot be parsed as number"
3403+ parseCoeff s = return (read (T.unpack s))
3404+
3405+instance ToJSON DistanceUnit where
3406+ toJSON Miles = String "mi"
3407+ toJSON Yards = String "yd"
3408+ toJSON Feet = String "ft"
3409+ toJSON Inches = String "in"
3410+ toJSON Kilometers = String "km"
3411+ toJSON Meters = String "m"
3412+ toJSON Centimeters = String "cm"
3413+ toJSON Millimeters = String "mm"
3414+ toJSON NauticalMiles = String "nmi"
3415+
3416+
3417+instance FromJSON DistanceUnit where
3418+ parseJSON = withText "DistanceUnit" parse
3419+ where parse "mi" = pure Miles
3420+ parse "yd" = pure Yards
3421+ parse "ft" = pure Feet
3422+ parse "in" = pure Inches
3423+ parse "km" = pure Kilometers
3424+ parse "m" = pure Meters
3425+ parse "cm" = pure Centimeters
3426+ parse "mm" = pure Millimeters
3427+ parse "nmi" = pure NauticalMiles
3428+ parse u = fail ("Unrecognized DistanceUnit: " <> show u)
3429+
3430+instance ToJSON DistanceType where
3431+ toJSON Arc = String "arc"
3432+ toJSON SloppyArc = String "sloppy_arc"
3433+ toJSON Plane = String "plane"
3434+
3435+instance FromJSON DistanceType where
3436+ parseJSON = withText "DistanceType" parse
3437+ where parse "arc" = pure Arc
3438+ parse "sloppy_arc" = pure SloppyArc
3439+ parse "plane" = pure Plane
3440+ parse t = fail ("Unrecognized DistanceType: " <> show t)
3441+
3442+
3443+instance ToJSON OptimizeBbox where
3444+ toJSON NoOptimizeBbox = String "none"
3445+ toJSON (OptimizeGeoFilterType gft) = toJSON gft
3446+
3447+instance FromJSON OptimizeBbox where
3448+ parseJSON v = withText "NoOptimizeBbox" parseNoOptimize v
3449+ <|> parseOptimize v
3450+ where parseNoOptimize "none" = pure NoOptimizeBbox
3451+ parseNoOptimize _ = mzero
3452+ parseOptimize = fmap OptimizeGeoFilterType . parseJSON
3453+
3454+instance ToJSON GeoBoundingBoxConstraint where
3455+ toJSON (GeoBoundingBoxConstraint
3456+ (FieldName gbbcGeoBBField) gbbcConstraintBox cache type') =
3457+ object [gbbcGeoBBField .= gbbcConstraintBox
3458+ , "_cache" .= cache
3459+ , "type" .= type']
3460+
3461+instance FromJSON GeoBoundingBoxConstraint where
3462+ parseJSON = withObject "GeoBoundingBoxConstraint" parse
3463+ where parse o = case HM.toList (deleteSeveral ["type", "_cache"] o) of
3464+ [(fn, v)] -> GeoBoundingBoxConstraint (FieldName fn)
3465+ <$> parseJSON v
3466+ <*> o .:? "_cache" .!= defaultCache
3467+ <*> o .: "type"
3468+ _ -> fail "Could not find field name for GeoBoundingBoxConstraint"
3469+
3470+instance ToJSON GeoFilterType where
3471+ toJSON GeoFilterMemory = String "memory"
3472+ toJSON GeoFilterIndexed = String "indexed"
3473+
3474+instance FromJSON GeoFilterType where
3475+ parseJSON = withText "GeoFilterType" parse
3476+ where parse "memory" = pure GeoFilterMemory
3477+ parse "indexed" = pure GeoFilterIndexed
3478+ parse t = fail ("Unrecognized GeoFilterType: " <> show t)
3479+
3480+instance ToJSON GeoBoundingBox where
3481+ toJSON (GeoBoundingBox gbbTopLeft gbbBottomRight) =
3482+ object ["top_left" .= gbbTopLeft
3483+ , "bottom_right" .= gbbBottomRight]
3484+
3485+instance FromJSON GeoBoundingBox where
3486+ parseJSON = withObject "GeoBoundingBox" parse
3487+ where parse o = GeoBoundingBox
3488+ <$> o .: "top_left"
3489+ <*> o .: "bottom_right"
3490+
3491+instance ToJSON LatLon where
3492+ toJSON (LatLon lLat lLon) =
3493+ object ["lat" .= lLat
3494+ , "lon" .= lLon]
3495+
3496+instance FromJSON LatLon where
3497+ parseJSON = withObject "LatLon" parse
3498+ where parse o = LatLon <$> o .: "lat"
3499+ <*> o .: "lon"
3500+
3501+-- index for smaller ranges, fielddata for longer ranges
3502+instance ToJSON RangeExecution where
3503+ toJSON RangeExecutionIndex = "index"
3504+ toJSON RangeExecutionFielddata = "fielddata"
3505+
3506+
3507+instance FromJSON RangeExecution where
3508+ parseJSON = withText "RangeExecution" parse
3509+ where parse "index" = pure RangeExecutionIndex
3510+ parse "fielddata" = pure RangeExecutionFielddata
3511+ parse t = error ("Unrecognized RangeExecution " <> show t)
3512+
3513+instance ToJSON RegexpFlags where
3514+ toJSON AllRegexpFlags = String "ALL"
3515+ toJSON NoRegexpFlags = String "NONE"
3516+ toJSON (SomeRegexpFlags (h :| fs)) = String $ T.intercalate "|" flagStrs
3517+ where flagStrs = map flagStr . nub $ h:fs
3518+ flagStr AnyString = "ANYSTRING"
3519+ flagStr Automaton = "AUTOMATON"
3520+ flagStr Complement = "COMPLEMENT"
3521+ flagStr Empty = "EMPTY"
3522+ flagStr Intersection = "INTERSECTION"
3523+ flagStr Interval = "INTERVAL"
3524+
3525+instance FromJSON RegexpFlags where
3526+ parseJSON = withText "RegexpFlags" parse
3527+ where parse "ALL" = pure AllRegexpFlags
3528+ parse "NONE" = pure NoRegexpFlags
3529+ parse t = SomeRegexpFlags <$> parseNEJSON (String <$> T.splitOn "|" t)
3530+
3531+instance FromJSON RegexpFlag where
3532+ parseJSON = withText "RegexpFlag" parse
3533+ where parse "ANYSTRING" = pure AnyString
3534+ parse "AUTOMATON" = pure Automaton
3535+ parse "COMPLEMENT" = pure Complement
3536+ parse "EMPTY" = pure Empty
3537+ parse "INTERSECTION" = pure Intersection
3538+ parse "INTERVAL" = pure Interval
3539+ parse f = fail ("Unknown RegexpFlag: " <> show f)
3540+
3541+instance ToJSON Term where
3542+ toJSON (Term field value) = object ["term" .= object
3543+ [field .= value]]
3544+
3545+instance FromJSON Term where
3546+ parseJSON = withObject "Term" parse
3547+ where parse o = do termObj <- o .: "term"
3548+ case HM.toList termObj of
3549+ [(fn, v)] -> Term fn <$> parseJSON v
3550+ _ -> fail "Expected object with 1 field-named key"
3551+
3552+instance ToJSON BoolMatch where
3553+ toJSON (MustMatch term cache) = object ["must" .= term,
3554+ "_cache" .= cache]
3555+ toJSON (MustNotMatch term cache) = object ["must_not" .= term,
3556+ "_cache" .= cache]
3557+ toJSON (ShouldMatch terms cache) = object ["should" .= fmap toJSON terms,
3558+ "_cache" .= cache]
3559+
3560+instance FromJSON BoolMatch where
3561+ parseJSON = withObject "BoolMatch" parse
3562+ where parse o = mustMatch `taggedWith` "must"
3563+ <|> mustNotMatch `taggedWith` "must_not"
3564+ <|> shouldMatch `taggedWith` "should"
3565+ where taggedWith parser k = parser =<< o .: k
3566+ mustMatch t = MustMatch t <$> o .:? "_cache" .!= defaultCache
3567+ mustNotMatch t = MustNotMatch t <$> o .:? "_cache" .!= defaultCache
3568+ shouldMatch t = ShouldMatch t <$> o .:? "_cache" .!= defaultCache
3569+
3570+instance (FromJSON a) => FromJSON (SearchResult a) where
3571+ parseJSON (Object v) = SearchResult <$>
3572+ v .: "took" <*>
3573+ v .: "timed_out" <*>
3574+ v .: "_shards" <*>
3575+ v .: "hits" <*>
3576+ v .:? "aggregations" <*>
3577+ v .:? "_scroll_id"
3578+ parseJSON _ = empty
3579+
3580+instance (FromJSON a) => FromJSON (SearchHits a) where
3581+ parseJSON (Object v) = SearchHits <$>
3582+ v .: "total" <*>
3583+ v .: "max_score" <*>
3584+ v .: "hits"
3585+ parseJSON _ = empty
3586+
3587+instance (FromJSON a) => FromJSON (Hit a) where
3588+ parseJSON (Object v) = Hit <$>
3589+ v .: "_index" <*>
3590+ v .: "_type" <*>
3591+ v .: "_id" <*>
3592+ v .: "_score" <*>
3593+ v .:? "_source" <*>
3594+ v .:? "highlight"
3595+ parseJSON _ = empty
3596+
3597+instance FromJSON ShardResult where
3598+ parseJSON (Object v) = ShardResult <$>
3599+ v .: "total" <*>
3600+ v .: "successful" <*>
3601+ v .: "failed"
3602+ parseJSON _ = empty
3603+
3604+
3605+instance FromJSON DocVersion where
3606+ parseJSON v = do
3607+ i <- parseJSON v
3608+ maybe (fail "DocVersion out of range") return $ mkDocVersion i
3609+
3610+-- This insanity is because ES *sometimes* returns Replica/Shard counts as strings
3611+instance FromJSON ReplicaCount where
3612+ parseJSON v = parseAsInt v
3613+ <|> parseAsString v
3614+ where parseAsInt = fmap ReplicaCount . parseJSON
3615+ parseAsString = withText "ReplicaCount" (fmap ReplicaCount . parseReadText)
3616+
3617+instance FromJSON ShardCount where
3618+ parseJSON v = parseAsInt v
3619+ <|> parseAsString v
3620+ where parseAsInt = fmap ShardCount . parseJSON
3621+ parseAsString = withText "ShardCount" (fmap ShardCount . parseReadText)
3622+
3623+instance Bounded DocVersion where
3624+ minBound = DocVersion 1
3625+ maxBound = DocVersion 9200000000000000000 -- 9.2e+18
3626+
3627+instance Enum DocVersion where
3628+ succ x
3629+ | x /= maxBound = DocVersion (succ $ docVersionNumber x)
3630+ | otherwise = succError "DocVersion"
3631+ pred x
3632+ | x /= minBound = DocVersion (pred $ docVersionNumber x)
3633+ | otherwise = predError "DocVersion"
3634+ toEnum i =
3635+ fromMaybe (error $ show i ++ " out of DocVersion range") $ mkDocVersion i
3636+ fromEnum = docVersionNumber
3637+ enumFrom = boundedEnumFrom
3638+ enumFromThen = boundedEnumFromThen
3639+
3640+-- | Username type used for HTTP Basic authentication. See 'basicAuthHook'.
3641+newtype EsUsername = EsUsername { esUsername :: Text } deriving (Read, Show, Eq)
3642+
3643+-- | Password type used for HTTP Basic authentication. See 'basicAuthHook'.
3644+newtype EsPassword = EsPassword { esPassword :: Text } deriving (Read, Show, Eq)
3645+
3646+
3647+data SnapshotRepoSelection = SnapshotRepoList (NonEmpty SnapshotRepoPattern)
3648+ | AllSnapshotRepos deriving (Eq, Generic, Show, Typeable)
3649+
3650+
3651+-- | Either specifies an exact repo name or one with globs in it,
3652+-- e.g. @RepoPattern "foo*"@ __NOTE__: Patterns are not supported on ES < 1.7
3653+data SnapshotRepoPattern = ExactRepo SnapshotRepoName
3654+ | RepoPattern Text
3655+ deriving (Eq, Generic, Show, Typeable)
3656+
3657+-- | The unique name of a snapshot repository.
3658+newtype SnapshotRepoName = SnapshotRepoName { snapshotRepoName :: Text }
3659+ deriving (Eq, Ord, Generic, Show, Typeable, ToJSON, FromJSON)
3660+
3661+
3662+-- | A generic representation of a snapshot repo. This is what gets
3663+-- sent to and parsed from the server. For repo types enabled by
3664+-- plugins that aren't exported by this library, consider making a
3665+-- custom type which implements 'SnapshotRepo'. If it is a common repo
3666+-- type, consider submitting a pull request to have it included in the
3667+-- library proper
3668+data GenericSnapshotRepo = GenericSnapshotRepo {
3669+ gSnapshotRepoName :: SnapshotRepoName
3670+ , gSnapshotRepoType :: SnapshotRepoType
3671+ , gSnapshotRepoSettings :: GenericSnapshotRepoSettings
3672+ } deriving (Eq, Generic, Show, Typeable)
3673+
3674+
3675+instance SnapshotRepo GenericSnapshotRepo where
3676+ toGSnapshotRepo = id
3677+ fromGSnapshotRepo = Right
3678+
3679+
3680+newtype SnapshotRepoType = SnapshotRepoType { snapshotRepoType :: Text }
3681+ deriving (Eq, Ord, Generic, Show, Typeable, ToJSON, FromJSON)
3682+
3683+
3684+-- | Opaque representation of snapshot repo settings. Instances of
3685+-- 'SnapshotRepo' will produce this.
3686+newtype GenericSnapshotRepoSettings = GenericSnapshotRepoSettings { gSnapshotRepoSettingsObject :: Object }
3687+ deriving (Eq, Generic, Show, Typeable, ToJSON)
3688+
3689+
3690+ -- Regardless of whether you send strongly typed json, my version of
3691+ -- ES sends back stringly typed json in the settings, e.g. booleans
3692+ -- as strings, so we'll try to convert them.
3693+instance FromJSON GenericSnapshotRepoSettings where
3694+ parseJSON = fmap (GenericSnapshotRepoSettings . fmap unStringlyTypeJSON). parseJSON
3695+
3696+-- | The result of running 'verifySnapshotRepo'.
3697+newtype SnapshotVerification = SnapshotVerification { snapshotNodeVerifications :: [SnapshotNodeVerification] }
3698+ deriving (Eq, Generic, Show, Typeable)
3699+
3700+
3701+instance FromJSON SnapshotVerification where
3702+ parseJSON = withObject "SnapshotVerification" parse
3703+ where
3704+ parse o = do
3705+ o2 <- o .: "nodes"
3706+ SnapshotVerification <$> mapM (uncurry parse') (HM.toList o2)
3707+ parse' rawFullId = withObject "SnapshotNodeVerification" $ \o ->
3708+ SnapshotNodeVerification (FullNodeId rawFullId) <$> o .: "name"
3709+
3710+
3711+-- | A node that has verified a snapshot
3712+data SnapshotNodeVerification = SnapshotNodeVerification {
3713+ snvFullId :: FullNodeId
3714+ , snvNodeName :: NodeName
3715+ } deriving (Eq, Generic, Show, Typeable)
3716+
3717+
3718+-- | Unique, automatically-generated name assigned to nodes that are
3719+-- usually returned in node-oriented APIs.
3720+newtype FullNodeId = FullNodeId { fullNodeId :: Text }
3721+ deriving (Eq, Ord, Generic, Show, Typeable, FromJSON)
3722+
3723+
3724+-- | A human-readable node name that is supplied by the user in the
3725+-- node config or automatically generated by ElasticSearch.
3726+newtype NodeName = NodeName { nodeName :: Text }
3727+ deriving (Eq, Ord, Generic, Show, Typeable, FromJSON)
3728+
3729+newtype ClusterName = ClusterName { clusterName :: Text }
3730+ deriving (Eq, Ord, Generic, Show, Typeable, FromJSON)
3731+
3732+data NodesInfo = NodesInfo {
3733+ nodesInfo :: [NodeInfo]
3734+ , nodesClusterName :: ClusterName
3735+ } deriving (Eq, Show, Generic, Typeable)
3736+
3737+data NodesStats = NodesStats {
3738+ nodesStats :: [NodeStats]
3739+ , nodesStatsClusterName :: ClusterName
3740+ } deriving (Eq, Show, Generic, Typeable)
3741+
3742+data NodeStats = NodeStats {
3743+ nodeStatsName :: NodeName
3744+ , nodeStatsFullId :: FullNodeId
3745+ , nodeStatsBreakersStats :: Maybe NodeBreakersStats
3746+ , nodeStatsHTTP :: NodeHTTPStats
3747+ , nodeStatsTransport :: NodeTransportStats
3748+ , nodeStatsFS :: NodeFSStats
3749+ , nodeStatsNetwork :: Maybe NodeNetworkStats
3750+ , nodeStatsThreadPool :: NodeThreadPoolsStats
3751+ , nodeStatsJVM :: NodeJVMStats
3752+ , nodeStatsProcess :: NodeProcessStats
3753+ , nodeStatsOS :: NodeOSStats
3754+ , nodeStatsIndices :: NodeIndicesStats
3755+ } deriving (Eq, Show, Generic, Typeable)
3756+
3757+data NodeBreakersStats = NodeBreakersStats {
3758+ nodeStatsParentBreaker :: NodeBreakerStats
3759+ , nodeStatsRequestBreaker :: NodeBreakerStats
3760+ , nodeStatsFieldDataBreaker :: NodeBreakerStats
3761+ } deriving (Eq, Show, Generic, Typeable)
3762+
3763+data NodeBreakerStats = NodeBreakerStats {
3764+ nodeBreakersTripped :: Int
3765+ , nodeBreakersOverhead :: Double
3766+ , nodeBreakersEstSize :: Bytes
3767+ , nodeBreakersLimitSize :: Bytes
3768+ } deriving (Eq, Show, Generic, Typeable)
3769+
3770+data NodeHTTPStats = NodeHTTPStats {
3771+ nodeHTTPTotalOpened :: Int
3772+ , nodeHTTPCurrentOpen :: Int
3773+ } deriving (Eq, Show, Generic, Typeable)
3774+
3775+data NodeTransportStats = NodeTransportStats {
3776+ nodeTransportTXSize :: Bytes
3777+ , nodeTransportCount :: Int
3778+ , nodeTransportRXSize :: Bytes
3779+ , nodeTransportRXCount :: Int
3780+ , nodeTransportServerOpen :: Int
3781+ } deriving (Eq, Show, Generic, Typeable)
3782+
3783+data NodeFSStats = NodeFSStats {
3784+ nodeFSDataPaths :: [NodeDataPathStats]
3785+ , nodeFSTotal :: NodeFSTotalStats
3786+ , nodeFSTimestamp :: UTCTime
3787+ } deriving (Eq, Show, Generic, Typeable)
3788+
3789+data NodeDataPathStats = NodeDataPathStats {
3790+ nodeDataPathDiskServiceTime :: Maybe Double
3791+ , nodeDataPathDiskQueue :: Maybe Double
3792+ , nodeDataPathIOSize :: Maybe Bytes
3793+ , nodeDataPathWriteSize :: Maybe Bytes
3794+ , nodeDataPathReadSize :: Maybe Bytes
3795+ , nodeDataPathIOOps :: Maybe Int
3796+ , nodeDataPathWrites :: Maybe Int
3797+ , nodeDataPathReads :: Maybe Int
3798+ , nodeDataPathAvailable :: Bytes
3799+ , nodeDataPathFree :: Bytes
3800+ , nodeDataPathTotal :: Bytes
3801+ , nodeDataPathType :: Maybe Text
3802+ , nodeDataPathDevice :: Maybe Text
3803+ , nodeDataPathMount :: Text
3804+ , nodeDataPathPath :: Text
3805+ } deriving (Eq, Show, Generic, Typeable)
3806+
3807+data NodeFSTotalStats = NodeFSTotalStats {
3808+ nodeFSTotalDiskServiceTime :: Maybe Double
3809+ , nodeFSTotalDiskQueue :: Maybe Double
3810+ , nodeFSTotalIOSize :: Maybe Bytes
3811+ , nodeFSTotalWriteSize :: Maybe Bytes
3812+ , nodeFSTotalReadSize :: Maybe Bytes
3813+ , nodeFSTotalIOOps :: Maybe Int
3814+ , nodeFSTotalWrites :: Maybe Int
3815+ , nodeFSTotalReads :: Maybe Int
3816+ , nodeFSTotalAvailable :: Bytes
3817+ , nodeFSTotalFree :: Bytes
3818+ , nodeFSTotalTotal :: Bytes
3819+ } deriving (Eq, Show, Generic, Typeable)
3820+
3821+data NodeNetworkStats = NodeNetworkStats {
3822+ nodeNetTCPOutRSTs :: Int
3823+ , nodeNetTCPInErrs :: Int
3824+ , nodeNetTCPAttemptFails :: Int
3825+ , nodeNetTCPEstabResets :: Int
3826+ , nodeNetTCPRetransSegs :: Int
3827+ , nodeNetTCPOutSegs :: Int
3828+ , nodeNetTCPInSegs :: Int
3829+ , nodeNetTCPCurrEstab :: Int
3830+ , nodeNetTCPPassiveOpens :: Int
3831+ , nodeNetTCPActiveOpens :: Int
3832+ } deriving (Eq, Show, Generic, Typeable)
3833+
3834+data NodeThreadPoolsStats = NodeThreadPoolsStats {
3835+ nodeThreadPoolsStatsSnapshot :: NodeThreadPoolStats
3836+ , nodeThreadPoolsStatsBulk :: NodeThreadPoolStats
3837+ , nodeThreadPoolsStatsMerge :: NodeThreadPoolStats
3838+ , nodeThreadPoolsStatsGet :: NodeThreadPoolStats
3839+ , nodeThreadPoolsStatsManagement :: NodeThreadPoolStats
3840+ , nodeThreadPoolsStatsFetchShardStore :: Maybe NodeThreadPoolStats
3841+ , nodeThreadPoolsStatsOptimize :: Maybe NodeThreadPoolStats
3842+ , nodeThreadPoolsStatsFlush :: NodeThreadPoolStats
3843+ , nodeThreadPoolsStatsSearch :: NodeThreadPoolStats
3844+ , nodeThreadPoolsStatsWarmer :: NodeThreadPoolStats
3845+ , nodeThreadPoolsStatsGeneric :: NodeThreadPoolStats
3846+ , nodeThreadPoolsStatsSuggest :: Maybe NodeThreadPoolStats
3847+ , nodeThreadPoolsStatsRefresh :: NodeThreadPoolStats
3848+ , nodeThreadPoolsStatsIndex :: NodeThreadPoolStats
3849+ , nodeThreadPoolsStatsListener :: Maybe NodeThreadPoolStats
3850+ , nodeThreadPoolsStatsFetchShardStarted :: Maybe NodeThreadPoolStats
3851+ , nodeThreadPoolsStatsPercolate :: Maybe NodeThreadPoolStats
3852+ } deriving (Eq, Show, Generic, Typeable)
3853+
3854+data NodeThreadPoolStats = NodeThreadPoolStats {
3855+ nodeThreadPoolCompleted :: Int
3856+ , nodeThreadPoolLargest :: Int
3857+ , nodeThreadPoolRejected :: Int
3858+ , nodeThreadPoolActive :: Int
3859+ , nodeThreadPoolQueue :: Int
3860+ , nodeThreadPoolThreads :: Int
3861+ } deriving (Eq, Show, Generic, Typeable)
3862+
3863+data NodeJVMStats = NodeJVMStats {
3864+ nodeJVMStatsMappedBufferPool :: JVMBufferPoolStats
3865+ , nodeJVMStatsDirectBufferPool :: JVMBufferPoolStats
3866+ , nodeJVMStatsGCOldCollector :: JVMGCStats
3867+ , nodeJVMStatsGCYoungCollector :: JVMGCStats
3868+ , nodeJVMStatsPeakThreadsCount :: Int
3869+ , nodeJVMStatsThreadsCount :: Int
3870+ , nodeJVMStatsOldPool :: JVMPoolStats
3871+ , nodeJVMStatsSurvivorPool :: JVMPoolStats
3872+ , nodeJVMStatsYoungPool :: JVMPoolStats
3873+ , nodeJVMStatsNonHeapCommitted :: Bytes
3874+ , nodeJVMStatsNonHeapUsed :: Bytes
3875+ , nodeJVMStatsHeapMax :: Bytes
3876+ , nodeJVMStatsHeapCommitted :: Bytes
3877+ , nodeJVMStatsHeapUsedPercent :: Int
3878+ , nodeJVMStatsHeapUsed :: Bytes
3879+ , nodeJVMStatsUptime :: NominalDiffTime
3880+ , nodeJVMStatsTimestamp :: UTCTime
3881+ } deriving (Eq, Show, Generic, Typeable)
3882+
3883+data JVMBufferPoolStats = JVMBufferPoolStats {
3884+ jvmBufferPoolStatsTotalCapacity :: Bytes
3885+ , jvmBufferPoolStatsUsed :: Bytes
3886+ , jvmBufferPoolStatsCount :: Int
3887+ } deriving (Eq, Show, Generic, Typeable)
3888+
3889+data JVMGCStats = JVMGCStats {
3890+ jvmGCStatsCollectionTime :: NominalDiffTime
3891+ , jvmGCStatsCollectionCount :: Int
3892+ } deriving (Eq, Show, Generic, Typeable)
3893+
3894+data JVMPoolStats = JVMPoolStats {
3895+ jvmPoolStatsPeakMax :: Bytes
3896+ , jvmPoolStatsPeakUsed :: Bytes
3897+ , jvmPoolStatsMax :: Bytes
3898+ , jvmPoolStatsUsed :: Bytes
3899+ } deriving (Eq, Show, Generic, Typeable)
3900+
3901+data NodeProcessStats = NodeProcessStats {
3902+ nodeProcessTimestamp :: UTCTime
3903+ , nodeProcessOpenFDs :: Int
3904+ , nodeProcessMaxFDs :: Int
3905+ , nodeProcessCPUPercent :: Int
3906+ , nodeProcessCPUTotal :: NominalDiffTime
3907+ , nodeProcessMemTotalVirtual :: Bytes
3908+ } deriving (Eq, Show, Generic, Typeable)
3909+
3910+data NodeOSStats = NodeOSStats {
3911+ nodeOSTimestamp :: UTCTime
3912+ , nodeOSCPUPercent :: Int
3913+ , nodeOSLoad :: Maybe LoadAvgs
3914+ , nodeOSMemTotal :: Bytes
3915+ , nodeOSMemFree :: Bytes
3916+ , nodeOSMemFreePercent :: Int
3917+ , nodeOSMemUsed :: Bytes
3918+ , nodeOSMemUsedPercent :: Int
3919+ , nodeOSSwapTotal :: Bytes
3920+ , nodeOSSwapFree :: Bytes
3921+ , nodeOSSwapUsed :: Bytes
3922+ } deriving (Eq, Show, Generic, Typeable)
3923+
3924+data LoadAvgs = LoadAvgs {
3925+ loadAvg1Min :: Double
3926+ , loadAvg5Min :: Double
3927+ , loadAvg15Min :: Double
3928+ } deriving (Eq, Show, Generic, Typeable)
3929+
3930+data NodeIndicesStats = NodeIndicesStats {
3931+ nodeIndicesStatsRecoveryThrottleTime :: Maybe NominalDiffTime
3932+ , nodeIndicesStatsRecoveryCurrentAsTarget :: Maybe Int
3933+ , nodeIndicesStatsRecoveryCurrentAsSource :: Maybe Int
3934+ , nodeIndicesStatsQueryCacheMisses :: Maybe Int
3935+ , nodeIndicesStatsQueryCacheHits :: Maybe Int
3936+ , nodeIndicesStatsQueryCacheEvictions :: Maybe Int
3937+ , nodeIndicesStatsQueryCacheSize :: Maybe Bytes
3938+ , nodeIndicesStatsSuggestCurrent :: Maybe Int
3939+ , nodeIndicesStatsSuggestTime :: Maybe NominalDiffTime
3940+ , nodeIndicesStatsSuggestTotal :: Maybe Int
3941+ , nodeIndicesStatsTranslogSize :: Bytes
3942+ , nodeIndicesStatsTranslogOps :: Int
3943+ , nodeIndicesStatsSegFixedBitSetMemory :: Maybe Bytes
3944+ , nodeIndicesStatsSegVersionMapMemory :: Bytes
3945+ , nodeIndicesStatsSegIndexWriterMaxMemory :: Maybe Bytes
3946+ , nodeIndicesStatsSegIndexWriterMemory :: Bytes
3947+ , nodeIndicesStatsSegMemory :: Bytes
3948+ , nodeIndicesStatsSegCount :: Int
3949+ , nodeIndicesStatsCompletionSize :: Bytes
3950+ , nodeIndicesStatsPercolateQueries :: Maybe Int
3951+ , nodeIndicesStatsPercolateMemory :: Maybe Bytes
3952+ , nodeIndicesStatsPercolateCurrent :: Maybe Int
3953+ , nodeIndicesStatsPercolateTime :: Maybe NominalDiffTime
3954+ , nodeIndicesStatsPercolateTotal :: Maybe Int
3955+ , nodeIndicesStatsFieldDataEvictions :: Int
3956+ , nodeIndicesStatsFieldDataMemory :: Bytes
3957+ , nodeIndicesStatsWarmerTotalTime :: NominalDiffTime
3958+ , nodeIndicesStatsWarmerTotal :: Int
3959+ , nodeIndicesStatsWarmerCurrent :: Int
3960+ , nodeIndicesStatsFlushTotalTime :: NominalDiffTime
3961+ , nodeIndicesStatsFlushTotal :: Int
3962+ , nodeIndicesStatsRefreshTotalTime :: NominalDiffTime
3963+ , nodeIndicesStatsRefreshTotal :: Int
3964+ , nodeIndicesStatsMergesTotalSize :: Bytes
3965+ , nodeIndicesStatsMergesTotalDocs :: Int
3966+ , nodeIndicesStatsMergesTotalTime :: NominalDiffTime
3967+ , nodeIndicesStatsMergesTotal :: Int
3968+ , nodeIndicesStatsMergesCurrentSize :: Bytes
3969+ , nodeIndicesStatsMergesCurrentDocs :: Int
3970+ , nodeIndicesStatsMergesCurrent :: Int
3971+ , nodeIndicesStatsSearchFetchCurrent :: Int
3972+ , nodeIndicesStatsSearchFetchTime :: NominalDiffTime
3973+ , nodeIndicesStatsSearchFetchTotal :: Int
3974+ , nodeIndicesStatsSearchQueryCurrent :: Int
3975+ , nodeIndicesStatsSearchQueryTime :: NominalDiffTime
3976+ , nodeIndicesStatsSearchQueryTotal :: Int
3977+ , nodeIndicesStatsSearchOpenContexts :: Int
3978+ , nodeIndicesStatsGetCurrent :: Int
3979+ , nodeIndicesStatsGetMissingTime :: NominalDiffTime
3980+ , nodeIndicesStatsGetMissingTotal :: Int
3981+ , nodeIndicesStatsGetExistsTime :: NominalDiffTime
3982+ , nodeIndicesStatsGetExistsTotal :: Int
3983+ , nodeIndicesStatsGetTime :: NominalDiffTime
3984+ , nodeIndicesStatsGetTotal :: Int
3985+ , nodeIndicesStatsIndexingThrottleTime :: Maybe NominalDiffTime
3986+ , nodeIndicesStatsIndexingIsThrottled :: Maybe Bool
3987+ , nodeIndicesStatsIndexingNoopUpdateTotal :: Maybe Int
3988+ , nodeIndicesStatsIndexingDeleteCurrent :: Int
3989+ , nodeIndicesStatsIndexingDeleteTime :: NominalDiffTime
3990+ , nodeIndicesStatsIndexingDeleteTotal :: Int
3991+ , nodeIndicesStatsIndexingIndexCurrent :: Int
3992+ , nodeIndicesStatsIndexingIndexTime :: NominalDiffTime
3993+ , nodeIndicesStatsIndexingTotal :: Int
3994+ , nodeIndicesStatsStoreThrottleTime :: NominalDiffTime
3995+ , nodeIndicesStatsStoreSize :: Bytes
3996+ , nodeIndicesStatsDocsDeleted :: Int
3997+ , nodeIndicesStatsDocsCount :: Int
3998+ } deriving (Eq, Show, Generic, Typeable)
3999+
4000+-- | A quirky address format used throughout ElasticSearch. An example
4001+-- would be inet[/1.1.1.1:9200]. inet may be a placeholder for a
4002+-- <https://en.wikipedia.org/wiki/Fully_qualified_domain_name FQDN>.
4003+newtype EsAddress = EsAddress { esAddress :: Text }
4004+ deriving (Eq, Ord, Generic, Show, Typeable, FromJSON)
4005+
4006+-- | Typically a 7 character hex string.
4007+newtype BuildHash = BuildHash { buildHash :: Text }
4008+ deriving (Eq, Ord, Generic, Read, Show, Typeable, FromJSON, ToJSON)
4009+
4010+newtype PluginName = PluginName { pluginName :: Text }
4011+ deriving (Eq, Ord, Generic, Show, Typeable, FromJSON)
4012+
4013+data NodeInfo = NodeInfo {
4014+ nodeInfoHTTPAddress :: Maybe EsAddress
4015+ , nodeInfoBuild :: BuildHash
4016+ , nodeInfoESVersion :: VersionNumber
4017+ , nodeInfoIP :: Server
4018+ , nodeInfoHost :: Server
4019+ , nodeInfoTransportAddress :: EsAddress
4020+ , nodeInfoName :: NodeName
4021+ , nodeInfoFullId :: FullNodeId
4022+ , nodeInfoPlugins :: [NodePluginInfo]
4023+ , nodeInfoHTTP :: NodeHTTPInfo
4024+ , nodeInfoTransport :: NodeTransportInfo
4025+ , nodeInfoNetwork :: Maybe NodeNetworkInfo
4026+ , nodeInfoThreadPool :: NodeThreadPoolsInfo
4027+ , nodeInfoJVM :: NodeJVMInfo
4028+ , nodeInfoProcess :: NodeProcessInfo
4029+ , nodeInfoOS :: NodeOSInfo
4030+ , nodeInfoSettings :: Object
4031+ -- ^ The members of the settings objects are not consistent,
4032+ -- dependent on plugins, etc.
4033+ } deriving (Eq, Show, Generic, Typeable)
4034+
4035+data NodePluginInfo = NodePluginInfo {
4036+ nodePluginSite :: Maybe Bool
4037+ -- ^ Is this a site plugin?
4038+ , nodePluginJVM :: Maybe Bool
4039+ -- ^ Is this plugin running on the JVM
4040+ , nodePluginDescription :: Text
4041+ , nodePluginVersion :: MaybeNA VersionNumber
4042+ , nodePluginName :: PluginName
4043+ } deriving (Eq, Show, Generic, Typeable)
4044+
4045+data NodeHTTPInfo = NodeHTTPInfo {
4046+ nodeHTTPMaxContentLength :: Bytes
4047+ , nodeHTTPTransportAddress :: BoundTransportAddress
4048+ } deriving (Eq, Show, Generic, Typeable)
4049+
4050+data NodeTransportInfo = NodeTransportInfo {
4051+ nodeTransportProfiles :: [BoundTransportAddress]
4052+ , nodeTransportAddress :: BoundTransportAddress
4053+ } deriving (Eq, Show, Generic, Typeable)
4054+
4055+data BoundTransportAddress = BoundTransportAddress {
4056+ publishAddress :: EsAddress
4057+ , boundAddress :: [EsAddress]
4058+ } deriving (Eq, Show, Generic, Typeable)
4059+
4060+data NodeNetworkInfo = NodeNetworkInfo {
4061+ nodeNetworkPrimaryInterface :: NodeNetworkInterface
4062+ , nodeNetworkRefreshInterval :: NominalDiffTime
4063+ } deriving (Eq, Show, Generic, Typeable)
4064+
4065+newtype MacAddress = MacAddress { macAddress :: Text }
4066+ deriving (Eq, Ord, Generic, Show, Typeable, FromJSON)
4067+
4068+newtype NetworkInterfaceName = NetworkInterfaceName { networkInterfaceName :: Text }
4069+ deriving (Eq, Ord, Generic, Show, Typeable, FromJSON)
4070+
4071+data NodeNetworkInterface = NodeNetworkInterface {
4072+ nodeNetIfaceMacAddress :: MacAddress
4073+ , nodeNetIfaceName :: NetworkInterfaceName
4074+ , nodeNetIfaceAddress :: Server
4075+ } deriving (Eq, Show, Generic, Typeable)
4076+
4077+data NodeThreadPoolsInfo = NodeThreadPoolsInfo {
4078+ nodeThreadPoolsRefresh :: NodeThreadPoolInfo
4079+ , nodeThreadPoolsManagement :: NodeThreadPoolInfo
4080+ , nodeThreadPoolsPercolate :: Maybe NodeThreadPoolInfo
4081+ , nodeThreadPoolsListener :: Maybe NodeThreadPoolInfo
4082+ , nodeThreadPoolsFetchShardStarted :: Maybe NodeThreadPoolInfo
4083+ , nodeThreadPoolsSearch :: NodeThreadPoolInfo
4084+ , nodeThreadPoolsFlush :: NodeThreadPoolInfo
4085+ , nodeThreadPoolsWarmer :: NodeThreadPoolInfo
4086+ , nodeThreadPoolsOptimize :: Maybe NodeThreadPoolInfo
4087+ , nodeThreadPoolsBulk :: NodeThreadPoolInfo
4088+ , nodeThreadPoolsSuggest :: Maybe NodeThreadPoolInfo
4089+ , nodeThreadPoolsMerge :: NodeThreadPoolInfo
4090+ , nodeThreadPoolsSnapshot :: NodeThreadPoolInfo
4091+ , nodeThreadPoolsGet :: NodeThreadPoolInfo
4092+ , nodeThreadPoolsFetchShardStore :: Maybe NodeThreadPoolInfo
4093+ , nodeThreadPoolsIndex :: NodeThreadPoolInfo
4094+ , nodeThreadPoolsGeneric :: NodeThreadPoolInfo
4095+ } deriving (Eq, Show, Generic, Typeable)
4096+
4097+data NodeThreadPoolInfo = NodeThreadPoolInfo {
4098+ nodeThreadPoolQueueSize :: ThreadPoolSize
4099+ , nodeThreadPoolKeepalive :: Maybe NominalDiffTime
4100+ , nodeThreadPoolMin :: Maybe Int
4101+ , nodeThreadPoolMax :: Maybe Int
4102+ , nodeThreadPoolType :: ThreadPoolType
4103+ } deriving (Eq, Show, Generic, Typeable)
4104+
4105+data ThreadPoolSize = ThreadPoolBounded Int
4106+ | ThreadPoolUnbounded
4107+ deriving (Eq, Show, Generic, Typeable)
4108+
4109+data ThreadPoolType = ThreadPoolScaling
4110+ | ThreadPoolFixed
4111+ | ThreadPoolCached
4112+ deriving (Eq, Show, Generic, Typeable)
4113+
4114+data NodeJVMInfo = NodeJVMInfo {
4115+ nodeJVMInfoMemoryPools :: [JVMMemoryPool]
4116+ , nodeJVMInfoMemoryPoolsGCCollectors :: [JVMGCCollector]
4117+ , nodeJVMInfoMemoryInfo :: JVMMemoryInfo
4118+ , nodeJVMInfoStartTime :: UTCTime
4119+ , nodeJVMInfoVMVendor :: Text
4120+ , nodeJVMVMVersion :: VersionNumber
4121+ -- ^ JVM doesn't seme to follow normal version conventions
4122+ , nodeJVMVMName :: Text
4123+ , nodeJVMVersion :: VersionNumber
4124+ , nodeJVMPID :: PID
4125+ } deriving (Eq, Show, Generic, Typeable)
4126+
4127+-- | Handles quirks in the way JVM versions are rendered (1.7.0_101 -> 1.7.0.101)
4128+newtype JVMVersion = JVMVersion { unJVMVersion :: VersionNumber }
4129+
4130+data JVMMemoryInfo = JVMMemoryInfo {
4131+ jvmMemoryInfoDirectMax :: Bytes
4132+ , jvmMemoryInfoNonHeapMax :: Bytes
4133+ , jvmMemoryInfoNonHeapInit :: Bytes
4134+ , jvmMemoryInfoHeapMax :: Bytes
4135+ , jvmMemoryInfoHeapInit :: Bytes
4136+ } deriving (Eq, Show, Generic, Typeable)
4137+
4138+newtype JVMMemoryPool = JVMMemoryPool {
4139+ jvmMemoryPool :: Text
4140+ } deriving (Eq, Show, Generic, Typeable, FromJSON)
4141+
4142+newtype JVMGCCollector = JVMGCCollector {
4143+ jvmGCCollector :: Text
4144+ } deriving (Eq, Show, Generic, Typeable, FromJSON)
4145+
4146+newtype PID = PID {
4147+ pid :: Int
4148+ } deriving (Eq, Show, Generic, Typeable, FromJSON)
4149+
4150+data NodeOSInfo = NodeOSInfo {
4151+ nodeOSRefreshInterval :: NominalDiffTime
4152+ , nodeOSName :: Text
4153+ , nodeOSArch :: Text
4154+ , nodeOSVersion :: VersionNumber
4155+ , nodeOSAvailableProcessors :: Int
4156+ , nodeOSAllocatedProcessors :: Int
4157+ } deriving (Eq, Show, Generic, Typeable)
4158+
4159+data CPUInfo = CPUInfo {
4160+ cpuCacheSize :: Bytes
4161+ , cpuCoresPerSocket :: Int
4162+ , cpuTotalSockets :: Int
4163+ , cpuTotalCores :: Int
4164+ , cpuMHZ :: Int
4165+ , cpuModel :: Text
4166+ , cpuVendor :: Text
4167+ } deriving (Eq, Show, Generic, Typeable)
4168+
4169+data NodeProcessInfo = NodeProcessInfo {
4170+ nodeProcessMLockAll :: Bool
4171+ -- ^ See <https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-configuration.html>
4172+ , nodeProcessMaxFileDescriptors :: Maybe Int
4173+ , nodeProcessId :: PID
4174+ , nodeProcessRefreshInterval :: NominalDiffTime
4175+ } deriving (Eq, Show, Generic, Typeable)
4176+
4177+data SnapshotRepoUpdateSettings = SnapshotRepoUpdateSettings {
4178+ repoUpdateVerify :: Bool
4179+ -- ^ After creation/update, synchronously check that nodes can
4180+ -- write to this repo. Defaults to True. You may use False if you
4181+ -- need a faster response and plan on verifying manually later
4182+ -- with 'verifySnapshotRepo'.
4183+ } deriving (Eq, Show, Generic, Typeable)
4184+
4185+
4186+-- | Reasonable defaults for repo creation/update
4187+--
4188+-- * repoUpdateVerify True
4189+defaultSnapshotRepoUpdateSettings :: SnapshotRepoUpdateSettings
4190+defaultSnapshotRepoUpdateSettings = SnapshotRepoUpdateSettings True
4191+
4192+
4193+-- | A filesystem-based snapshot repo that ships with
4194+-- ElasticSearch. This is an instance of 'SnapshotRepo' so it can be
4195+-- used with 'updateSnapshotRepo'
4196+data FsSnapshotRepo = FsSnapshotRepo {
4197+ fsrName :: SnapshotRepoName
4198+ , fsrLocation :: FilePath
4199+ , fsrCompressMetadata :: Bool
4200+ , fsrChunkSize :: Maybe Bytes
4201+ -- ^ Size by which to split large files during snapshotting.
4202+ , fsrMaxRestoreBytesPerSec :: Maybe Bytes
4203+ -- ^ Throttle node restore rate. If not supplied, defaults to 40mb/sec
4204+ , fsrMaxSnapshotBytesPerSec :: Maybe Bytes
4205+ -- ^ Throttle node snapshot rate. If not supplied, defaults to 40mb/sec
4206+ } deriving (Eq, Generic, Show, Typeable)
4207+
4208+
4209+instance SnapshotRepo FsSnapshotRepo where
4210+ toGSnapshotRepo FsSnapshotRepo {..} =
4211+ GenericSnapshotRepo fsrName fsRepoType (GenericSnapshotRepoSettings settings)
4212+ where
4213+ Object settings = object $ [ "location" .= fsrLocation
4214+ , "compress" .= fsrCompressMetadata
4215+ ] ++ optionalPairs
4216+ optionalPairs = catMaybes [ ("chunk_size" .=) <$> fsrChunkSize
4217+ , ("max_restore_bytes_per_sec" .=) <$> fsrMaxRestoreBytesPerSec
4218+ , ("max_snapshot_bytes_per_sec" .=) <$> fsrMaxSnapshotBytesPerSec
4219+ ]
4220+ fromGSnapshotRepo GenericSnapshotRepo {..}
4221+ | gSnapshotRepoType == fsRepoType = do
4222+ let o = gSnapshotRepoSettingsObject gSnapshotRepoSettings
4223+ parseRepo $ do
4224+ FsSnapshotRepo gSnapshotRepoName <$> o .: "location"
4225+ <*> o .:? "compress" .!= False
4226+ <*> o .:? "chunk_size"
4227+ <*> o .:? "max_restore_bytes_per_sec"
4228+ <*> o .:? "max_snapshot_bytes_per_sec"
4229+ | otherwise = Left (RepoTypeMismatch fsRepoType gSnapshotRepoType)
4230+
4231+
4232+parseRepo :: Parser a -> Either SnapshotRepoConversionError a
4233+parseRepo parser = case parseEither (const parser) () of
4234+ Left e -> Left (OtherRepoConversionError (T.pack e))
4235+ Right a -> Right a
4236+
4237+
4238+fsRepoType :: SnapshotRepoType
4239+fsRepoType = SnapshotRepoType "fs"
4240+
4241+-- | Law: fromGSnapshotRepo (toGSnapshotRepo r) == Right r
4242+class SnapshotRepo r where
4243+ toGSnapshotRepo :: r -> GenericSnapshotRepo
4244+ fromGSnapshotRepo :: GenericSnapshotRepo -> Either SnapshotRepoConversionError r
4245+
4246+
4247+data SnapshotRepoConversionError = RepoTypeMismatch SnapshotRepoType SnapshotRepoType
4248+ -- ^ Expected type and actual type
4249+ | OtherRepoConversionError Text
4250+ deriving (Show, Eq, Generic, Typeable)
4251+
4252+
4253+instance Exception SnapshotRepoConversionError
4254+
4255+
4256+data SnapshotCreateSettings = SnapshotCreateSettings {
4257+ snapWaitForCompletion :: Bool
4258+ -- ^ Should the API call return immediately after initializing
4259+ -- the snapshot or wait until completed? Note that if this is
4260+ -- enabled it could wait a long time, so you should adjust your
4261+ -- 'ManagerSettings' accordingly to set long timeouts or
4262+ -- explicitly handle timeouts.
4263+ , snapIndices :: Maybe IndexSelection
4264+ -- ^ Nothing will snapshot all indices. Just [] is permissable and
4265+ -- will essentially be a no-op snapshot.
4266+ , snapIgnoreUnavailable :: Bool
4267+ -- ^ If set to True, any matched indices that don't exist will be
4268+ -- ignored. Otherwise it will be an error and fail.
4269+ , snapIncludeGlobalState :: Bool
4270+ , snapPartial :: Bool
4271+ -- ^ If some indices failed to snapshot (e.g. if not all primary
4272+ -- shards are available), should the process proceed?
4273+ } deriving (Eq, Generic, Show, Typeable)
4274+
4275+
4276+-- | Reasonable defaults for snapshot creation
4277+--
4278+-- * snapWaitForCompletion False
4279+-- * snapIndices Nothing
4280+-- * snapIgnoreUnavailable False
4281+-- * snapIncludeGlobalState True
4282+-- * snapPartial False
4283+defaultSnapshotCreateSettings :: SnapshotCreateSettings
4284+defaultSnapshotCreateSettings = SnapshotCreateSettings {
4285+ snapWaitForCompletion = False
4286+ , snapIndices = Nothing
4287+ , snapIgnoreUnavailable = False
4288+ , snapIncludeGlobalState = True
4289+ , snapPartial = False
4290+ }
4291+
4292+
4293+data SnapshotSelection = SnapshotList (NonEmpty SnapshotPattern)
4294+ | AllSnapshots deriving (Eq, Generic, Show, Typeable)
4295+
4296+
4297+-- | Either specifies an exact snapshot name or one with globs in it,
4298+-- e.g. @SnapPattern "foo*"@ __NOTE__: Patterns are not supported on
4299+-- ES < 1.7
4300+data SnapshotPattern = ExactSnap SnapshotName
4301+ | SnapPattern Text
4302+ deriving (Eq, Generic, Show, Typeable)
4303+
4304+
4305+-- | General information about the state of a snapshot. Has some
4306+-- redundancies with 'SnapshotStatus'
4307+data SnapshotInfo = SnapshotInfo {
4308+ snapInfoShards :: ShardResult
4309+ , snapInfoFailures :: [SnapshotShardFailure]
4310+ , snapInfoDuration :: NominalDiffTime
4311+ , snapInfoEndTime :: UTCTime
4312+ , snapInfoStartTime :: UTCTime
4313+ , snapInfoState :: SnapshotState
4314+ , snapInfoIndices :: [IndexName]
4315+ , snapInfoName :: SnapshotName
4316+ } deriving (Eq, Generic, Show, Typeable)
4317+
4318+
4319+instance FromJSON SnapshotInfo where
4320+ parseJSON = withObject "SnapshotInfo" parse
4321+ where
4322+ parse o = SnapshotInfo <$> o .: "shards"
4323+ <*> o .: "failures"
4324+ <*> (unMS <$> o .: "duration_in_millis")
4325+ <*> (posixMS <$> o .: "end_time_in_millis")
4326+ <*> (posixMS <$> o .: "start_time_in_millis")
4327+ <*> o .: "state"
4328+ <*> o .: "indices"
4329+ <*> o .: "snapshot"
4330+
4331+data SnapshotShardFailure = SnapshotShardFailure {
4332+ snapShardFailureIndex :: IndexName
4333+ , snapShardFailureNodeId :: Maybe NodeName -- I'm not 100% sure this isn't actually 'FullNodeId'
4334+ , snapShardFailureReason :: Text
4335+ , snapShardFailureShardId :: ShardId
4336+ } deriving (Eq, Show, Generic, Typeable)
4337+
4338+
4339+instance FromJSON SnapshotShardFailure where
4340+ parseJSON = withObject "SnapshotShardFailure" parse
4341+ where
4342+ parse o = SnapshotShardFailure <$> o .: "index"
4343+ <*> o .:? "node_id"
4344+ <*> o .: "reason"
4345+ <*> o .: "shard_id"
4346+
4347+
4348+newtype ShardId = ShardId { shardId :: Int }
4349+ deriving (Eq, Show, Generic, Typeable, FromJSON)
4350+
4351+-- | Milliseconds
4352+newtype MS = MS NominalDiffTime
4353+
4354+
4355+-- keeps the unexported constructor warnings at bay
4356+unMS :: MS -> NominalDiffTime
4357+unMS (MS t) = t
4358+
4359+
4360+instance FromJSON MS where
4361+ parseJSON = withScientific "MS" (return . MS . parse)
4362+ where
4363+ parse n = fromInteger ((truncate n) * 1000)
4364+
4365+
4366+data SnapshotState = SnapshotInit
4367+ | SnapshotStarted
4368+ | SnapshotSuccess
4369+ | SnapshotFailed
4370+ | SnapshotAborted
4371+ | SnapshotMissing
4372+ | SnapshotWaiting
4373+ deriving (Show, Eq, Generic, Typeable)
4374+
4375+instance FromJSON SnapshotState where
4376+ parseJSON = withText "SnapshotState" parse
4377+ where
4378+ parse "INIT" = return SnapshotInit
4379+ parse "STARTED" = return SnapshotStarted
4380+ parse "SUCCESS" = return SnapshotSuccess
4381+ parse "FAILED" = return SnapshotFailed
4382+ parse "ABORTED" = return SnapshotAborted
4383+ parse "MISSING" = return SnapshotMissing
4384+ parse "WAITING" = return SnapshotWaiting
4385+ parse t = fail ("Invalid snapshot state " <> T.unpack t)
4386+
4387+
4388+newtype SnapshotName = SnapshotName { snapshotName :: Text }
4389+ deriving (Show, Eq, Ord, Generic, Typeable, ToJSON, FromJSON)
4390+
4391+
4392+data SnapshotRestoreSettings = SnapshotRestoreSettings {
4393+ snapRestoreWaitForCompletion :: Bool
4394+ -- ^ Should the API call return immediately after initializing
4395+ -- the restore or wait until completed? Note that if this is
4396+ -- enabled, it could wait a long time, so you should adjust your
4397+ -- 'ManagerSettings' accordingly to set long timeouts or
4398+ -- explicitly handle timeouts.
4399+ , snapRestoreIndices :: Maybe IndexSelection
4400+ -- ^ Nothing will restore all indices in the snapshot. Just [] is
4401+ -- permissable and will essentially be a no-op restore.
4402+ , snapRestoreIgnoreUnavailable :: Bool
4403+ -- ^ If set to True, any indices that do not exist will be ignored
4404+ -- during snapshot rather than failing the restore.
4405+ , snapRestoreIncludeGlobalState :: Bool
4406+ -- ^ If set to false, will ignore any global state in the snapshot
4407+ -- and will not restore it.
4408+ , snapRestoreRenamePattern :: Maybe RestoreRenamePattern
4409+ -- ^ A regex pattern for matching indices. Used with
4410+ -- 'snapRestoreRenameReplacement', the restore can reference the
4411+ -- matched index and create a new index name upon restore.
4412+ , snapRestoreRenameReplacement :: Maybe (NonEmpty RestoreRenameToken)
4413+ -- ^ Expression of how index renames should be constructed.
4414+ , snapRestorePartial :: Bool
4415+ -- ^ If some indices fail to restore, should the process proceed?
4416+ , snapRestoreIncludeAliases :: Bool
4417+ -- ^ Should the restore also restore the aliases captured in the
4418+ -- snapshot.
4419+ , snapRestoreIndexSettingsOverrides :: Maybe RestoreIndexSettings
4420+ -- ^ Settings to apply during the restore process. __NOTE:__ This
4421+ -- option is not supported in ES < 1.5 and should be set to
4422+ -- Nothing in that case.
4423+ , snapRestoreIgnoreIndexSettings :: Maybe (NonEmpty Text)
4424+ -- ^ This type could be more rich but it isn't clear which
4425+ -- settings are allowed to be ignored during restore, so we're
4426+ -- going with including this feature in a basic form rather than
4427+ -- omitting it. One example here would be
4428+ -- "index.refresh_interval". Any setting specified here will
4429+ -- revert back to the server default during the restore process.
4430+ } deriving (Eq, Generic, Show, Typeable)
4431+
4432+-- | Regex-stype pattern, e.g. "index_(.+)" to match index names
4433+newtype RestoreRenamePattern = RestoreRenamePattern { rrPattern :: Text }
4434+ deriving (Show, Eq, Generic, Typeable, Ord, ToJSON)
4435+
4436+
4437+-- | A single token in a index renaming scheme for a restore. These
4438+-- are concatenated into a string before being sent to
4439+-- ElasticSearch. Check out these Java
4440+-- <https://docs.oracle.com/javase/7/docs/api/java/util/regex/Matcher.html docs> to find out more if you're into that sort of thing.
4441+data RestoreRenameToken = RRTLit Text
4442+ -- ^ Just a literal string of characters
4443+ | RRSubWholeMatch
4444+ -- ^ Equivalent to $0. The entire matched pattern, not any subgroup
4445+ | RRSubGroup RRGroupRefNum
4446+ -- ^ A specific reference to a group number
4447+ deriving (Show, Eq, Generic, Typeable)
4448+
4449+
4450+-- | A group number for regex matching. Only values from 1-9 are
4451+-- supported. Construct with 'mkRRGroupRefNum'
4452+newtype RRGroupRefNum = RRGroupRefNum { rrGroupRefNum :: Int }
4453+ deriving (Show, Eq, Generic, Typeable, Ord)
4454+
4455+instance Bounded RRGroupRefNum where
4456+ minBound = RRGroupRefNum 1
4457+ maxBound = RRGroupRefNum 9
4458+
4459+
4460+-- | Only allows valid group number references (1-9).
4461+mkRRGroupRefNum :: Int -> Maybe RRGroupRefNum
4462+mkRRGroupRefNum i
4463+ | i >= (rrGroupRefNum minBound) && i <= (rrGroupRefNum maxBound) =
4464+ Just $ RRGroupRefNum i
4465+ | otherwise = Nothing
4466+
4467+
4468+-- | Reasonable defaults for snapshot restores
4469+--
4470+-- * snapRestoreWaitForCompletion False
4471+-- * snapRestoreIndices Nothing
4472+-- * snapRestoreIgnoreUnavailable False
4473+-- * snapRestoreIncludeGlobalState True
4474+-- * snapRestoreRenamePattern Nothing
4475+-- * snapRestoreRenameReplacement Nothing
4476+-- * snapRestorePartial False
4477+-- * snapRestoreIncludeAliases True
4478+-- * snapRestoreIndexSettingsOverrides Nothing
4479+-- * snapRestoreIgnoreIndexSettings Nothing
4480+defaultSnapshotRestoreSettings :: SnapshotRestoreSettings
4481+defaultSnapshotRestoreSettings = SnapshotRestoreSettings {
4482+ snapRestoreWaitForCompletion = False
4483+ , snapRestoreIndices = Nothing
4484+ , snapRestoreIgnoreUnavailable = False
4485+ , snapRestoreIncludeGlobalState = True
4486+ , snapRestoreRenamePattern = Nothing
4487+ , snapRestoreRenameReplacement = Nothing
4488+ , snapRestorePartial = False
4489+ , snapRestoreIncludeAliases = True
4490+ , snapRestoreIndexSettingsOverrides = Nothing
4491+ , snapRestoreIgnoreIndexSettings = Nothing
4492+ }
4493+
4494+
4495+-- | Index settings that can be overridden. The docs only mention you
4496+-- can update number of replicas, but there may be more. You
4497+-- definitely cannot override shard count.
4498+data RestoreIndexSettings = RestoreIndexSettings {
4499+ restoreOverrideReplicas :: Maybe ReplicaCount
4500+ } deriving (Show, Eq, Generic, Typeable)
4501+
4502+
4503+instance ToJSON RestoreIndexSettings where
4504+ toJSON RestoreIndexSettings {..} = object prs
4505+ where
4506+ prs = catMaybes [("index.number_of_replicas" .=) <$> restoreOverrideReplicas]
4507+
4508+
4509+instance FromJSON NodesInfo where
4510+ parseJSON = withObject "NodesInfo" parse
4511+ where
4512+ parse o = do
4513+ nodes <- o .: "nodes"
4514+ infos <- forM (HM.toList nodes) $ \(fullNID, v) -> do
4515+ node <- parseJSON v
4516+ parseNodeInfo (FullNodeId fullNID) node
4517+ cn <- o .: "cluster_name"
4518+ return (NodesInfo infos cn)
4519+
4520+instance FromJSON NodesStats where
4521+ parseJSON = withObject "NodesStats" parse
4522+ where
4523+ parse o = do
4524+ nodes <- o .: "nodes"
4525+ stats <- forM (HM.toList nodes) $ \(fullNID, v) -> do
4526+ node <- parseJSON v
4527+ parseNodeStats (FullNodeId fullNID) node
4528+ cn <- o .: "cluster_name"
4529+ return (NodesStats stats cn)
4530+
4531+instance FromJSON NodeBreakerStats where
4532+ parseJSON = withObject "NodeBreakerStats" parse
4533+ where
4534+ parse o = NodeBreakerStats <$> o .: "tripped"
4535+ <*> o .: "overhead"
4536+ <*> o .: "estimated_size_in_bytes"
4537+ <*> o .: "limit_size_in_bytes"
4538+
4539+instance FromJSON NodeHTTPStats where
4540+ parseJSON = withObject "NodeHTTPStats" parse
4541+ where
4542+ parse o = NodeHTTPStats <$> o .: "total_opened"
4543+ <*> o .: "current_open"
4544+
4545+instance FromJSON NodeTransportStats where
4546+ parseJSON = withObject "NodeTransportStats" parse
4547+ where
4548+ parse o = NodeTransportStats <$> o .: "tx_size_in_bytes"
4549+ <*> o .: "tx_count"
4550+ <*> o .: "rx_size_in_bytes"
4551+ <*> o .: "rx_count"
4552+ <*> o .: "server_open"
4553+
4554+instance FromJSON NodeFSStats where
4555+ parseJSON = withObject "NodeFSStats" parse
4556+ where
4557+ parse o = NodeFSStats <$> o .: "data"
4558+ <*> o .: "total"
4559+ <*> (posixMS <$> o .: "timestamp")
4560+
4561+instance FromJSON NodeDataPathStats where
4562+ parseJSON = withObject "NodeDataPathStats" parse
4563+ where
4564+ parse o =
4565+ NodeDataPathStats <$> (fmap unStringlyTypedDouble <$> o .:? "disk_service_time")
4566+ <*> (fmap unStringlyTypedDouble <$> o .:? "disk_queue")
4567+ <*> o .:? "disk_io_size_in_bytes"
4568+ <*> o .:? "disk_write_size_in_bytes"
4569+ <*> o .:? "disk_read_size_in_bytes"
4570+ <*> o .:? "disk_io_op"
4571+ <*> o .:? "disk_writes"
4572+ <*> o .:? "disk_reads"
4573+ <*> o .: "available_in_bytes"
4574+ <*> o .: "free_in_bytes"
4575+ <*> o .: "total_in_bytes"
4576+ <*> o .:? "type"
4577+ <*> o .:? "dev"
4578+ <*> o .: "mount"
4579+ <*> o .: "path"
4580+
4581+newtype StringlyTypedDouble = StringlyTypedDouble { unStringlyTypedDouble :: Double }
4582+
4583+
4584+instance FromJSON StringlyTypedDouble where
4585+ parseJSON = fmap StringlyTypedDouble . parseJSON . unStringlyTypeJSON
4586+
4587+
4588+instance FromJSON NodeFSTotalStats where
4589+ parseJSON = withObject "NodeFSTotalStats" parse
4590+ where
4591+ parse o = NodeFSTotalStats <$> (fmap unStringlyTypedDouble <$> o .:? "disk_service_time")
4592+ <*> (fmap unStringlyTypedDouble <$> o .:? "disk_queue")
4593+ <*> o .:? "disk_io_size_in_bytes"
4594+ <*> o .:? "disk_write_size_in_bytes"
4595+ <*> o .:? "disk_read_size_in_bytes"
4596+ <*> o .:? "disk_io_op"
4597+ <*> o .:? "disk_writes"
4598+ <*> o .:? "disk_reads"
4599+ <*> o .: "available_in_bytes"
4600+ <*> o .: "free_in_bytes"
4601+ <*> o .: "total_in_bytes"
4602+
4603+instance FromJSON NodeNetworkStats where
4604+ parseJSON = withObject "NodeNetworkStats" parse
4605+ where
4606+ parse o = do
4607+ tcp <- o .: "tcp"
4608+ NodeNetworkStats <$> tcp .: "out_rsts"
4609+ <*> tcp .: "in_errs"
4610+ <*> tcp .: "attempt_fails"
4611+ <*> tcp .: "estab_resets"
4612+ <*> tcp .: "retrans_segs"
4613+ <*> tcp .: "out_segs"
4614+ <*> tcp .: "in_segs"
4615+ <*> tcp .: "curr_estab"
4616+ <*> tcp .: "passive_opens"
4617+ <*> tcp .: "active_opens"
4618+
4619+instance FromJSON NodeThreadPoolsStats where
4620+ parseJSON = withObject "NodeThreadPoolsStats" parse
4621+ where
4622+ parse o = NodeThreadPoolsStats <$> o .: "snapshot"
4623+ <*> o .: "bulk"
4624+ <*> o .: "force_merge"
4625+ <*> o .: "get"
4626+ <*> o .: "management"
4627+ <*> o .:? "fetch_shard_store"
4628+ <*> o .:? "optimize"
4629+ <*> o .: "flush"
4630+ <*> o .: "search"
4631+ <*> o .: "warmer"
4632+ <*> o .: "generic"
4633+ <*> o .:? "suggest"
4634+ <*> o .: "refresh"
4635+ <*> o .: "index"
4636+ <*> o .:? "listener"
4637+ <*> o .:? "fetch_shard_started"
4638+ <*> o .:? "percolate"
4639+instance FromJSON NodeThreadPoolStats where
4640+ parseJSON = withObject "NodeThreadPoolStats" parse
4641+ where
4642+ parse o = NodeThreadPoolStats <$> o .: "completed"
4643+ <*> o .: "largest"
4644+ <*> o .: "rejected"
4645+ <*> o .: "active"
4646+ <*> o .: "queue"
4647+ <*> o .: "threads"
4648+
4649+instance FromJSON NodeJVMStats where
4650+ parseJSON = withObject "NodeJVMStats" parse
4651+ where
4652+ parse o = do
4653+ bufferPools <- o .: "buffer_pools"
4654+ mapped <- bufferPools .: "mapped"
4655+ direct <- bufferPools .: "direct"
4656+ gc <- o .: "gc"
4657+ collectors <- gc .: "collectors"
4658+ oldC <- collectors .: "old"
4659+ youngC <- collectors .: "young"
4660+ threads <- o .: "threads"
4661+ mem <- o .: "mem"
4662+ pools <- mem .: "pools"
4663+ oldM <- pools .: "old"
4664+ survivorM <- pools .: "survivor"
4665+ youngM <- pools .: "young"
4666+ NodeJVMStats <$> pure mapped
4667+ <*> pure direct
4668+ <*> pure oldC
4669+ <*> pure youngC
4670+ <*> threads .: "peak_count"
4671+ <*> threads .: "count"
4672+ <*> pure oldM
4673+ <*> pure survivorM
4674+ <*> pure youngM
4675+ <*> mem .: "non_heap_committed_in_bytes"
4676+ <*> mem .: "non_heap_used_in_bytes"
4677+ <*> mem .: "heap_max_in_bytes"
4678+ <*> mem .: "heap_committed_in_bytes"
4679+ <*> mem .: "heap_used_percent"
4680+ <*> mem .: "heap_used_in_bytes"
4681+ <*> (unMS <$> o .: "uptime_in_millis")
4682+ <*> (posixMS <$> o .: "timestamp")
4683+
4684+instance FromJSON JVMBufferPoolStats where
4685+ parseJSON = withObject "JVMBufferPoolStats" parse
4686+ where
4687+ parse o = JVMBufferPoolStats <$> o .: "total_capacity_in_bytes"
4688+ <*> o .: "used_in_bytes"
4689+ <*> o .: "count"
4690+
4691+instance FromJSON JVMGCStats where
4692+ parseJSON = withObject "JVMGCStats" parse
4693+ where
4694+ parse o = JVMGCStats <$> (unMS <$> o .: "collection_time_in_millis")
4695+ <*> o .: "collection_count"
4696+
4697+instance FromJSON JVMPoolStats where
4698+ parseJSON = withObject "JVMPoolStats" parse
4699+ where
4700+ parse o = JVMPoolStats <$> o .: "peak_max_in_bytes"
4701+ <*> o .: "peak_used_in_bytes"
4702+ <*> o .: "max_in_bytes"
4703+ <*> o .: "used_in_bytes"
4704+
4705+instance FromJSON NodeProcessStats where
4706+ parseJSON = withObject "NodeProcessStats" parse
4707+ where
4708+ parse o = do
4709+ mem <- o .: "mem"
4710+ cpu <- o .: "cpu"
4711+ NodeProcessStats <$> (posixMS <$> o .: "timestamp")
4712+ <*> o .: "open_file_descriptors"
4713+ <*> o .: "max_file_descriptors"
4714+ <*> cpu .: "percent"
4715+ <*> (unMS <$> cpu .: "total_in_millis")
4716+ <*> mem .: "total_virtual_in_bytes"
4717+
4718+instance FromJSON NodeOSStats where
4719+ parseJSON = withObject "NodeOSStats" parse
4720+ where
4721+ parse o = do
4722+ swap <- o .: "swap"
4723+ mem <- o .: "mem"
4724+ cpu <- o .: "cpu"
4725+ load <- o .:? "load_average"
4726+ NodeOSStats <$> (posixMS <$> o .: "timestamp")
4727+ <*> cpu .: "percent"
4728+ <*> pure load
4729+ <*> mem .: "total_in_bytes"
4730+ <*> mem .: "free_in_bytes"
4731+ <*> mem .: "free_percent"
4732+ <*> mem .: "used_in_bytes"
4733+ <*> mem .: "used_percent"
4734+ <*> swap .: "total_in_bytes"
4735+ <*> swap .: "free_in_bytes"
4736+ <*> swap .: "used_in_bytes"
4737+
4738+instance FromJSON LoadAvgs where
4739+ parseJSON = withArray "LoadAvgs" parse
4740+ where
4741+ parse v = case V.toList v of
4742+ [one, five, fifteen] -> LoadAvgs <$> parseJSON one
4743+ <*> parseJSON five
4744+ <*> parseJSON fifteen
4745+ _ -> fail "Expecting a triple of Doubles"
4746+
4747+instance FromJSON NodeIndicesStats where
4748+ parseJSON = withObject "NodeIndicesStats" parse
4749+ where
4750+ parse o = do
4751+ let (.::) mv k = case mv of
4752+ Just v -> Just <$> v .: k
4753+ Nothing -> pure Nothing
4754+ mRecovery <- o .:? "recovery"
4755+ mQueryCache <- o .:? "query_cache"
4756+ mSuggest <- o .:? "suggest"
4757+ translog <- o .: "translog"
4758+ segments <- o .: "segments"
4759+ completion <- o .: "completion"
4760+ mPercolate <- o .:? "percolate"
4761+ fielddata <- o .: "fielddata"
4762+ warmer <- o .: "warmer"
4763+ flush <- o .: "flush"
4764+ refresh <- o .: "refresh"
4765+ merges <- o .: "merges"
4766+ search <- o .: "search"
4767+ getStats <- o .: "get"
4768+ indexing <- o .: "indexing"
4769+ store <- o .: "store"
4770+ docs <- o .: "docs"
4771+ NodeIndicesStats <$> (fmap unMS <$> mRecovery .:: "throttle_time_in_millis")
4772+ <*> mRecovery .:: "current_as_target"
4773+ <*> mRecovery .:: "current_as_source"
4774+ <*> mQueryCache .:: "miss_count"
4775+ <*> mQueryCache .:: "hit_count"
4776+ <*> mQueryCache .:: "evictions"
4777+ <*> mQueryCache .:: "memory_size_in_bytes"
4778+ <*> mSuggest .:: "current"
4779+ <*> (fmap unMS <$> mSuggest .:: "time_in_millis")
4780+ <*> mSuggest .:: "total"
4781+ <*> translog .: "size_in_bytes"
4782+ <*> translog .: "operations"
4783+ <*> segments .:? "fixed_bit_set_memory_in_bytes"
4784+ <*> segments .: "version_map_memory_in_bytes"
4785+ <*> segments .:? "index_writer_max_memory_in_bytes"
4786+ <*> segments .: "index_writer_memory_in_bytes"
4787+ <*> segments .: "memory_in_bytes"
4788+ <*> segments .: "count"
4789+ <*> completion .: "size_in_bytes"
4790+ <*> mPercolate .:: "queries"
4791+ <*> mPercolate .:: "memory_size_in_bytes"
4792+ <*> mPercolate .:: "current"
4793+ <*> (fmap unMS <$> mPercolate .:: "time_in_millis")
4794+ <*> mPercolate .:: "total"
4795+ <*> fielddata .: "evictions"
4796+ <*> fielddata .: "memory_size_in_bytes"
4797+ <*> (unMS <$> warmer .: "total_time_in_millis")
4798+ <*> warmer .: "total"
4799+ <*> warmer .: "current"
4800+ <*> (unMS <$> flush .: "total_time_in_millis")
4801+ <*> flush .: "total"
4802+ <*> (unMS <$> refresh .: "total_time_in_millis")
4803+ <*> refresh .: "total"
4804+ <*> merges .: "total_size_in_bytes"
4805+ <*> merges .: "total_docs"
4806+ <*> (unMS <$> merges .: "total_time_in_millis")
4807+ <*> merges .: "total"
4808+ <*> merges .: "current_size_in_bytes"
4809+ <*> merges .: "current_docs"
4810+ <*> merges .: "current"
4811+ <*> search .: "fetch_current"
4812+ <*> (unMS <$> search .: "fetch_time_in_millis")
4813+ <*> search .: "fetch_total"
4814+ <*> search .: "query_current"
4815+ <*> (unMS <$> search .: "query_time_in_millis")
4816+ <*> search .: "query_total"
4817+ <*> search .: "open_contexts"
4818+ <*> getStats .: "current"
4819+ <*> (unMS <$> getStats .: "missing_time_in_millis")
4820+ <*> getStats .: "missing_total"
4821+ <*> (unMS <$> getStats .: "exists_time_in_millis")
4822+ <*> getStats .: "exists_total"
4823+ <*> (unMS <$> getStats .: "time_in_millis")
4824+ <*> getStats .: "total"
4825+ <*> (fmap unMS <$> indexing .:? "throttle_time_in_millis")
4826+ <*> indexing .:? "is_throttled"
4827+ <*> indexing .:? "noop_update_total"
4828+ <*> indexing .: "delete_current"
4829+ <*> (unMS <$> indexing .: "delete_time_in_millis")
4830+ <*> indexing .: "delete_total"
4831+ <*> indexing .: "index_current"
4832+ <*> (unMS <$> indexing .: "index_time_in_millis")
4833+ <*> indexing .: "index_total"
4834+ <*> (unMS <$> store .: "throttle_time_in_millis")
4835+ <*> store .: "size_in_bytes"
4836+ <*> docs .: "deleted"
4837+ <*> docs .: "count"
4838+
4839+instance FromJSON NodeBreakersStats where
4840+ parseJSON = withObject "NodeBreakersStats" parse
4841+ where
4842+ parse o = NodeBreakersStats <$> o .: "parent"
4843+ <*> o .: "request"
4844+ <*> o .: "fielddata"
4845+
4846+parseNodeStats :: FullNodeId -> Object -> Parser NodeStats
4847+parseNodeStats fnid o = do
4848+ NodeStats <$> o .: "name"
4849+ <*> pure fnid
4850+ <*> o .:? "breakers"
4851+ <*> o .: "http"
4852+ <*> o .: "transport"
4853+ <*> o .: "fs"
4854+ <*> o .:? "network"
4855+ <*> o .: "thread_pool"
4856+ <*> o .: "jvm"
4857+ <*> o .: "process"
4858+ <*> o .: "os"
4859+ <*> o .: "indices"
4860+
4861+parseNodeInfo :: FullNodeId -> Object -> Parser NodeInfo
4862+parseNodeInfo nid o =
4863+ NodeInfo <$> o .:? "http_address"
4864+ <*> o .: "build_hash"
4865+ <*> o .: "version"
4866+ <*> o .: "ip"
4867+ <*> o .: "host"
4868+ <*> o .: "transport_address"
4869+ <*> o .: "name"
4870+ <*> pure nid
4871+ <*> o .: "plugins"
4872+ <*> o .: "http"
4873+ <*> o .: "transport"
4874+ <*> o .:? "network"
4875+ <*> o .: "thread_pool"
4876+ <*> o .: "jvm"
4877+ <*> o .: "process"
4878+ <*> o .: "os"
4879+ <*> o .: "settings"
4880+
4881+instance FromJSON NodePluginInfo where
4882+ parseJSON = withObject "NodePluginInfo" parse
4883+ where
4884+ parse o = NodePluginInfo <$> o .:? "site"
4885+ <*> o .:? "jvm"
4886+ <*> o .: "description"
4887+ <*> o .: "version"
4888+ <*> o .: "name"
4889+
4890+instance FromJSON NodeHTTPInfo where
4891+ parseJSON = withObject "NodeHTTPInfo" parse
4892+ where
4893+ parse o = NodeHTTPInfo <$> o .: "max_content_length_in_bytes"
4894+ <*> parseJSON (Object o)
4895+
4896+instance FromJSON BoundTransportAddress where
4897+ parseJSON = withObject "BoundTransportAddress" parse
4898+ where
4899+ parse o = BoundTransportAddress <$> o .: "publish_address"
4900+ <*> o .: "bound_address"
4901+
4902+instance FromJSON NodeOSInfo where
4903+ parseJSON = withObject "NodeOSInfo" parse
4904+ where
4905+ parse o = do
4906+ NodeOSInfo <$> (unMS <$> o .: "refresh_interval_in_millis")
4907+ <*> o .: "name"
4908+ <*> o .: "arch"
4909+ <*> o .: "version"
4910+ <*> o .: "available_processors"
4911+ <*> o .: "allocated_processors"
4912+
4913+
4914+instance FromJSON CPUInfo where
4915+ parseJSON = withObject "CPUInfo" parse
4916+ where
4917+ parse o = CPUInfo <$> o .: "cache_size_in_bytes"
4918+ <*> o .: "cores_per_socket"
4919+ <*> o .: "total_sockets"
4920+ <*> o .: "total_cores"
4921+ <*> o .: "mhz"
4922+ <*> o .: "model"
4923+ <*> o .: "vendor"
4924+
4925+instance FromJSON NodeProcessInfo where
4926+ parseJSON = withObject "NodeProcessInfo" parse
4927+ where
4928+ parse o = NodeProcessInfo <$> o .: "mlockall"
4929+ <*> o .:? "max_file_descriptors"
4930+ <*> o .: "id"
4931+ <*> (unMS <$> o .: "refresh_interval_in_millis")
4932+
4933+instance FromJSON NodeJVMInfo where
4934+ parseJSON = withObject "NodeJVMInfo" parse
4935+ where
4936+ parse o = NodeJVMInfo <$> o .: "memory_pools"
4937+ <*> o .: "gc_collectors"
4938+ <*> o .: "mem"
4939+ <*> (posixMS <$> o .: "start_time_in_millis")
4940+ <*> o .: "vm_vendor"
4941+ <*> o .: "vm_version"
4942+ <*> o .: "vm_name"
4943+ <*> (unJVMVersion <$> o .: "version")
4944+ <*> o .: "pid"
4945+
4946+instance FromJSON JVMVersion where
4947+ parseJSON (String t) =
4948+ JVMVersion <$> parseJSON (String (T.replace "_" "." t))
4949+ parseJSON v = JVMVersion <$> parseJSON v
4950+
4951+instance FromJSON JVMMemoryInfo where
4952+ parseJSON = withObject "JVMMemoryInfo" parse
4953+ where
4954+ parse o = JVMMemoryInfo <$> o .: "direct_max_in_bytes"
4955+ <*> o .: "non_heap_max_in_bytes"
4956+ <*> o .: "non_heap_init_in_bytes"
4957+ <*> o .: "heap_max_in_bytes"
4958+ <*> o .: "heap_init_in_bytes"
4959+
4960+instance FromJSON NodeThreadPoolsInfo where
4961+ parseJSON = withObject "NodeThreadPoolsInfo" parse
4962+ where
4963+ parse o = NodeThreadPoolsInfo <$> o .: "refresh"
4964+ <*> o .: "management"
4965+ <*> o .:? "percolate"
4966+ <*> o .:? "listener"
4967+ <*> o .:? "fetch_shard_started"
4968+ <*> o .: "search"
4969+ <*> o .: "flush"
4970+ <*> o .: "warmer"
4971+ <*> o .:? "optimize"
4972+ <*> o .: "bulk"
4973+ <*> o .:? "suggest"
4974+ <*> o .: "force_merge"
4975+ <*> o .: "snapshot"
4976+ <*> o .: "get"
4977+ <*> o .:? "fetch_shard_store"
4978+ <*> o .: "index"
4979+ <*> o .: "generic"
4980+
4981+instance FromJSON NodeThreadPoolInfo where
4982+ parseJSON = withObject "NodeThreadPoolInfo" parse
4983+ where
4984+ parse o = do
4985+ ka <- maybe (return Nothing) (fmap Just . parseStringInterval) =<< o .:? "keep_alive"
4986+ NodeThreadPoolInfo <$> (parseJSON . unStringlyTypeJSON =<< o .: "queue_size")
4987+ <*> pure ka
4988+ <*> o .:? "min"
4989+ <*> o .:? "max"
4990+ <*> o .: "type"
4991+
4992+parseStringInterval :: (Monad m) => String -> m NominalDiffTime
4993+parseStringInterval s = case span isNumber s of
4994+ ("", _) -> fail "Invalid interval"
4995+ (nS, unitS) -> case (readMay nS, readMay unitS) of
4996+ (Just n, Just unit) -> return (fromInteger (n * unitNDT unit))
4997+ (Nothing, _) -> fail "Invalid interval number"
4998+ (_, Nothing) -> fail "Invalid interval unit"
4999+ where
5000+ unitNDT Seconds = 1
5001+ unitNDT Minutes = 60
5002+ unitNDT Hours = 60 * 60
5003+ unitNDT Days = 24 * 60 * 60
5004+ unitNDT Weeks = 7 * 24 * 60 * 60
5005+
5006+instance FromJSON ThreadPoolSize where
5007+ parseJSON v = parseAsNumber v <|> parseAsString v
5008+ where
5009+ parseAsNumber = parseAsInt <=< parseJSON
5010+ parseAsInt (-1) = return ThreadPoolUnbounded
5011+ parseAsInt n
5012+ | n >= 0 = return (ThreadPoolBounded n)
5013+ | otherwise = fail "Thread pool size must be >= -1."
5014+ parseAsString = withText "ThreadPoolSize" $ \t ->
5015+ case first (readMay . T.unpack) (T.span isNumber t) of
5016+ (Just n, "k") -> return (ThreadPoolBounded (n * 1000))
5017+ (Just n, "") -> return (ThreadPoolBounded n)
5018+ _ -> fail ("Invalid thread pool size " <> T.unpack t)
5019+
5020+instance FromJSON ThreadPoolType where
5021+ parseJSON = withText "ThreadPoolType" parse
5022+ where
5023+ parse "scaling" = return ThreadPoolScaling
5024+ parse "fixed" = return ThreadPoolFixed
5025+ parse "cached" = return ThreadPoolCached
5026+ parse e = fail ("Unexpected thread pool type" <> T.unpack e)
5027+
5028+instance FromJSON NodeTransportInfo where
5029+ parseJSON = withObject "NodeTransportInfo" parse
5030+ where
5031+ parse o = NodeTransportInfo <$> (maybe (return mempty) parseProfiles =<< o .:? "profiles")
5032+ <*> parseJSON (Object o)
5033+ parseProfiles (Object o) | HM.null o = return []
5034+ parseProfiles v@(Array _) = parseJSON v
5035+ parseProfiles Null = return []
5036+ parseProfiles _ = fail "Could not parse profiles"
5037+
5038+instance FromJSON NodeNetworkInfo where
5039+ parseJSON = withObject "NodeNetworkInfo" parse
5040+ where
5041+ parse o = NodeNetworkInfo <$> o .: "primary_interface"
5042+ <*> (unMS <$> o .: "refresh_interval_in_millis")
5043+
5044+
5045+instance FromJSON NodeNetworkInterface where
5046+ parseJSON = withObject "NodeNetworkInterface" parse
5047+ where
5048+ parse o = NodeNetworkInterface <$> o .: "mac_address"
5049+ <*> o .: "name"
5050+ <*> o .: "address"
5051+
5052+newtype MaybeNA a = MaybeNA { unMaybeNA :: Maybe a }
5053+ deriving (Show, Eq)
5054+
5055+instance FromJSON a => FromJSON (MaybeNA a) where
5056+ parseJSON (String "NA") = pure $ MaybeNA Nothing
5057+ parseJSON o = MaybeNA . Just <$> parseJSON o
src/Database/V5/Bloodhound/Types/Class.hsnew file+17
@@ -0,0 +1,17 @@
1+{-# LANGUAGE CPP #-}
2+module Database.V5.Bloodhound.Types.Class
3+ ( Seminearring(..) )
4+ where
5+
6+#if !MIN_VERSION_base(4,8,0)
7+import Data.Monoid
8+#endif
9+
10+class Monoid a => Seminearring a where
11+ -- 0, +, *
12+ (<||>) :: a -> a -> a
13+ (<&&>) :: a -> a -> a
14+ (<&&>) = mappend
15+
16+infixr 5 <||>
17+infixr 5 <&&>
src/Database/V5/Bloodhound/Types/Internal.hsnew file+57
@@ -0,0 +1,57 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5+
6+-------------------------------------------------------------------------------
7+-- |
8+-- Module : Database.Bloodhound.Types.Internal
9+-- Copyright : (C) 2014 Chris Allen
10+-- License : BSD-style (see the file LICENSE)
11+-- Maintainer : Chris Allen <cma@bitemyapp.com>
12+-- Stability : provisional
13+-- Portability : DeriveGeneric, RecordWildCards
14+--
15+-- Internal data types for Bloodhound. These types may change without
16+-- notice so import at your own risk.
17+-------------------------------------------------------------------------------
18+module Database.V5.Bloodhound.Types.Internal
19+ ( BHEnv(..)
20+ , Server(..)
21+ , MonadBH(..)
22+ ) where
23+
24+
25+import Control.Applicative as A
26+import Control.Monad.Reader
27+import Data.Aeson
28+import Data.Text (Text)
29+import Data.Typeable (Typeable)
30+import GHC.Generics (Generic)
31+import Network.HTTP.Client
32+
33+{-| Common environment for Elasticsearch calls. Connections will be
34+ pipelined according to the provided HTTP connection manager.
35+-}
36+data BHEnv = BHEnv { bhServer :: Server
37+ , bhManager :: Manager
38+ , bhRequestHook :: Request -> IO Request
39+ -- ^ Low-level hook that is run before every request is sent. Used to implement custom authentication strategies. Defaults to 'return' with 'mkBHEnv'.
40+ }
41+
42+instance (Functor m, Applicative m, MonadIO m) => MonadBH (ReaderT BHEnv m) where
43+ getBHEnv = ask
44+
45+{-| 'Server' is used with the client functions to point at the ES instance
46+-}
47+newtype Server = Server Text deriving (Eq, Show, Generic, Typeable, FromJSON)
48+
49+{-| All API calls to Elasticsearch operate within
50+ MonadBH
51+ . The idea is that it can be easily embedded in your
52+ own monad transformer stack. A default instance for a ReaderT and
53+ alias 'BH' is provided for the simple case.
54+-}
55+class (Functor m, A.Applicative m, MonadIO m) => MonadBH m where
56+ getBHEnv :: m BHEnv
57+
stack-7.10.yaml+7−4
@@ -1,15 +1,18 @@
1−flags: {}
1+flags:
2+ bloodhound-examples:
3+ werror: true
24packages:
35- '.'
6+- './examples'
47extra-deps:
58- http-client-0.5.0
6−- aeson-0.11.1.0
9+- aeson-1.2.0.0
710- fail-4.9.0.0
811- http-types-0.9
912- attoparsec-0.13.0.1
10−- doctest-0.10.1
1113- quickcheck-properties-0.1
12−- semigroups-0.18.0.1
14+- semigroups-0.18.3
1315- uri-bytestring-0.1.9
1416- temporary-resourcet-0.1.0.0
17+- transformers-compat-0.5.1.4
1518resolver: lts-6.12
stack-7.8.yaml+3−1
@@ -1,15 +1,17 @@
11flags:
22 aeson:
33 old-locale: true
4+ bloodhound-examples:
5+ werror: true
46packages:
57- '.'
8+- './examples'
69extra-deps:
710- aeson-0.11.1.0
811- fail-4.9.0.0
912- http-types-0.9
1013- http-client-0.5.0
1114- attoparsec-0.13.0.1
12−- doctest-0.10.1
1315- quickcheck-properties-0.1
1416- semigroups-0.18.0.1
1517- tagged-0.8.3
stack-8.0.yaml+12−7
@@ -1,15 +1,20 @@
1−flags: {}
1+flags:
2+ bloodhound-examples:
3+ werror: true
4+
25packages:
36- '.'
4−extra-deps:
5−- quickcheck-properties-0.1
6−
7+- './examples'
8+# extra-deps:
79# - http-client-0.5.0
10+# - aeson-1.2.0.0
811# - fail-4.9.0.0
912# - http-types-0.9
1013# - attoparsec-0.13.0.1
11−# - doctest-0.10.1
12−# - semigroups-0.18.0.1
14+# - quickcheck-properties-0.1
15+# - semigroups-0.18.3
1316# - uri-bytestring-0.1.9
1417# - temporary-resourcet-0.1.0.0
15−resolver: nightly-2016-11-17
18+# - transformers-compat-0.5.1.4
19+
20+resolver: lts-8.14
stack-8.2.yamlnew file+44
@@ -0,0 +1,44 @@
1+compiler: ghc-8.2.0.20170507
2+compiler-check: match-exact
3+resolver: lts-8.13
4+setup-info:
5+ ghc:
6+ linux64:
7+ 8.2.0.20170507:
8+ url: https://downloads.haskell.org/~ghc/8.2.1-rc2/ghc-8.2.0.20170507-x86_64-deb8-linux.tar.xz
9+ content-length: 141011788
10+ sha1: ff886437c1d2ddfa5686d6c9efb0819a957c3dde
11+ macosx:
12+ 8.2.0.20170507:
13+ url: https://downloads.haskell.org/~ghc/8.2.1-rc2/ghc-8.2.0.20170507-x86_64-apple-darwin.tar.xz
14+ content-length: 126192308
15+ sha1: f059fe6f8a898d5619ab03e06e0ff0912a5f3340
16+ windows64:
17+ 8.2.0.20170507:
18+ url: https://downloads.haskell.org/~ghc/8.2.1-rc2/ghc-8.2.0.20170507-x86_64-unknown-mingw32.tar.xz
19+ content-length: 177295548
20+ sha1: 87646ca3a89278775ceb7be429faafac3cf738d8
21+
22+ghc-options: { "*": -XTypeSynonymInstances }
23+
24+flags:
25+ bloodhound-examples:
26+ werror: false
27+
28+allow-newer: true
29+
30+packages:
31+- '.'
32+- './examples'
33+
34+extra-deps:
35+- http-client-0.5.0
36+- aeson-1.2.0.0
37+- fail-4.9.0.0
38+- http-types-0.9
39+- attoparsec-0.13.0.1
40+- quickcheck-properties-0.1
41+- semigroups-0.18.3
42+- uri-bytestring-0.1.9
43+- temporary-resourcet-0.1.0.0
44+- transformers-compat-0.5.1.4
stack.yamlmode 120000 → 100644+13−1
@@ -1 +0,0 @@
1−stack-7.10.yaml
@@ -0,0 +1,13 @@
1+flags:
2+ bloodhound-examples:
3+ werror: true
4+packages:
5+- '.'
6+- './examples'
7+extra-deps:
8+- hspec-2.4.1
9+- hspec-core-2.4.1
10+- hspec-discover-2.4.1
11+- quickcheck-properties-0.1
12+- vector-0.12.0.0
13+resolver: nightly-2017-01-30
tests/tests.hs → tests/V1/tests.hsrenamed+16−4
@@ -40,7 +40,7 @@ import Data.Time.Clock (NominalDiffTime, UTCTime (..),
4040import Data.Typeable
4141import qualified Data.Vector as V
4242import qualified Data.Version as Vers
43−import Database.Bloodhound
43+import Database.V1.Bloodhound
4444import GHC.Generics as G
4545import Network.HTTP.Client hiding (Proxy)
4646import qualified Network.HTTP.Types.Method as NHTM
@@ -48,7 +48,7 @@ import qualified Network.HTTP.Types.Status as NHTS
4848import qualified Network.URI as URI
4949import Prelude hiding (filter)
5050import System.IO.Temp
51−import System.Posix.Files
51+import System.PosixCompat.Files
5252import Test.Hspec
5353import Test.QuickCheck.Property.Monoid (T (..), eq, prop_Monoid)
5454
@@ -586,7 +586,7 @@ instance ApproxEq Vers.Version where
586586 (=~) = (==)
587587instance (ApproxEq a, Show a) => ApproxEq [a] where
588588 as =~ bs = and (zipWith (=~) as bs)
589−instance (ApproxEq l, Show l, ApproxEq r, Show r) => ApproxEq (Either l r) where
589+instance (ApproxEq l, ApproxEq r) => ApproxEq (Either l r) where
590590 Left a =~ Left b = a =~ b
591591 Right a =~ Right b = a =~ b
592592 _ =~ _ = False
@@ -1306,11 +1306,23 @@ main = hspec $ do
13061306 searchExpectAggs search
13071307 searchValidBucketAgg search "users" toTerms
13081308
1309+ it "return sub-aggregation results" $ withTestEnv $ do
1310+ _ <- insertData
1311+ let subaggs = mkAggregations "age_agg" . TermsAgg $ mkTermsAggregation "age"
1312+ agg = TermsAgg $ (mkTermsAggregation "user") { termAggs = Just subaggs}
1313+ search = mkAggregateSearch Nothing $ mkAggregations "users" agg
1314+ reply <- searchByIndex testIndex search
1315+ let result = decode (responseBody reply) :: Maybe (SearchResult Tweet)
1316+ usersAggResults = result >>= aggregations >>= toTerms "users"
1317+ subAggResults = usersAggResults >>= (listToMaybe . buckets) >>= termsAggs >>= toTerms "age_agg"
1318+ subAddResultsExists = isJust subAggResults
1319+ liftIO $ (subAddResultsExists) `shouldBe` True
1320+
13091321 it "returns cardinality aggregation results" $ withTestEnv $ do
13101322 _ <- insertData
13111323 let cardinality = CardinalityAgg $ mkCardinalityAggregation $ FieldName "user"
13121324 let search = mkAggregateSearch Nothing $ mkAggregations "users" cardinality
1313− let search' = search { Database.Bloodhound.from = From 0, size = Size 0 }
1325+ let search' = search { Database.V1.Bloodhound.from = From 0, size = Size 0 }
13141326 searchExpectAggs search'
13151327 let docCountPair k n = (k, object ["value" .= Number n])
13161328 res <- searchTweets search'
tests/V5/tests.hsnew file+1664
@@ -0,0 +1,1664 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE DefaultSignatures #-}
3+{-# LANGUAGE DeriveDataTypeable #-}
4+{-# LANGUAGE DeriveGeneric #-}
5+{-# LANGUAGE FlexibleContexts #-}
6+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7+{-# LANGUAGE OverloadedStrings #-}
8+{-# LANGUAGE RecordWildCards #-}
9+{-# LANGUAGE ScopedTypeVariables #-}
10+{-# LANGUAGE TypeOperators #-}
11+{-# OPTIONS_GHC -fno-warn-orphans #-}
12+#if __GLASGOW_HASKELL__ < 800
13+{-# OPTIONS_GHC -fcontext-stack=100 #-}
14+#endif
15+module Main where
16+
17+import Control.Applicative
18+import Control.Error
19+import Control.Exception (evaluate)
20+import Control.Monad
21+import Control.Monad.Catch
22+import Control.Monad.Reader
23+import Data.Aeson
24+import Data.Aeson.Types (parseEither)
25+import qualified Data.ByteString.Lazy.Char8 as BL8
26+import qualified Data.HashMap.Strict as HM
27+import Data.List (nub)
28+import qualified Data.List as L
29+import Data.List.NonEmpty (NonEmpty (..))
30+import qualified Data.List.NonEmpty as NE
31+import qualified Data.Map.Strict as M
32+import Data.Monoid
33+import Data.Ord (comparing)
34+import Data.Proxy
35+import Data.Text (Text)
36+import qualified Data.Text as T
37+import Data.Time.Calendar (Day (..), fromGregorian)
38+import Data.Time.Clock (NominalDiffTime, UTCTime (..),
39+ secondsToDiffTime)
40+import Data.Typeable
41+import qualified Data.Vector as V
42+import qualified Data.Version as Vers
43+import Database.V5.Bloodhound
44+import GHC.Generics as G
45+import Network.HTTP.Client hiding (Proxy)
46+import qualified Network.HTTP.Types.Method as NHTM
47+import qualified Network.HTTP.Types.Status as NHTS
48+import qualified Network.URI as URI
49+import Prelude hiding (filter)
50+import System.IO.Temp
51+import System.PosixCompat.Files
52+import Test.Hspec
53+import Test.QuickCheck.Property.Monoid (T (..), eq, prop_Monoid)
54+
55+import Test.Hspec.QuickCheck (prop)
56+import Test.QuickCheck
57+
58+import qualified Generics.SOP as SOP
59+import qualified Generics.SOP.GGP as SOP
60+
61+testServer :: Server
62+testServer = Server "http://localhost:9200"
63+testIndex :: IndexName
64+testIndex = IndexName "bloodhound-tests-twitter-1"
65+testMapping :: MappingName
66+testMapping = MappingName "tweet"
67+
68+withTestEnv :: BH IO a -> IO a
69+withTestEnv = withBH defaultManagerSettings testServer
70+
71+validateStatus :: Show body => Response body -> Int -> Expectation
72+validateStatus resp expected =
73+ if actual == expected
74+ then return ()
75+ else expectationFailure ("Expected " <> show expected <> " but got " <> show actual <> ": " <> show body)
76+ where
77+ actual = NHTS.statusCode (responseStatus resp)
78+ body = responseBody resp
79+
80+createExampleIndex :: (MonadBH m) => m Reply
81+createExampleIndex = createIndex (IndexSettings (ShardCount 1) (ReplicaCount 0)) testIndex
82+deleteExampleIndex :: (MonadBH m) => m Reply
83+deleteExampleIndex = deleteIndex testIndex
84+
85+es13 :: Vers.Version
86+es13 = Vers.Version [1, 3, 0] []
87+
88+es12 :: Vers.Version
89+es12 = Vers.Version [1, 2, 0] []
90+
91+es11 :: Vers.Version
92+es11 = Vers.Version [1, 1, 0] []
93+
94+es14 :: Vers.Version
95+es14 = Vers.Version [1, 4, 0] []
96+
97+es15 :: Vers.Version
98+es15 = Vers.Version [1, 5, 0] []
99+
100+es16 :: Vers.Version
101+es16 = Vers.Version [1, 6, 0] []
102+
103+es20 :: Vers.Version
104+es20 = Vers.Version [2, 0, 0] []
105+
106+es50 :: Vers.Version
107+es50 = Vers.Version [5, 0, 0] []
108+
109+getServerVersion :: IO (Maybe Vers.Version)
110+getServerVersion = fmap extractVersion <$> withTestEnv getStatus
111+ where
112+ extractVersion = versionNumber . number . version
113+
114+-- | Get configured repo paths for snapshotting. Note that by default
115+-- this is not enabled and if we are over es 1.5, we won't be able to
116+-- test snapshotting. Note that this can and should be part of the
117+-- client functionality in a much less ad-hoc incarnation.
118+getRepoPaths :: IO [FilePath]
119+getRepoPaths = withTestEnv $ do
120+ bhe <- getBHEnv
121+ let Server s = bhServer bhe
122+ let tUrl = s <> "/" <> "_nodes"
123+ initReq <- parseRequest (URI.escapeURIString URI.isAllowedInURI (T.unpack tUrl))
124+ let req = setRequestIgnoreStatus $ initReq { method = NHTM.methodGet }
125+ Right (Object o) <- parseEsResponse =<< liftIO (httpLbs req (bhManager bhe))
126+ return $ fromMaybe mempty $ do
127+ Object nodes <- HM.lookup "nodes" o
128+ Object firstNode <- snd <$> headMay (HM.toList nodes)
129+ Object settings <- HM.lookup "settings" firstNode
130+ Object path <- HM.lookup "path" settings
131+ Array repo <- HM.lookup "repo" path
132+ return [ T.unpack t | String t <- V.toList repo]
133+
134+-- | 1.5 and earlier don't care about repo paths
135+canSnapshot :: IO Bool
136+canSnapshot = do
137+ caresAboutRepos <- atleast es16
138+ repoPaths <- getRepoPaths
139+ return (not caresAboutRepos || not (null (repoPaths)))
140+
141+atleast :: Vers.Version -> IO Bool
142+atleast v = getServerVersion >>= \x -> return $ x >= Just v
143+
144+atmost :: Vers.Version -> IO Bool
145+atmost v = getServerVersion >>= \x -> return $ x <= Just v
146+
147+is :: Vers.Version -> IO Bool
148+is v = getServerVersion >>= \x -> return $ x == Just v
149+
150+when' :: Monad m => m Bool -> m () -> m ()
151+when' b f = b >>= \x -> when x f
152+
153+(==~) :: (ApproxEq a) => a -> a -> Property
154+a ==~ b = counterexample (showApproxEq a ++ " !=~ " ++ showApproxEq b) (a =~ b)
155+
156+propJSON :: forall a. (Arbitrary a, ToJSON a, FromJSON a, Show a, ApproxEq a, Typeable a) => Proxy a -> Spec
157+propJSON _ = prop testName $ \(a :: a) ->
158+ let jsonStr = "via " <> BL8.unpack (encode a)
159+ in counterexample jsonStr (parseEither parseJSON (toJSON a) ==~ Right a)
160+ where testName = show ty <> " FromJSON/ToJSON roundtrips"
161+ ty = typeOf (undefined :: a)
162+
163+data Location = Location { lat :: Double
164+ , lon :: Double } deriving (Eq, Generic, Show)
165+
166+data Tweet = Tweet { user :: Text
167+ , postDate :: UTCTime
168+ , message :: Text
169+ , age :: Int
170+ , location :: Location
171+ , extra :: Maybe Text }
172+ deriving (Eq, Generic, Show)
173+
174+instance ToJSON Tweet where
175+ toJSON = genericToJSON defaultOptions
176+instance FromJSON Tweet where
177+ parseJSON = genericParseJSON defaultOptions
178+instance ToJSON Location where
179+ toJSON = genericToJSON defaultOptions
180+instance FromJSON Location where
181+ parseJSON = genericParseJSON defaultOptions
182+
183+data ParentMapping = ParentMapping deriving (Eq, Show)
184+
185+instance ToJSON ParentMapping where
186+ toJSON ParentMapping =
187+ object ["properties" .=
188+ object [ "user" .= object ["type" .= ("string" :: Text)
189+ , "fielddata" .= True
190+ ]
191+ -- Serializing the date as a date is breaking other tests, mysteriously.
192+ -- , "postDate" .= object [ "type" .= ("date" :: Text)
193+ -- , "format" .= ("YYYY-MM-dd`T`HH:mm:ss.SSSZZ" :: Text)]
194+ , "message" .= object ["type" .= ("string" :: Text)]
195+ , "age" .= object ["type" .= ("integer" :: Text)]
196+ , "location" .= object ["type" .= ("geo_point" :: Text)]
197+ , "extra" .= object ["type" .= ("keyword" :: Text)]
198+ ]]
199+
200+data ChildMapping = ChildMapping deriving (Eq, Show)
201+
202+instance ToJSON ChildMapping where
203+ toJSON ChildMapping =
204+ object ["_parent" .= object ["type" .= ("parent" :: Text)]
205+ , "properties" .=
206+ object [ "user" .= object ["type" .= ("string" :: Text)
207+ , "fielddata" .= True
208+ ]
209+ -- Serializing the date as a date is breaking other tests, mysteriously.
210+ -- , "postDate" .= object [ "type" .= ("date" :: Text)
211+ -- , "format" .= ("YYYY-MM-dd`T`HH:mm:ss.SSSZZ" :: Text)]
212+ , "message" .= object ["type" .= ("string" :: Text)]
213+ , "age" .= object ["type" .= ("integer" :: Text)]
214+ , "location" .= object ["type" .= ("geo_point" :: Text)]
215+ , "extra" .= object ["type" .= ("keyword" :: Text)]
216+ ]]
217+
218+data TweetMapping = TweetMapping deriving (Eq, Show)
219+
220+instance ToJSON TweetMapping where
221+ toJSON TweetMapping =
222+ object ["tweet" .=
223+ object ["properties" .=
224+ object [ "user" .= object [ "type" .= ("string" :: Text)
225+ , "fielddata" .= True
226+ ]
227+ -- Serializing the date as a date is breaking other tests, mysteriously.
228+ -- , "postDate" .= object [ "type" .= ("date" :: Text)
229+ -- , "format" .= ("YYYY-MM-dd`T`HH:mm:ss.SSSZZ" :: Text)]
230+ , "message" .= object ["type" .= ("string" :: Text)]
231+ , "age" .= object ["type" .= ("integer" :: Text)]
232+ , "location" .= object ["type" .= ("geo_point" :: Text)]
233+ , "extra" .= object ["type" .= ("keyword" :: Text)]
234+ ]]]
235+
236+exampleTweet :: Tweet
237+exampleTweet = Tweet { user = "bitemyapp"
238+ , postDate = UTCTime
239+ (ModifiedJulianDay 55000)
240+ (secondsToDiffTime 10)
241+ , message = "Use haskell!"
242+ , age = 10000
243+ , location = Location 40.12 (-71.34)
244+ , extra = Nothing }
245+
246+tweetWithExtra :: Tweet
247+tweetWithExtra = Tweet { user = "bitemyapp"
248+ , postDate = UTCTime
249+ (ModifiedJulianDay 55000)
250+ (secondsToDiffTime 10)
251+ , message = "Use haskell!"
252+ , age = 10000
253+ , location = Location 40.12 (-71.34)
254+ , extra = Just "blah blah" }
255+
256+newAge :: Int
257+newAge = 31337
258+
259+newUser :: Text
260+newUser = "someotherapp"
261+
262+tweetPatch :: Value
263+tweetPatch =
264+ object [ "age" .= newAge
265+ , "user" .= newUser
266+ ]
267+
268+patchedTweet :: Tweet
269+patchedTweet = exampleTweet{age = newAge, user = newUser}
270+
271+otherTweet :: Tweet
272+otherTweet = Tweet { user = "notmyapp"
273+ , postDate = UTCTime
274+ (ModifiedJulianDay 55000)
275+ (secondsToDiffTime 11)
276+ , message = "Use haskell!"
277+ , age = 1000
278+ , location = Location 40.12 (-71.34)
279+ , extra = Nothing }
280+
281+resetIndex :: BH IO ()
282+resetIndex = do
283+ _ <- deleteExampleIndex
284+ _ <- createExampleIndex
285+ _ <- putMapping testIndex testMapping TweetMapping
286+ return ()
287+
288+insertData :: BH IO Reply
289+insertData = do
290+ resetIndex
291+ insertData' defaultIndexDocumentSettings
292+
293+insertData' :: IndexDocumentSettings -> BH IO Reply
294+insertData' ids = do
295+ r <- indexDocument testIndex testMapping ids exampleTweet (DocId "1")
296+ _ <- refreshIndex testIndex
297+ return r
298+
299+updateData :: BH IO Reply
300+updateData = do
301+ r <- updateDocument testIndex testMapping defaultIndexDocumentSettings tweetPatch (DocId "1")
302+ _ <- refreshIndex testIndex
303+ return r
304+
305+insertOther :: BH IO ()
306+insertOther = do
307+ _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings otherTweet (DocId "2")
308+ _ <- refreshIndex testIndex
309+ return ()
310+
311+insertExtra :: BH IO ()
312+insertExtra = do
313+ _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings tweetWithExtra (DocId "4")
314+ _ <- refreshIndex testIndex
315+ return ()
316+
317+insertWithSpaceInId :: BH IO ()
318+insertWithSpaceInId = do
319+ _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings exampleTweet (DocId "Hello World")
320+ _ <- refreshIndex testIndex
321+ return ()
322+
323+searchTweet :: Search -> BH IO (Either EsError Tweet)
324+searchTweet search = do
325+ result <- searchTweets search
326+ let myTweet :: Either EsError Tweet
327+ myTweet = grabFirst result
328+ return myTweet
329+
330+searchTweets :: Search -> BH IO (Either EsError (SearchResult Tweet))
331+searchTweets search = parseEsResponse =<< searchByIndex testIndex search
332+
333+searchExpectNoResults :: Search -> BH IO ()
334+searchExpectNoResults search = do
335+ result <- searchTweets search
336+ let emptyHits = fmap (hits . searchHits) result
337+ liftIO $
338+ emptyHits `shouldBe` Right []
339+
340+searchExpectAggs :: Search -> BH IO ()
341+searchExpectAggs search = do
342+ reply <- searchByIndex testIndex search
343+ let isEmpty x = return (M.null x)
344+ let result = decode (responseBody reply) :: Maybe (SearchResult Tweet)
345+ liftIO $
346+ (result >>= aggregations >>= isEmpty) `shouldBe` Just False
347+
348+searchValidBucketAgg :: (BucketAggregation a, FromJSON a, Show a) =>
349+ Search -> Text -> (Text -> AggregationResults -> Maybe (Bucket a)) -> BH IO ()
350+searchValidBucketAgg search aggKey extractor = do
351+ reply <- searchByIndex testIndex search
352+ let bucketDocs = docCount . head . buckets
353+ let result = decode (responseBody reply) :: Maybe (SearchResult Tweet)
354+ let count = result >>= aggregations >>= extractor aggKey >>= \x -> return (bucketDocs x)
355+ liftIO $
356+ count `shouldBe` Just 1
357+
358+searchTermsAggHint :: [ExecutionHint] -> BH IO ()
359+searchTermsAggHint hints = do
360+ let terms hint = TermsAgg $ (mkTermsAggregation "user") { termExecutionHint = Just hint }
361+ let search hint = mkAggregateSearch Nothing $ mkAggregations "users" $ terms hint
362+ forM_ hints $ searchExpectAggs . search
363+ forM_ hints (\x -> searchValidBucketAgg (search x) "users" toTerms)
364+
365+searchTweetHighlight :: Search -> BH IO (Either EsError (Maybe HitHighlight))
366+searchTweetHighlight search = do
367+ result <- searchTweets search
368+ let myHighlight = fmap (hitHighlight . head . hits . searchHits) result
369+ return myHighlight
370+
371+searchExpectSource :: Source -> Either EsError Value -> BH IO ()
372+searchExpectSource src expected = do
373+ _ <- insertData
374+ let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
375+ let search = (mkSearch (Just query) Nothing) { source = Just src }
376+ reply <- searchByIndex testIndex search
377+ result <- parseEsResponse reply
378+ let value = grabFirst result
379+ liftIO $
380+ value `shouldBe` expected
381+
382+withSnapshotRepo
383+ :: ( MonadMask m
384+ , MonadBH m
385+ )
386+ => SnapshotRepoName
387+ -> (GenericSnapshotRepo -> m a)
388+ -> m a
389+withSnapshotRepo srn@(SnapshotRepoName n) f = do
390+ repoPaths <- liftIO getRepoPaths
391+ -- we'll use the first repo path if available, otherwise system temp
392+ -- dir. Note that this will fail on ES > 1.6, so be sure you use
393+ -- @when' canSnapshot@.
394+ case repoPaths of
395+ (firstRepoPath:_) -> withTempDirectory firstRepoPath (T.unpack n) $ \dir -> bracket (alloc dir) free f
396+ [] -> withSystemTempDirectory (T.unpack n) $ \dir -> bracket (alloc dir) free f
397+ where
398+ alloc dir = do
399+ liftIO (setFileMode dir mode)
400+ let repo = FsSnapshotRepo srn "bloodhound-tests-backups" True Nothing Nothing Nothing
401+ resp <- updateSnapshotRepo defaultSnapshotRepoUpdateSettings repo
402+ liftIO (validateStatus resp 200)
403+ return (toGSnapshotRepo repo)
404+ mode = ownerModes `unionFileModes` groupModes `unionFileModes` otherModes
405+ free GenericSnapshotRepo {..} = do
406+ resp <- deleteSnapshotRepo gSnapshotRepoName
407+ liftIO (validateStatus resp 200)
408+
409+
410+withSnapshot
411+ :: ( MonadMask m
412+ , MonadBH m
413+ )
414+ => SnapshotRepoName
415+ -> SnapshotName
416+ -> m a
417+ -> m a
418+withSnapshot srn sn = bracket_ alloc free
419+ where
420+ alloc = do
421+ resp <- createSnapshot srn sn createSettings
422+ liftIO (validateStatus resp 200)
423+ -- We'll make this synchronous for testing purposes
424+ createSettings = defaultSnapshotCreateSettings { snapWaitForCompletion = True
425+ , snapIndices = Just (IndexList (testIndex :| []))
426+ -- We don't actually need to back up any data
427+ }
428+ free = do
429+ deleteSnapshot srn sn
430+
431+
432+
433+data BulkTest = BulkTest { name :: Text } deriving (Eq, Generic, Show)
434+instance FromJSON BulkTest where
435+ parseJSON = genericParseJSON defaultOptions
436+instance ToJSON BulkTest where
437+ toJSON = genericToJSON defaultOptions
438+
439+class GApproxEq f where
440+ gApproxEq :: f a -> f a -> Bool
441+
442+-- | Unit type
443+instance GApproxEq U1 where
444+ gApproxEq U1 U1 = True
445+
446+-- | Sum type, ensure same constructors, recurse
447+instance (GApproxEq a, GApproxEq b) => GApproxEq (a :+: b) where
448+ gApproxEq (L1 a) (L1 b) = gApproxEq a b
449+ gApproxEq (R1 a) (R1 b) = gApproxEq a b
450+ gApproxEq _ _ = False
451+
452+-- | Product type, ensure each field is approx eq
453+instance (GApproxEq a, GApproxEq b) => GApproxEq (a :*: b) where
454+ gApproxEq (a1 :*: b1) (a2 :*: b2) = gApproxEq a1 a2 && gApproxEq b1 b2
455+
456+-- | Value type, actually check the values for approx equality
457+instance (ApproxEq a) => GApproxEq (K1 i a) where
458+ gApproxEq (K1 a) (K1 b) = a =~ b
459+
460+instance (GApproxEq f) => GApproxEq (M1 i t f) where
461+ gApproxEq (M1 a) (M1 b) = gApproxEq a b
462+
463+-- | Typeclass for "equal where it matters". Use this to specify
464+-- less-strict equivalence for things such as lists that can wind up
465+-- in an unpredictable order
466+class ApproxEq a where
467+ (=~) :: a -> a -> Bool
468+ default (=~) :: (Generic a, GApproxEq (Rep a)) => a -> a -> Bool
469+ a =~ b = gApproxEq (G.from a) (G.from b)
470+
471+ showApproxEq :: a -> String
472+ default showApproxEq :: (Show a) => a -> String
473+ showApproxEq = show
474+
475+instance ApproxEq NominalDiffTime where (=~) = (==)
476+instance ApproxEq UTCTime where (=~) = (==)
477+instance ApproxEq Text where (=~) = (==)
478+instance ApproxEq Bool where (=~) = (==)
479+instance ApproxEq Int where (=~) = (==)
480+instance ApproxEq Double where (=~) = (==)
481+instance (ApproxEq a, Show a) => ApproxEq (NonEmpty a)
482+instance (ApproxEq a, Show a) => ApproxEq (Maybe a)
483+instance ApproxEq GeoPoint
484+instance ApproxEq Regexp
485+instance ApproxEq RangeValue
486+instance ApproxEq LessThan
487+instance ApproxEq LessThanEq
488+instance ApproxEq LessThanD
489+instance ApproxEq LessThanEqD
490+instance ApproxEq GreaterThan
491+instance ApproxEq GreaterThanEq
492+instance ApproxEq GreaterThanD
493+instance ApproxEq GreaterThanEqD
494+instance ApproxEq MinimumMatchHighLow
495+instance ApproxEq RegexpFlag
496+instance ApproxEq RegexpFlags
497+instance ApproxEq NullValue
498+instance ApproxEq Version
499+instance ApproxEq VersionNumber
500+instance ApproxEq DistanceRange
501+instance ApproxEq IndexName
502+instance ApproxEq MappingName
503+instance ApproxEq DocId
504+instance ApproxEq IndexAliasRouting
505+instance ApproxEq RoutingValue
506+instance ApproxEq ShardCount
507+instance ApproxEq ReplicaCount
508+instance ApproxEq TemplateName
509+instance ApproxEq TemplatePattern
510+instance ApproxEq QueryString
511+instance ApproxEq FieldName
512+instance ApproxEq CacheName
513+instance ApproxEq CacheKey
514+instance ApproxEq Existence
515+instance ApproxEq CutoffFrequency
516+instance ApproxEq Analyzer
517+instance ApproxEq Lenient
518+instance ApproxEq Tiebreaker
519+instance ApproxEq Boost
520+instance ApproxEq BoostTerms
521+instance ApproxEq MaxExpansions
522+instance ApproxEq MinimumMatch
523+instance ApproxEq DisableCoord
524+instance ApproxEq IgnoreTermFrequency
525+instance ApproxEq MinimumTermFrequency
526+instance ApproxEq MaxQueryTerms
527+instance ApproxEq Fuzziness
528+instance ApproxEq PrefixLength
529+instance ApproxEq TypeName
530+instance ApproxEq PercentMatch
531+instance ApproxEq StopWord
532+instance ApproxEq QueryPath
533+instance ApproxEq AllowLeadingWildcard
534+instance ApproxEq LowercaseExpanded
535+instance ApproxEq EnablePositionIncrements
536+instance ApproxEq AnalyzeWildcard
537+instance ApproxEq GeneratePhraseQueries
538+instance ApproxEq Locale
539+instance ApproxEq MaxWordLength
540+instance ApproxEq MinWordLength
541+instance ApproxEq PhraseSlop
542+instance ApproxEq MinDocFrequency
543+instance ApproxEq MaxDocFrequency
544+instance ApproxEq Filter
545+instance ApproxEq Query
546+instance ApproxEq SimpleQueryStringQuery
547+instance ApproxEq FieldOrFields
548+instance ApproxEq SimpleQueryFlag
549+instance ApproxEq RegexpQuery
550+instance ApproxEq QueryStringQuery
551+instance ApproxEq RangeQuery
552+instance ApproxEq PrefixQuery
553+instance ApproxEq NestedQuery
554+instance ApproxEq MoreLikeThisFieldQuery
555+instance ApproxEq MoreLikeThisQuery
556+instance ApproxEq IndicesQuery
557+instance ApproxEq HasParentQuery
558+instance ApproxEq HasChildQuery
559+instance ApproxEq FuzzyQuery
560+instance ApproxEq FuzzyLikeFieldQuery
561+instance ApproxEq FuzzyLikeThisQuery
562+instance ApproxEq DisMaxQuery
563+instance ApproxEq CommonTermsQuery
564+instance ApproxEq CommonMinimumMatch
565+instance ApproxEq BoostingQuery
566+instance ApproxEq BoolQuery
567+instance ApproxEq MatchQuery
568+instance ApproxEq MultiMatchQueryType
569+instance ApproxEq BooleanOperator
570+instance ApproxEq ZeroTermsQuery
571+instance ApproxEq MatchQueryType
572+instance ApproxEq AliasRouting
573+instance ApproxEq IndexAliasCreate
574+instance ApproxEq SearchAliasRouting
575+instance ApproxEq ScoreType
576+instance ApproxEq Distance
577+instance ApproxEq DistanceUnit
578+instance ApproxEq DistanceType
579+instance ApproxEq OptimizeBbox
580+instance ApproxEq GeoBoundingBoxConstraint
581+instance ApproxEq GeoFilterType
582+instance ApproxEq GeoBoundingBox
583+instance ApproxEq LatLon
584+instance ApproxEq RangeExecution
585+instance ApproxEq FSType
586+instance ApproxEq CompoundFormat
587+instance ApproxEq InitialShardCount
588+instance ApproxEq Bytes
589+instance ApproxEq ReplicaBounds
590+instance ApproxEq Term
591+instance ApproxEq BoolMatch
592+instance ApproxEq MultiMatchQuery
593+instance ApproxEq IndexSettings
594+instance ApproxEq AllocationPolicy
595+instance ApproxEq Char where
596+ (=~) = (==)
597+instance ApproxEq Vers.Version where
598+ (=~) = (==)
599+instance (ApproxEq a, Show a) => ApproxEq [a] where
600+ as =~ bs = and (zipWith (=~) as bs)
601+instance (ApproxEq l, Show l, ApproxEq r, Show r) => ApproxEq (Either l r) where
602+ Left a =~ Left b = a =~ b
603+ Right a =~ Right b = a =~ b
604+ _ =~ _ = False
605+ showApproxEq (Left x) = "Left " <> showApproxEq x
606+ showApproxEq (Right x) = "Right " <> showApproxEq x
607+instance ApproxEq NodeAttrFilter
608+instance ApproxEq NodeAttrName
609+instance ApproxEq BuildHash
610+
611+-- | Due to the way nodeattrfilters get serialized here, they may come
612+-- out in a different order, but they are morally equivalent
613+instance ApproxEq UpdatableIndexSetting where
614+ RoutingAllocationInclude a =~ RoutingAllocationInclude b =
615+ NE.sort a =~ NE.sort b
616+ RoutingAllocationExclude a =~ RoutingAllocationExclude b =
617+ NE.sort a =~ NE.sort b
618+ RoutingAllocationRequire a =~ RoutingAllocationRequire b =
619+ NE.sort a =~ NE.sort b
620+ a =~ b = a == b
621+ showApproxEq (RoutingAllocationInclude xs) = show (RoutingAllocationInclude (NE.sort xs))
622+ showApproxEq (RoutingAllocationExclude xs) = show (RoutingAllocationExclude (NE.sort xs))
623+ showApproxEq (RoutingAllocationRequire xs) = show (RoutingAllocationRequire (NE.sort xs))
624+ showApproxEq x = show x
625+
626+
627+noDuplicates :: Eq a => [a] -> Bool
628+noDuplicates xs = nub xs == xs
629+
630+instance Arbitrary NominalDiffTime where
631+ arbitrary = fromInteger <$> arbitrary
632+
633+#if !MIN_VERSION_QuickCheck(2,8,0)
634+instance (Arbitrary k, Ord k, Arbitrary v) => Arbitrary (M.Map k v) where
635+ arbitrary = M.fromList <$> arbitrary
636+#endif
637+
638+instance Arbitrary Text where
639+ arbitrary = T.pack <$> arbitrary
640+
641+instance Arbitrary UTCTime where
642+ arbitrary = UTCTime
643+ <$> arbitrary
644+ <*> (fromRational . toRational <$> choose (0::Double, 86400))
645+
646+instance Arbitrary Day where
647+ arbitrary = ModifiedJulianDay <$> (2000 +) <$> arbitrary
648+ shrink = (ModifiedJulianDay <$>) . shrink . toModifiedJulianDay
649+
650+#if !MIN_VERSION_QuickCheck(2,9,0)
651+instance Arbitrary a => Arbitrary (NonEmpty a) where
652+ arbitrary = liftA2 (:|) arbitrary arbitrary
653+#endif
654+
655+arbitraryScore :: Gen Score
656+arbitraryScore = fmap getPositive <$> arbitrary
657+
658+instance (Arbitrary a, Typeable a) => Arbitrary (Hit a) where
659+ arbitrary = Hit <$> arbitrary
660+ <*> arbitrary
661+ <*> arbitrary
662+ <*> arbitraryScore
663+ <*> arbitrary
664+ <*> arbitrary
665+ shrink = genericShrink
666+
667+
668+instance (Arbitrary a, Typeable a) => Arbitrary (SearchHits a) where
669+ arbitrary = reduceSize $ do
670+ tot <- getPositive <$> arbitrary
671+ score <- arbitraryScore
672+ hs <- arbitrary
673+ return $ SearchHits tot score hs
674+ shrink = genericShrink
675+
676+reduceSize :: Gen a -> Gen a
677+reduceSize f = sized $ \n -> resize (n `div` 2) f
678+
679+getSource :: EsResult a -> Maybe a
680+getSource = fmap _source . foundResult
681+
682+grabFirst :: Either EsError (SearchResult a) -> Either EsError a
683+grabFirst r =
684+ case fmap (hitSource . head . hits . searchHits) r of
685+ (Left e) -> Left e
686+ (Right Nothing) -> Left (EsError 500 "Source was missing")
687+ (Right (Just x)) -> Right x
688+
689+-------------------------------------------------------------------------------
690+arbitraryAlphaNum :: Gen Char
691+arbitraryAlphaNum = oneof [choose ('a', 'z')
692+ ,choose ('A','Z')
693+ , choose ('0', '9')]
694+
695+instance Arbitrary RoutingValue where
696+ arbitrary = RoutingValue . T.pack <$> listOf1 arbitraryAlphaNum
697+
698+instance Arbitrary AliasRouting where
699+ arbitrary = oneof [allAlias
700+ ,one
701+ ,theOther
702+ ,both]
703+ where one = GranularAliasRouting
704+ <$> (Just <$> arbitrary)
705+ <*> pure Nothing
706+ theOther = GranularAliasRouting Nothing
707+ <$> (Just <$> arbitrary)
708+ both = GranularAliasRouting
709+ <$> (Just <$> arbitrary)
710+ <*> (Just <$> arbitrary)
711+ allAlias = AllAliasRouting <$> arbitrary
712+ shrink = genericShrink
713+
714+
715+instance Arbitrary FieldName where
716+ arbitrary = FieldName . T.pack <$> listOf1 arbitraryAlphaNum
717+ shrink = genericShrink
718+
719+
720+instance Arbitrary RegexpFlags where
721+ arbitrary = oneof [ pure AllRegexpFlags
722+ , pure NoRegexpFlags
723+ , SomeRegexpFlags <$> genUniqueFlags
724+ ]
725+ where genUniqueFlags = NE.fromList . nub <$> listOf1 arbitrary
726+ shrink = genericShrink
727+
728+
729+instance Arbitrary IndexAliasCreate where
730+ arbitrary = IndexAliasCreate <$> arbitrary <*> reduceSize arbitrary
731+ shrink = genericShrink
732+
733+instance Arbitrary Query where
734+ arbitrary = reduceSize $ oneof [ TermQuery <$> arbitrary <*> arbitrary
735+ , TermsQuery <$> arbitrary <*> arbitrary
736+ , QueryMatchQuery <$> arbitrary
737+ , QueryMultiMatchQuery <$> arbitrary
738+ , QueryBoolQuery <$> arbitrary
739+ , QueryBoostingQuery <$> arbitrary
740+ , QueryCommonTermsQuery <$> arbitrary
741+ , ConstantScoreQuery <$> arbitrary <*> arbitrary
742+ , QueryDisMaxQuery <$> arbitrary
743+ , QueryFuzzyLikeThisQuery <$> arbitrary
744+ , QueryFuzzyLikeFieldQuery <$> arbitrary
745+ , QueryFuzzyQuery <$> arbitrary
746+ , QueryHasChildQuery <$> arbitrary
747+ , QueryHasParentQuery <$> arbitrary
748+ , IdsQuery <$> arbitrary <*> arbitrary
749+ , QueryIndicesQuery <$> arbitrary
750+ , MatchAllQuery <$> arbitrary
751+ , QueryMoreLikeThisQuery <$> arbitrary
752+ , QueryMoreLikeThisFieldQuery <$> arbitrary
753+ , QueryNestedQuery <$> arbitrary
754+ , QueryPrefixQuery <$> arbitrary
755+ , QueryQueryStringQuery <$> arbitrary
756+ , QuerySimpleQueryStringQuery <$> arbitrary
757+ , QueryRangeQuery <$> arbitrary
758+ , QueryRegexpQuery <$> arbitrary
759+ ]
760+ shrink = genericShrink
761+
762+instance Arbitrary Filter where
763+ arbitrary = Filter <$> arbitrary
764+ shrink = genericShrink
765+
766+instance Arbitrary ReplicaBounds where
767+ arbitrary = oneof [ replicasBounded
768+ , replicasLowerBounded
769+ , pure ReplicasUnbounded
770+ ]
771+ where replicasBounded = do Positive a <- arbitrary
772+ Positive b <- arbitrary
773+ return (ReplicasBounded a b)
774+ replicasLowerBounded = do Positive a <- arbitrary
775+ return (ReplicasLowerBounded a)
776+
777+instance Arbitrary NodeAttrName where
778+ arbitrary = NodeAttrName . T.pack <$> listOf1 arbitraryAlphaNum
779+
780+
781+instance Arbitrary NodeAttrFilter where
782+ arbitrary = do
783+ n <- arbitrary
784+ s:ss <- listOf1 (listOf1 arbitraryAlphaNum)
785+ let ts = T.pack <$> s :| ss
786+ return (NodeAttrFilter n ts)
787+ shrink = genericShrink
788+
789+instance Arbitrary VersionNumber where
790+ arbitrary = mk . fmap getPositive . getNonEmpty <$> arbitrary
791+ where
792+ mk versions = VersionNumber (Vers.Version versions [])
793+
794+instance Arbitrary IndexName where arbitrary = sopArbitrary; shrink = genericShrink
795+instance Arbitrary MappingName where arbitrary = sopArbitrary; shrink = genericShrink
796+instance Arbitrary DocId where arbitrary = sopArbitrary; shrink = genericShrink
797+instance Arbitrary Version where arbitrary = sopArbitrary; shrink = genericShrink
798+instance Arbitrary BuildHash where arbitrary = sopArbitrary; shrink = genericShrink
799+instance Arbitrary IndexAliasRouting where arbitrary = sopArbitrary; shrink = genericShrink
800+instance Arbitrary ShardCount where arbitrary = sopArbitrary; shrink = genericShrink
801+instance Arbitrary ReplicaCount where arbitrary = sopArbitrary; shrink = genericShrink
802+instance Arbitrary TemplateName where arbitrary = sopArbitrary; shrink = genericShrink
803+instance Arbitrary TemplatePattern where arbitrary = sopArbitrary; shrink = genericShrink
804+instance Arbitrary QueryString where arbitrary = sopArbitrary; shrink = genericShrink
805+instance Arbitrary CacheName where arbitrary = sopArbitrary; shrink = genericShrink
806+instance Arbitrary CacheKey where arbitrary = sopArbitrary; shrink = genericShrink
807+instance Arbitrary Existence where arbitrary = sopArbitrary; shrink = genericShrink
808+instance Arbitrary CutoffFrequency where arbitrary = sopArbitrary; shrink = genericShrink
809+instance Arbitrary Analyzer where arbitrary = sopArbitrary; shrink = genericShrink
810+instance Arbitrary MaxExpansions where arbitrary = sopArbitrary; shrink = genericShrink
811+instance Arbitrary Lenient where arbitrary = sopArbitrary; shrink = genericShrink
812+instance Arbitrary Tiebreaker where arbitrary = sopArbitrary; shrink = genericShrink
813+instance Arbitrary Boost where arbitrary = sopArbitrary; shrink = genericShrink
814+instance Arbitrary BoostTerms where arbitrary = sopArbitrary; shrink = genericShrink
815+instance Arbitrary MinimumMatch where arbitrary = sopArbitrary; shrink = genericShrink
816+instance Arbitrary DisableCoord where arbitrary = sopArbitrary; shrink = genericShrink
817+instance Arbitrary IgnoreTermFrequency where arbitrary = sopArbitrary; shrink = genericShrink
818+instance Arbitrary MinimumTermFrequency where arbitrary = sopArbitrary; shrink = genericShrink
819+instance Arbitrary MaxQueryTerms where arbitrary = sopArbitrary; shrink = genericShrink
820+instance Arbitrary Fuzziness where arbitrary = sopArbitrary; shrink = genericShrink
821+instance Arbitrary PrefixLength where arbitrary = sopArbitrary; shrink = genericShrink
822+instance Arbitrary TypeName where arbitrary = sopArbitrary; shrink = genericShrink
823+instance Arbitrary PercentMatch where arbitrary = sopArbitrary; shrink = genericShrink
824+instance Arbitrary StopWord where arbitrary = sopArbitrary; shrink = genericShrink
825+instance Arbitrary QueryPath where arbitrary = sopArbitrary; shrink = genericShrink
826+instance Arbitrary AllowLeadingWildcard where arbitrary = sopArbitrary; shrink = genericShrink
827+instance Arbitrary LowercaseExpanded where arbitrary = sopArbitrary; shrink = genericShrink
828+instance Arbitrary EnablePositionIncrements where arbitrary = sopArbitrary; shrink = genericShrink
829+instance Arbitrary AnalyzeWildcard where arbitrary = sopArbitrary; shrink = genericShrink
830+instance Arbitrary GeneratePhraseQueries where arbitrary = sopArbitrary; shrink = genericShrink
831+instance Arbitrary Locale where arbitrary = sopArbitrary; shrink = genericShrink
832+instance Arbitrary MaxWordLength where arbitrary = sopArbitrary; shrink = genericShrink
833+instance Arbitrary MinWordLength where arbitrary = sopArbitrary; shrink = genericShrink
834+instance Arbitrary PhraseSlop where arbitrary = sopArbitrary; shrink = genericShrink
835+instance Arbitrary MinDocFrequency where arbitrary = sopArbitrary; shrink = genericShrink
836+instance Arbitrary MaxDocFrequency where arbitrary = sopArbitrary; shrink = genericShrink
837+instance Arbitrary Regexp where arbitrary = sopArbitrary; shrink = genericShrink
838+instance Arbitrary SimpleQueryStringQuery where arbitrary = sopArbitrary; shrink = genericShrink
839+instance Arbitrary FieldOrFields where arbitrary = sopArbitrary; shrink = genericShrink
840+instance Arbitrary SimpleQueryFlag where arbitrary = sopArbitrary; shrink = genericShrink
841+instance Arbitrary RegexpQuery where arbitrary = sopArbitrary; shrink = genericShrink
842+instance Arbitrary QueryStringQuery where arbitrary = sopArbitrary; shrink = genericShrink
843+instance Arbitrary RangeQuery where arbitrary = sopArbitrary; shrink = genericShrink
844+instance Arbitrary RangeValue where arbitrary = sopArbitrary; shrink = genericShrink
845+instance Arbitrary PrefixQuery where arbitrary = sopArbitrary; shrink = genericShrink
846+instance Arbitrary NestedQuery where arbitrary = sopArbitrary; shrink = genericShrink
847+instance Arbitrary MoreLikeThisFieldQuery where arbitrary = sopArbitrary; shrink = genericShrink
848+instance Arbitrary MoreLikeThisQuery where arbitrary = sopArbitrary; shrink = genericShrink
849+instance Arbitrary IndicesQuery where arbitrary = sopArbitrary; shrink = genericShrink
850+instance Arbitrary HasParentQuery where arbitrary = sopArbitrary; shrink = genericShrink
851+instance Arbitrary HasChildQuery where arbitrary = sopArbitrary; shrink = genericShrink
852+instance Arbitrary FuzzyQuery where arbitrary = sopArbitrary; shrink = genericShrink
853+instance Arbitrary FuzzyLikeFieldQuery where arbitrary = sopArbitrary; shrink = genericShrink
854+instance Arbitrary FuzzyLikeThisQuery where arbitrary = sopArbitrary; shrink = genericShrink
855+instance Arbitrary DisMaxQuery where arbitrary = sopArbitrary; shrink = genericShrink
856+instance Arbitrary CommonTermsQuery where arbitrary = sopArbitrary; shrink = genericShrink
857+instance Arbitrary DistanceRange where arbitrary = sopArbitrary; shrink = genericShrink
858+instance Arbitrary MultiMatchQuery where arbitrary = sopArbitrary; shrink = genericShrink
859+instance Arbitrary LessThanD where arbitrary = sopArbitrary; shrink = genericShrink
860+instance Arbitrary LessThanEqD where arbitrary = sopArbitrary; shrink = genericShrink
861+instance Arbitrary GreaterThanD where arbitrary = sopArbitrary; shrink = genericShrink
862+instance Arbitrary GreaterThanEqD where arbitrary = sopArbitrary; shrink = genericShrink
863+instance Arbitrary LessThan where arbitrary = sopArbitrary; shrink = genericShrink
864+instance Arbitrary LessThanEq where arbitrary = sopArbitrary; shrink = genericShrink
865+instance Arbitrary GreaterThan where arbitrary = sopArbitrary; shrink = genericShrink
866+instance Arbitrary GreaterThanEq where arbitrary = sopArbitrary; shrink = genericShrink
867+instance Arbitrary GeoPoint where arbitrary = sopArbitrary; shrink = genericShrink
868+instance Arbitrary NullValue where arbitrary = sopArbitrary; shrink = genericShrink
869+instance Arbitrary MinimumMatchHighLow where arbitrary = sopArbitrary; shrink = genericShrink
870+instance Arbitrary CommonMinimumMatch where arbitrary = sopArbitrary; shrink = genericShrink
871+instance Arbitrary BoostingQuery where arbitrary = sopArbitrary; shrink = genericShrink
872+instance Arbitrary BoolQuery where arbitrary = sopArbitrary; shrink = genericShrink
873+instance Arbitrary MatchQuery where arbitrary = sopArbitrary; shrink = genericShrink
874+instance Arbitrary MultiMatchQueryType where arbitrary = sopArbitrary; shrink = genericShrink
875+instance Arbitrary BooleanOperator where arbitrary = sopArbitrary; shrink = genericShrink
876+instance Arbitrary ZeroTermsQuery where arbitrary = sopArbitrary; shrink = genericShrink
877+instance Arbitrary MatchQueryType where arbitrary = sopArbitrary; shrink = genericShrink
878+instance Arbitrary SearchAliasRouting where arbitrary = sopArbitrary; shrink = genericShrink
879+instance Arbitrary ScoreType where arbitrary = sopArbitrary; shrink = genericShrink
880+instance Arbitrary Distance where arbitrary = sopArbitrary; shrink = genericShrink
881+instance Arbitrary DistanceUnit where arbitrary = sopArbitrary; shrink = genericShrink
882+instance Arbitrary DistanceType where arbitrary = sopArbitrary; shrink = genericShrink
883+instance Arbitrary OptimizeBbox where arbitrary = sopArbitrary; shrink = genericShrink
884+instance Arbitrary GeoBoundingBoxConstraint where arbitrary = sopArbitrary; shrink = genericShrink
885+instance Arbitrary GeoFilterType where arbitrary = sopArbitrary; shrink = genericShrink
886+instance Arbitrary GeoBoundingBox where arbitrary = sopArbitrary; shrink = genericShrink
887+instance Arbitrary LatLon where arbitrary = sopArbitrary; shrink = genericShrink
888+instance Arbitrary RangeExecution where arbitrary = sopArbitrary; shrink = genericShrink
889+instance Arbitrary RegexpFlag where arbitrary = sopArbitrary; shrink = genericShrink
890+instance Arbitrary BoolMatch where arbitrary = sopArbitrary; shrink = genericShrink
891+instance Arbitrary Term where arbitrary = sopArbitrary; shrink = genericShrink
892+instance Arbitrary IndexSettings where arbitrary = sopArbitrary; shrink = genericShrink
893+instance Arbitrary UpdatableIndexSetting where arbitrary = sopArbitrary; shrink = genericShrink
894+instance Arbitrary Bytes where arbitrary = sopArbitrary; shrink = genericShrink
895+instance Arbitrary AllocationPolicy where arbitrary = sopArbitrary; shrink = genericShrink
896+instance Arbitrary InitialShardCount where arbitrary = sopArbitrary; shrink = genericShrink
897+instance Arbitrary FSType where arbitrary = sopArbitrary; shrink = genericShrink
898+instance Arbitrary CompoundFormat where arbitrary = sopArbitrary; shrink = genericShrink
899+instance Arbitrary FsSnapshotRepo where arbitrary = sopArbitrary; shrink = genericShrink
900+instance Arbitrary SnapshotRepoName where arbitrary = sopArbitrary; shrink = genericShrink
901+
902+newtype UpdatableIndexSetting' = UpdatableIndexSetting' UpdatableIndexSetting
903+ deriving (Show, Eq, ToJSON, FromJSON, ApproxEq, Typeable)
904+
905+instance Arbitrary UpdatableIndexSetting' where
906+ arbitrary = do
907+ settings <- arbitrary
908+ return $ UpdatableIndexSetting' $ case settings of
909+ RoutingAllocationInclude xs -> RoutingAllocationInclude (dropDuplicateAttrNames xs)
910+ RoutingAllocationExclude xs -> RoutingAllocationExclude (dropDuplicateAttrNames xs)
911+ RoutingAllocationRequire xs -> RoutingAllocationRequire (dropDuplicateAttrNames xs)
912+ x -> x
913+ where
914+ dropDuplicateAttrNames = NE.fromList . L.nubBy sameAttrName . NE.toList
915+ sameAttrName a b = nodeAttrFilterName a == nodeAttrFilterName b
916+
917+main :: IO ()
918+main = hspec $ do
919+
920+ describe "index create/delete API" $ do
921+ it "creates and then deletes the requested index" $ withTestEnv $ do
922+ -- priming state.
923+ _ <- deleteExampleIndex
924+ resp <- createExampleIndex
925+ deleteResp <- deleteExampleIndex
926+ liftIO $ do
927+ validateStatus resp 200
928+ validateStatus deleteResp 200
929+
930+ describe "error parsing" $ do
931+ it "can parse EsErrors for < 2.0" $ when' (atmost es16) $ withTestEnv $ do
932+ res <- getDocument (IndexName "bogus") (MappingName "also_bogus") (DocId "bogus_as_well")
933+ let errorResp = eitherDecode (responseBody res)
934+ liftIO (errorResp `shouldBe` Right (EsError 404 "IndexMissingException[[bogus] missing]"))
935+
936+ it "can parse EsErrors for >= 2.0" $ when' (atleast es20) $ withTestEnv $ do
937+ res <- getDocument (IndexName "bogus") (MappingName "also_bogus") (DocId "bogus_as_well")
938+ let errorResp = eitherDecode (responseBody res)
939+ liftIO (errorResp `shouldBe` Right (EsError 404 "no such index"))
940+
941+ describe "document API" $ do
942+ it "indexes, updates, gets, and then deletes the generated document" $ withTestEnv $ do
943+ _ <- insertData
944+ _ <- updateData
945+ docInserted <- getDocument testIndex testMapping (DocId "1")
946+ let newTweet = eitherDecode
947+ (responseBody docInserted) :: Either String (EsResult Tweet)
948+ liftIO $ (fmap getSource newTweet `shouldBe` Right (Just patchedTweet))
949+
950+ it "indexes, gets, and then deletes the generated document with a DocId containing a space" $ withTestEnv $ do
951+ _ <- insertWithSpaceInId
952+ docInserted <- getDocument testIndex testMapping (DocId "Hello World")
953+ let newTweet = eitherDecode
954+ (responseBody docInserted) :: Either String (EsResult Tweet)
955+ liftIO $ (fmap getSource newTweet `shouldBe` Right (Just exampleTweet))
956+
957+ it "produces a parseable result when looking up a bogus document" $ withTestEnv $ do
958+ doc <- getDocument testIndex testMapping (DocId "bogus")
959+ let noTweet = eitherDecode
960+ (responseBody doc) :: Either String (EsResult Tweet)
961+ liftIO $ fmap foundResult noTweet `shouldBe` Right Nothing
962+
963+ it "can use optimistic concurrency control" $ withTestEnv $ do
964+ let ev = ExternalDocVersion minBound
965+ let cfg = defaultIndexDocumentSettings { idsVersionControl = ExternalGT ev }
966+ resetIndex
967+ res <- insertData' cfg
968+ liftIO $ isCreated res `shouldBe` True
969+ res' <- insertData' cfg
970+ liftIO $ isVersionConflict res' `shouldBe` True
971+
972+ it "indexes two documents in a parent/child relationship and checks that the child exists" $ withTestEnv $ do
973+ resetIndex
974+ _ <- putMapping testIndex (MappingName "child") ChildMapping
975+ _ <- putMapping testIndex (MappingName "parent") ParentMapping
976+ _ <- indexDocument testIndex (MappingName "parent") defaultIndexDocumentSettings exampleTweet (DocId "1")
977+ let parent = (Just . DocumentParent . DocId) "1"
978+ ids = IndexDocumentSettings NoVersionControl parent
979+ _ <- indexDocument testIndex (MappingName "child") ids otherTweet (DocId "2")
980+ _ <- refreshIndex testIndex
981+ exists <- documentExists testIndex (MappingName "child") parent (DocId "2")
982+ liftIO $ exists `shouldBe` True
983+
984+ describe "template API" $ do
985+ it "can create a template" $ withTestEnv $ do
986+ let idxTpl = IndexTemplate (TemplatePattern "tweet-*") (Just (IndexSettings (ShardCount 1) (ReplicaCount 1))) [toJSON TweetMapping]
987+ resp <- putTemplate idxTpl (TemplateName "tweet-tpl")
988+ liftIO $ validateStatus resp 200
989+
990+ it "can detect if a template exists" $ withTestEnv $ do
991+ exists <- templateExists (TemplateName "tweet-tpl")
992+ liftIO $ exists `shouldBe` True
993+
994+ it "can delete a template" $ withTestEnv $ do
995+ resp <- deleteTemplate (TemplateName "tweet-tpl")
996+ liftIO $ validateStatus resp 200
997+
998+ it "can detect if a template doesn't exist" $ withTestEnv $ do
999+ exists <- templateExists (TemplateName "tweet-tpl")
1000+ liftIO $ exists `shouldBe` False
1001+
1002+ describe "bulk API" $ do
1003+ it "inserts all documents we request" $ withTestEnv $ do
1004+ _ <- insertData
1005+ let firstTest = BulkTest "blah"
1006+ let secondTest = BulkTest "bloo"
1007+ let firstDoc = BulkIndex testIndex
1008+ testMapping (DocId "2") (toJSON firstTest)
1009+ let secondDoc = BulkCreate testIndex
1010+ testMapping (DocId "3") (toJSON secondTest)
1011+ let stream = V.fromList [firstDoc, secondDoc]
1012+ _ <- bulk stream
1013+ _ <- refreshIndex testIndex
1014+ fDoc <- getDocument testIndex testMapping (DocId "2")
1015+ sDoc <- getDocument testIndex testMapping (DocId "3")
1016+ let maybeFirst = eitherDecode $ responseBody fDoc :: Either String (EsResult BulkTest)
1017+ let maybeSecond = eitherDecode $ responseBody sDoc :: Either String (EsResult BulkTest)
1018+ liftIO $ do
1019+ fmap getSource maybeFirst `shouldBe` Right (Just firstTest)
1020+ fmap getSource maybeSecond `shouldBe` Right (Just secondTest)
1021+
1022+
1023+ describe "query API" $ do
1024+ it "returns document for term query and identity filter" $ withTestEnv $ do
1025+ _ <- insertData
1026+ let query = TermQuery (Term "user" "bitemyapp") Nothing
1027+ let filter = Filter $ MatchAllQuery Nothing
1028+ let search = mkSearch (Just query) (Just filter)
1029+ myTweet <- searchTweet search
1030+ liftIO $
1031+ myTweet `shouldBe` Right exampleTweet
1032+
1033+ it "handles constant score queries" $ withTestEnv $ do
1034+ _ <- insertData
1035+ let query = TermsQuery "user" ("bitemyapp" :| [])
1036+ let cfQuery = ConstantScoreQuery query (Boost 1.0)
1037+ let filter = Filter $ MatchAllQuery Nothing
1038+ let search = mkSearch (Just cfQuery) (Just filter)
1039+ myTweet <- searchTweet search
1040+ liftIO $
1041+ myTweet `shouldBe` Right exampleTweet
1042+
1043+ it "returns document for terms query and identity filter" $ withTestEnv $ do
1044+ _ <- insertData
1045+ let query = TermsQuery "user" ("bitemyapp" :| [])
1046+ let filter = Filter $ MatchAllQuery Nothing
1047+ let search = mkSearch (Just query) (Just filter)
1048+ myTweet <- searchTweet search
1049+ liftIO $
1050+ myTweet `shouldBe` Right exampleTweet
1051+
1052+ it "returns document for match query" $ withTestEnv $ do
1053+ _ <- insertData
1054+ let query = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
1055+ let search = mkSearch (Just query) Nothing
1056+ myTweet <- searchTweet search
1057+ liftIO $
1058+ myTweet `shouldBe` Right exampleTweet
1059+
1060+ it "returns document for multi-match query" $ withTestEnv $ do
1061+ _ <- insertData
1062+ let flds = [FieldName "user", FieldName "message"]
1063+ let query = QueryMultiMatchQuery $ mkMultiMatchQuery flds (QueryString "bitemyapp")
1064+ let search = mkSearch (Just query) Nothing
1065+ myTweet <- searchTweet search
1066+ liftIO $
1067+ myTweet `shouldBe` Right exampleTweet
1068+
1069+ it "returns document for bool query" $ withTestEnv $ do
1070+ _ <- insertData
1071+ let innerQuery = QueryMatchQuery $
1072+ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
1073+ let query = QueryBoolQuery $
1074+ mkBoolQuery [innerQuery] [] [] []
1075+ let search = mkSearch (Just query) Nothing
1076+ myTweet <- searchTweet search
1077+ liftIO $
1078+ myTweet `shouldBe` Right exampleTweet
1079+
1080+ it "returns document for boosting query" $ withTestEnv $ do
1081+ _ <- insertData
1082+ let posQuery = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
1083+ let negQuery = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "notmyapp")
1084+ let query = QueryBoostingQuery $ BoostingQuery posQuery negQuery (Boost 0.2)
1085+ let search = mkSearch (Just query) Nothing
1086+ myTweet <- searchTweet search
1087+ liftIO $
1088+ myTweet `shouldBe` Right exampleTweet
1089+
1090+ it "returns document for common terms query" $ withTestEnv $ do
1091+ _ <- insertData
1092+ let query = QueryCommonTermsQuery $
1093+ CommonTermsQuery (FieldName "user")
1094+ (QueryString "bitemyapp")
1095+ (CutoffFrequency 0.0001)
1096+ Or Or Nothing Nothing Nothing Nothing
1097+ let search = mkSearch (Just query) Nothing
1098+ myTweet <- searchTweet search
1099+ liftIO $
1100+ myTweet `shouldBe` Right exampleTweet
1101+
1102+
1103+ describe "sorting" $ do
1104+ it "returns documents in the right order" $ withTestEnv $ do
1105+ _ <- insertData
1106+ _ <- insertOther
1107+ let sortSpec = DefaultSortSpec $ mkSort (FieldName "age") Ascending
1108+ let search = Search Nothing
1109+ Nothing (Just [sortSpec]) Nothing Nothing
1110+ False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing
1111+ result <- searchTweets search
1112+ let myTweet = grabFirst result
1113+ liftIO $
1114+ myTweet `shouldBe` Right otherTweet
1115+
1116+ describe "Aggregation API" $ do
1117+ it "returns term aggregation results" $ withTestEnv $ do
1118+ _ <- insertData
1119+ let terms = TermsAgg $ mkTermsAggregation "user"
1120+ let search = mkAggregateSearch Nothing $ mkAggregations "users" terms
1121+ searchExpectAggs search
1122+ searchValidBucketAgg search "users" toTerms
1123+
1124+ it "return sub-aggregation results" $ withTestEnv $ do
1125+ _ <- insertData
1126+ let subaggs = mkAggregations "age_agg" . TermsAgg $ mkTermsAggregation "age"
1127+ agg = TermsAgg $ (mkTermsAggregation "user") { termAggs = Just subaggs}
1128+ search = mkAggregateSearch Nothing $ mkAggregations "users" agg
1129+ reply <- searchByIndex testIndex search
1130+ let result = decode (responseBody reply) :: Maybe (SearchResult Tweet)
1131+ usersAggResults = result >>= aggregations >>= toTerms "users"
1132+ subAggResults = usersAggResults >>= (listToMaybe . buckets) >>= termsAggs >>= toTerms "age_agg"
1133+ subAddResultsExists = isJust subAggResults
1134+ liftIO $ (subAddResultsExists) `shouldBe` True
1135+
1136+ it "returns cardinality aggregation results" $ withTestEnv $ do
1137+ _ <- insertData
1138+ let cardinality = CardinalityAgg $ mkCardinalityAggregation $ FieldName "user"
1139+ let search = mkAggregateSearch Nothing $ mkAggregations "users" cardinality
1140+ let search' = search { Database.V5.Bloodhound.from = From 0, size = Size 0 }
1141+ searchExpectAggs search'
1142+ let docCountPair k n = (k, object ["value" .= Number n])
1143+ res <- searchTweets search'
1144+ liftIO $
1145+ fmap aggregations res `shouldBe` Right (Just (M.fromList [ docCountPair "users" 1]))
1146+
1147+ it "returns stats aggregation results" $ withTestEnv $ do
1148+ _ <- insertData
1149+ let stats = StatsAgg $ mkStatsAggregation $ FieldName "age"
1150+ let search = mkAggregateSearch Nothing $ mkAggregations "users" stats
1151+ let search' = search { Database.V5.Bloodhound.from = From 0, size = Size 0 }
1152+ searchExpectAggs search'
1153+ let statsAggRes k n = (k, object [ "max" .= Number n
1154+ , "avg" .= Number n
1155+ , "count" .= Number 1
1156+ , "min" .= Number n
1157+ , "sum" .= Number n])
1158+ res <- searchTweets search'
1159+ liftIO $
1160+ fmap aggregations res `shouldBe` Right (Just (M.fromList [ statsAggRes "users" 10000]))
1161+
1162+ it "can give collection hint parameters to term aggregations" $ when' (atleast es13) $ withTestEnv $ do
1163+ _ <- insertData
1164+ let terms = TermsAgg $ (mkTermsAggregation "user") { termCollectMode = Just BreadthFirst }
1165+ let search = mkAggregateSearch Nothing $ mkAggregations "users" terms
1166+ searchExpectAggs search
1167+ searchValidBucketAgg search "users" toTerms
1168+
1169+ -- One of these fails with 1.7.3
1170+ it "can give execution hint parameters to term aggregations" $ when' (atmost es11) $ withTestEnv $ do
1171+ _ <- insertData
1172+ searchTermsAggHint [Map, Ordinals]
1173+
1174+ it "can give execution hint parameters to term aggregations" $ when' (is es12) $ withTestEnv $ do
1175+ _ <- insertData
1176+ searchTermsAggHint [GlobalOrdinals, GlobalOrdinalsHash, GlobalOrdinalsLowCardinality, Map, Ordinals]
1177+
1178+ it "can give execution hint parameters to term aggregations" $ when' (atleast es12) $ withTestEnv $ do
1179+ _ <- insertData
1180+ searchTermsAggHint [GlobalOrdinals, GlobalOrdinalsHash, GlobalOrdinalsLowCardinality, Map]
1181+ -- One of the above.
1182+
1183+ it "can execute value_count aggregations" $ withTestEnv $ do
1184+ _ <- insertData
1185+ _ <- insertOther
1186+ let ags = mkAggregations "user_count" (ValueCountAgg (FieldValueCount (FieldName "user"))) <>
1187+ mkAggregations "bogus_count" (ValueCountAgg (FieldValueCount (FieldName "bogus")))
1188+ let search = mkAggregateSearch Nothing ags
1189+ let docCountPair k n = (k, object ["value" .= Number n])
1190+ res <- searchTweets search
1191+ liftIO $
1192+ fmap aggregations res `shouldBe` Right (Just (M.fromList [ docCountPair "user_count" 2
1193+ , docCountPair "bogus_count" 0
1194+ ]))
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
1223+ 2
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
1230+ 1
1231+ Nothing
1232+ ]
1233+
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
1240+
1241+ it "can execute missing aggregations" $ withTestEnv $ do
1242+ _ <- insertData
1243+ _ <- insertExtra
1244+ let ags = mkAggregations "missing_agg" (MissingAgg (MissingAggregation "extra"))
1245+ let search = mkAggregateSearch Nothing ags
1246+ let docCountPair k n = (k, object ["doc_count" .= Number n])
1247+ res <- searchTweets search
1248+ liftIO $
1249+ fmap aggregations res `shouldBe` Right (Just (M.fromList [docCountPair "missing_agg" 1]))
1250+
1251+ describe "Highlights API" $ do
1252+
1253+ it "returns highlight from query when there should be one" $ withTestEnv $ do
1254+ _ <- insertData
1255+ _ <- insertOther
1256+ let query = QueryMatchQuery $ mkMatchQuery (FieldName "message") (QueryString "haskell")
1257+ let testHighlight = Highlights Nothing [FieldHighlight (FieldName "message") Nothing]
1258+
1259+ let search = mkHighlightSearch (Just query) testHighlight
1260+ myHighlight <- searchTweetHighlight search
1261+ liftIO $
1262+ myHighlight `shouldBe` Right (Just (M.fromList [("message",["Use <em>haskell</em>!"])]))
1263+
1264+ it "doesn't return highlight from a query when it shouldn't" $ withTestEnv $ do
1265+ _ <- insertData
1266+ _ <- insertOther
1267+ let query = QueryMatchQuery $ mkMatchQuery (FieldName "message") (QueryString "haskell")
1268+ let testHighlight = Highlights Nothing [FieldHighlight (FieldName "user") Nothing]
1269+
1270+ let search = mkHighlightSearch (Just query) testHighlight
1271+ myHighlight <- searchTweetHighlight search
1272+ liftIO $
1273+ myHighlight `shouldBe` Right Nothing
1274+
1275+ describe "Source filtering" $ do
1276+
1277+ it "doesn't include source when sources are disabled" $ withTestEnv $ do
1278+ searchExpectSource
1279+ NoSource
1280+ (Left (EsError 500 "Source was missing"))
1281+
1282+ it "includes a source" $ withTestEnv $ do
1283+ searchExpectSource
1284+ (SourcePatterns (PopPattern (Pattern "message")))
1285+ (Right (Object (HM.fromList [("message", String "Use haskell!")])))
1286+
1287+ it "includes sources" $ withTestEnv $ do
1288+ searchExpectSource
1289+ (SourcePatterns (PopPatterns [Pattern "user", Pattern "message"]))
1290+ (Right (Object (HM.fromList [("user",String "bitemyapp"),("message", String "Use haskell!")])))
1291+
1292+ it "includes source patterns" $ withTestEnv $ do
1293+ searchExpectSource
1294+ (SourcePatterns (PopPattern (Pattern "*ge")))
1295+ (Right (Object (HM.fromList [("age", Number 10000),("message", String "Use haskell!")])))
1296+
1297+ it "excludes source patterns" $ withTestEnv $ do
1298+ searchExpectSource
1299+ (SourceIncludeExclude (Include [])
1300+ (Exclude [Pattern "l*", Pattern "*ge", Pattern "postDate", Pattern "extra"]))
1301+ (Right (Object (HM.fromList [("user",String "bitemyapp")])))
1302+
1303+ describe "ToJSON RegexpFlags" $ do
1304+ it "generates the correct JSON for AllRegexpFlags" $
1305+ toJSON AllRegexpFlags `shouldBe` String "ALL"
1306+
1307+ it "generates the correct JSON for NoRegexpFlags" $
1308+ toJSON NoRegexpFlags `shouldBe` String "NONE"
1309+
1310+ it "generates the correct JSON for SomeRegexpFlags" $
1311+ let flags = AnyString :| [ Automaton
1312+ , Complement
1313+ , Empty
1314+ , Intersection
1315+ , Interval ]
1316+ in toJSON (SomeRegexpFlags flags) `shouldBe` String "ANYSTRING|AUTOMATON|COMPLEMENT|EMPTY|INTERSECTION|INTERVAL"
1317+
1318+ prop "removes duplicates from flags" $ \(flags :: RegexpFlags) ->
1319+ let String str = toJSON flags
1320+ flagStrs = T.splitOn "|" str
1321+ in noDuplicates flagStrs
1322+
1323+ describe "omitNulls" $ do
1324+ it "checks that omitNulls drops list elements when it should" $
1325+ let dropped = omitNulls $ [ "test1" .= (toJSON ([] :: [Int]))
1326+ , "test2" .= (toJSON ("some value" :: Text))]
1327+ in dropped `shouldBe` Object (HM.fromList [("test2", String "some value")])
1328+
1329+ it "checks that omitNulls doesn't drop list elements when it shouldn't" $
1330+ let notDropped = omitNulls $ [ "test1" .= (toJSON ([1] :: [Int]))
1331+ , "test2" .= (toJSON ("some value" :: Text))]
1332+ in notDropped `shouldBe` Object (HM.fromList [ ("test1", Array (V.fromList [Number 1.0]))
1333+ , ("test2", String "some value")])
1334+ it "checks that omitNulls drops non list elements when it should" $
1335+ let dropped = omitNulls $ [ "test1" .= (toJSON Null)
1336+ , "test2" .= (toJSON ("some value" :: Text))]
1337+ in dropped `shouldBe` Object (HM.fromList [("test2", String "some value")])
1338+ it "checks that omitNulls doesn't drop non list elements when it shouldn't" $
1339+ let notDropped = omitNulls $ [ "test1" .= (toJSON (1 :: Int))
1340+ , "test2" .= (toJSON ("some value" :: Text))]
1341+ in notDropped `shouldBe` Object (HM.fromList [ ("test1", Number 1.0)
1342+ , ("test2", String "some value")])
1343+ describe "Monoid (SearchHits a)" $ do
1344+ prop "abides the monoid laws" $ eq $
1345+ prop_Monoid (T :: T (SearchHits ()))
1346+
1347+ describe "mkDocVersion" $ do
1348+ prop "can never construct an out of range docVersion" $ \i ->
1349+ let res = mkDocVersion i
1350+ in case res of
1351+ Nothing -> property True
1352+ Just dv -> (dv >= minBound) .&&.
1353+ (dv <= maxBound) .&&.
1354+ docVersionNumber dv === i
1355+
1356+ describe "FsSnapshotRepo" $ do
1357+ prop "SnapshotRepo laws" $ \fsr ->
1358+ fromGSnapshotRepo (toGSnapshotRepo fsr) === Right (fsr :: FsSnapshotRepo)
1359+
1360+ describe "snapshot repos" $ do
1361+ it "always parses all snapshot repos API" $ when' canSnapshot $ withTestEnv $ do
1362+ res <- getSnapshotRepos AllSnapshotRepos
1363+ liftIO $ case res of
1364+ Left e -> expectationFailure ("Expected a right but got Left " <> show e)
1365+ Right _ -> return ()
1366+
1367+ it "finds an existing list of repos" $ when' canSnapshot $ withTestEnv $ do
1368+ let r1n = SnapshotRepoName "bloodhound-repo1"
1369+ let r2n = SnapshotRepoName "bloodhound-repo2"
1370+ withSnapshotRepo r1n $ \r1 ->
1371+ withSnapshotRepo r2n $ \r2 -> do
1372+ repos <- getSnapshotRepos (SnapshotRepoList (ExactRepo r1n :| [ExactRepo r2n]))
1373+ liftIO $ case repos of
1374+ Right xs -> do
1375+ let srt = L.sortBy (comparing gSnapshotRepoName)
1376+ srt xs `shouldBe` srt [r1, r2]
1377+ Left e -> expectationFailure (show e)
1378+
1379+ it "creates and updates with updateSnapshotRepo" $ when' canSnapshot $ withTestEnv $ do
1380+ let r1n = SnapshotRepoName "bloodhound-repo1"
1381+ withSnapshotRepo r1n $ \r1 -> do
1382+ let Just (String dir) = HM.lookup "location" (gSnapshotRepoSettingsObject (gSnapshotRepoSettings r1))
1383+ let noCompression = FsSnapshotRepo r1n (T.unpack dir) False Nothing Nothing Nothing
1384+ resp <- updateSnapshotRepo defaultSnapshotRepoUpdateSettings noCompression
1385+ liftIO (validateStatus resp 200)
1386+ Right [roundtrippedNoCompression] <- getSnapshotRepos (SnapshotRepoList (ExactRepo r1n :| []))
1387+ liftIO (roundtrippedNoCompression `shouldBe` toGSnapshotRepo noCompression)
1388+
1389+ -- verify came around in 1.4 it seems
1390+ it "can verify existing repos" $ when' canSnapshot $ when' (atleast es14) $ withTestEnv $ do
1391+ let r1n = SnapshotRepoName "bloodhound-repo1"
1392+ withSnapshotRepo r1n $ \_ -> do
1393+ res <- verifySnapshotRepo r1n
1394+ liftIO $ case res of
1395+ Right (SnapshotVerification vs)
1396+ | null vs -> expectationFailure "Expected nonempty set of verifying nodes"
1397+ | otherwise -> return ()
1398+ Left e -> expectationFailure (show e)
1399+
1400+ describe "snapshots" $ do
1401+ it "always parses all snapshots API" $ when' canSnapshot $ withTestEnv $ do
1402+ let r1n = SnapshotRepoName "bloodhound-repo1"
1403+ withSnapshotRepo r1n $ \_ -> do
1404+ res <- getSnapshots r1n AllSnapshots
1405+ liftIO $ case res of
1406+ Left e -> expectationFailure ("Expected a right but got Left " <> show e)
1407+ Right _ -> return ()
1408+
1409+ it "can parse a snapshot that it created" $ when' canSnapshot $ withTestEnv $ do
1410+ let r1n = SnapshotRepoName "bloodhound-repo1"
1411+ withSnapshotRepo r1n $ \_ -> do
1412+ let s1n = SnapshotName "example-snapshot"
1413+ withSnapshot r1n s1n $ do
1414+ res <- getSnapshots r1n (SnapshotList (ExactSnap s1n :| []))
1415+ liftIO $ case res of
1416+ Right [snap]
1417+ | snapInfoState snap == SnapshotSuccess &&
1418+ snapInfoName snap == s1n -> return ()
1419+ | otherwise -> expectationFailure (show snap)
1420+ Right [] -> expectationFailure "There were no snapshots"
1421+ Right snaps -> expectationFailure ("Expected 1 snapshot but got" <> show (length snaps))
1422+ Left e -> expectationFailure (show e)
1423+
1424+ describe "snapshot restore" $ do
1425+ it "can restore a snapshot that we create" $ when' canSnapshot $ withTestEnv $ do
1426+ let r1n = SnapshotRepoName "bloodhound-repo1"
1427+ withSnapshotRepo r1n $ \_ -> do
1428+ let s1n = SnapshotName "example-snapshot"
1429+ withSnapshot r1n s1n $ do
1430+ let settings = defaultSnapshotRestoreSettings { snapRestoreWaitForCompletion = True }
1431+ -- have to close an index to restore it
1432+ resp1 <- closeIndex testIndex
1433+ liftIO (validateStatus resp1 200)
1434+ resp2 <- restoreSnapshot r1n s1n settings
1435+ liftIO (validateStatus resp2 200)
1436+
1437+ it "can restore and rename" $ when' canSnapshot $ withTestEnv $ do
1438+ let r1n = SnapshotRepoName "bloodhound-repo1"
1439+ withSnapshotRepo r1n $ \_ -> do
1440+ let s1n = SnapshotName "example-snapshot"
1441+ withSnapshot r1n s1n $ do
1442+ let pat = RestoreRenamePattern "bloodhound-tests-twitter-(\\d+)"
1443+ let replace = RRTLit "restored-" :| [RRSubWholeMatch]
1444+ let expectedIndex = IndexName "restored-bloodhound-tests-twitter-1"
1445+ oldEnoughForOverrides <- liftIO (atleast es15)
1446+ let overrides = RestoreIndexSettings { restoreOverrideReplicas = Just (ReplicaCount 0) }
1447+ let settings = defaultSnapshotRestoreSettings { snapRestoreWaitForCompletion = True
1448+ , snapRestoreRenamePattern = Just pat
1449+ , snapRestoreRenameReplacement = Just replace
1450+ , snapRestoreIndexSettingsOverrides = if oldEnoughForOverrides
1451+ then Just overrides
1452+ else Nothing
1453+ }
1454+ -- have to close an index to restore it
1455+ let go = do
1456+ resp <- restoreSnapshot r1n s1n settings
1457+ liftIO (validateStatus resp 200)
1458+ exists <- indexExists expectedIndex
1459+ liftIO (exists `shouldBe` True)
1460+ go `finally` deleteIndex expectedIndex
1461+
1462+ describe "getNodesInfo" $ do
1463+ it "fetches the responding node when LocalNode is used" $ withTestEnv $ do
1464+ res <- getNodesInfo LocalNode
1465+ liftIO $ case res of
1466+ -- This is really just a smoke test for response
1467+ -- parsing. Node info is so variable, there's not much I can
1468+ -- assert here.
1469+ Right NodesInfo {..} -> length nodesInfo `shouldBe` 1
1470+ Left e -> expectationFailure ("Expected NodesInfo but got " <> show e)
1471+
1472+ describe "getNodesStats" $ do
1473+ it "fetches the responding node when LocalNode is used" $ withTestEnv $ do
1474+ res <- getNodesStats LocalNode
1475+ liftIO $ case res of
1476+ -- This is really just a smoke test for response
1477+ -- parsing. Node stats is so variable, there's not much I can
1478+ -- assert here.
1479+ Right NodesStats {..} -> length nodesStats `shouldBe` 1
1480+ Left e -> expectationFailure ("Expected NodesStats but got " <> show e)
1481+
1482+ describe "Enum DocVersion" $ do
1483+ it "follows the laws of Enum, Bounded" $ do
1484+ evaluate (succ maxBound :: DocVersion) `shouldThrow` anyErrorCall
1485+ evaluate (pred minBound :: DocVersion) `shouldThrow` anyErrorCall
1486+ evaluate (toEnum 0 :: DocVersion) `shouldThrow` anyErrorCall
1487+ evaluate (toEnum 9200000000000000001 :: DocVersion) `shouldThrow` anyErrorCall
1488+ enumFrom (pred maxBound :: DocVersion) `shouldBe` [pred maxBound, maxBound]
1489+ enumFrom (pred maxBound :: DocVersion) `shouldBe` [pred maxBound, maxBound]
1490+ enumFromThen minBound (pred maxBound :: DocVersion) `shouldBe` [minBound, pred maxBound]
1491+
1492+ describe "scan&scroll API" $ do
1493+ it "returns documents using the scan&scroll API" $ withTestEnv $ do
1494+ _ <- insertData
1495+ _ <- insertOther
1496+ let search = (mkSearch (Just $ MatchAllQuery Nothing) Nothing) { size = (Size 1) }
1497+ regular_search <- searchTweet search
1498+ scan_search' <- scanSearch testIndex testMapping search :: BH IO [Hit Tweet]
1499+ let scan_search = map hitSource scan_search'
1500+ liftIO $
1501+ regular_search `shouldBe` Right exampleTweet -- Check that the size restrtiction is being honored
1502+ liftIO $
1503+ scan_search `shouldMatchList` [Just exampleTweet, Just otherTweet]
1504+
1505+ describe "index aliases" $ do
1506+ it "handles the simple case of aliasing an existing index" $ do
1507+ let alias = IndexAlias (testIndex) (IndexAliasName (IndexName "bloodhound-tests-twitter-1-alias"))
1508+ let create = IndexAliasCreate Nothing Nothing
1509+ let action = AddAlias alias create
1510+
1511+ withTestEnv $ do
1512+ resetIndex
1513+ resp <- updateIndexAliases (action :| [])
1514+ liftIO $ validateStatus resp 200
1515+ let cleanup = withTestEnv (updateIndexAliases (RemoveAlias alias :| []))
1516+ (do aliases <- withTestEnv getIndexAliases
1517+ let expected = IndexAliasSummary alias create
1518+ case aliases of
1519+ Right (IndexAliasesSummary summs) ->
1520+ L.find ((== alias) . indexAliasSummaryAlias) summs `shouldBe` Just expected
1521+ Left e -> expectationFailure ("Expected an IndexAliasesSummary but got " <> show e)) `finally` cleanup
1522+
1523+ describe "Index Listing" $ do
1524+ it "returns a list of index names" $ withTestEnv $ do
1525+ _ <- createExampleIndex
1526+ ixns <- listIndices
1527+ liftIO (ixns `shouldContain` [testIndex])
1528+
1529+ describe "Index Settings" $ do
1530+ it "persists settings" $ withTestEnv $ do
1531+ _ <- deleteExampleIndex
1532+ _ <- createExampleIndex
1533+ let updates = BlocksWrite False :| []
1534+ updateResp <- updateIndexSettings updates testIndex
1535+ liftIO $ validateStatus updateResp 200
1536+ getResp <- getIndexSettings testIndex
1537+ liftIO $
1538+ getResp `shouldBe` Right (IndexSettingsSummary
1539+ testIndex
1540+ (IndexSettings (ShardCount 1) (ReplicaCount 0))
1541+ (NE.toList updates))
1542+
1543+ describe "Index Optimization" $ do
1544+ it "returns a successful response upon completion" $ withTestEnv $ do
1545+ _ <- createExampleIndex
1546+ resp <- forceMergeIndex (IndexList (testIndex :| [])) defaultForceMergeIndexSettings
1547+ liftIO $ validateStatus resp 200
1548+
1549+ describe "JSON instances" $ do
1550+ propJSON (Proxy :: Proxy Version)
1551+ propJSON (Proxy :: Proxy IndexName)
1552+ propJSON (Proxy :: Proxy MappingName)
1553+ propJSON (Proxy :: Proxy DocId)
1554+ propJSON (Proxy :: Proxy IndexAliasRouting)
1555+ propJSON (Proxy :: Proxy RoutingValue)
1556+ propJSON (Proxy :: Proxy ShardCount)
1557+ propJSON (Proxy :: Proxy ReplicaCount)
1558+ propJSON (Proxy :: Proxy TemplateName)
1559+ propJSON (Proxy :: Proxy TemplatePattern)
1560+ propJSON (Proxy :: Proxy QueryString)
1561+ propJSON (Proxy :: Proxy FieldName)
1562+ propJSON (Proxy :: Proxy CacheName)
1563+ propJSON (Proxy :: Proxy CacheKey)
1564+ propJSON (Proxy :: Proxy Existence)
1565+ propJSON (Proxy :: Proxy CutoffFrequency)
1566+ propJSON (Proxy :: Proxy Analyzer)
1567+ propJSON (Proxy :: Proxy MaxExpansions)
1568+ propJSON (Proxy :: Proxy Lenient)
1569+ propJSON (Proxy :: Proxy Tiebreaker)
1570+ propJSON (Proxy :: Proxy Boost)
1571+ propJSON (Proxy :: Proxy BoostTerms)
1572+ propJSON (Proxy :: Proxy MinimumMatch)
1573+ propJSON (Proxy :: Proxy DisableCoord)
1574+ propJSON (Proxy :: Proxy IgnoreTermFrequency)
1575+ propJSON (Proxy :: Proxy MinimumTermFrequency)
1576+ propJSON (Proxy :: Proxy MaxQueryTerms)
1577+ propJSON (Proxy :: Proxy Fuzziness)
1578+ propJSON (Proxy :: Proxy PrefixLength)
1579+ propJSON (Proxy :: Proxy TypeName)
1580+ propJSON (Proxy :: Proxy PercentMatch)
1581+ propJSON (Proxy :: Proxy StopWord)
1582+ propJSON (Proxy :: Proxy QueryPath)
1583+ propJSON (Proxy :: Proxy AllowLeadingWildcard)
1584+ propJSON (Proxy :: Proxy LowercaseExpanded)
1585+ propJSON (Proxy :: Proxy EnablePositionIncrements)
1586+ propJSON (Proxy :: Proxy AnalyzeWildcard)
1587+ propJSON (Proxy :: Proxy GeneratePhraseQueries)
1588+ propJSON (Proxy :: Proxy Locale)
1589+ propJSON (Proxy :: Proxy MaxWordLength)
1590+ propJSON (Proxy :: Proxy MinWordLength)
1591+ propJSON (Proxy :: Proxy PhraseSlop)
1592+ propJSON (Proxy :: Proxy MinDocFrequency)
1593+ propJSON (Proxy :: Proxy MaxDocFrequency)
1594+ propJSON (Proxy :: Proxy Filter)
1595+ propJSON (Proxy :: Proxy Query)
1596+ propJSON (Proxy :: Proxy SimpleQueryStringQuery)
1597+ propJSON (Proxy :: Proxy FieldOrFields)
1598+ propJSON (Proxy :: Proxy SimpleQueryFlag)
1599+ propJSON (Proxy :: Proxy RegexpQuery)
1600+ propJSON (Proxy :: Proxy QueryStringQuery)
1601+ propJSON (Proxy :: Proxy RangeQuery)
1602+ propJSON (Proxy :: Proxy PrefixQuery)
1603+ propJSON (Proxy :: Proxy NestedQuery)
1604+ propJSON (Proxy :: Proxy MoreLikeThisFieldQuery)
1605+ propJSON (Proxy :: Proxy MoreLikeThisQuery)
1606+ propJSON (Proxy :: Proxy IndicesQuery)
1607+ propJSON (Proxy :: Proxy HasParentQuery)
1608+ propJSON (Proxy :: Proxy HasChildQuery)
1609+ propJSON (Proxy :: Proxy FuzzyQuery)
1610+ propJSON (Proxy :: Proxy FuzzyLikeFieldQuery)
1611+ propJSON (Proxy :: Proxy FuzzyLikeThisQuery)
1612+ propJSON (Proxy :: Proxy DisMaxQuery)
1613+ propJSON (Proxy :: Proxy CommonTermsQuery)
1614+ propJSON (Proxy :: Proxy CommonMinimumMatch)
1615+ propJSON (Proxy :: Proxy BoostingQuery)
1616+ propJSON (Proxy :: Proxy BoolQuery)
1617+ propJSON (Proxy :: Proxy MatchQuery)
1618+ propJSON (Proxy :: Proxy MultiMatchQueryType)
1619+ propJSON (Proxy :: Proxy BooleanOperator)
1620+ propJSON (Proxy :: Proxy ZeroTermsQuery)
1621+ propJSON (Proxy :: Proxy MatchQueryType)
1622+ propJSON (Proxy :: Proxy AliasRouting)
1623+ propJSON (Proxy :: Proxy IndexAliasCreate)
1624+ propJSON (Proxy :: Proxy SearchAliasRouting)
1625+ propJSON (Proxy :: Proxy ScoreType)
1626+ propJSON (Proxy :: Proxy Distance)
1627+ propJSON (Proxy :: Proxy DistanceUnit)
1628+ propJSON (Proxy :: Proxy DistanceType)
1629+ propJSON (Proxy :: Proxy OptimizeBbox)
1630+ propJSON (Proxy :: Proxy GeoBoundingBoxConstraint)
1631+ propJSON (Proxy :: Proxy GeoFilterType)
1632+ propJSON (Proxy :: Proxy GeoBoundingBox)
1633+ propJSON (Proxy :: Proxy LatLon)
1634+ propJSON (Proxy :: Proxy RangeExecution)
1635+ prop "RegexpFlags FromJSON/ToJSON roundtrips, removing dups " $ \rfs ->
1636+ let expected = case rfs of
1637+ SomeRegexpFlags fs -> SomeRegexpFlags (NE.fromList (nub (NE.toList fs)))
1638+ x -> x
1639+ in parseEither parseJSON (toJSON rfs) === Right expected
1640+ propJSON (Proxy :: Proxy BoolMatch)
1641+ propJSON (Proxy :: Proxy Term)
1642+ propJSON (Proxy :: Proxy MultiMatchQuery)
1643+ propJSON (Proxy :: Proxy IndexSettings)
1644+ propJSON (Proxy :: Proxy UpdatableIndexSetting')
1645+ propJSON (Proxy :: Proxy ReplicaBounds)
1646+ propJSON (Proxy :: Proxy Bytes)
1647+ propJSON (Proxy :: Proxy AllocationPolicy)
1648+ propJSON (Proxy :: Proxy InitialShardCount)
1649+ propJSON (Proxy :: Proxy FSType)
1650+ propJSON (Proxy :: Proxy CompoundFormat)
1651+
1652+-- Temporary solution for lacking of generic derivation of Arbitrary
1653+-- We use generics-sop, as it's much more concise than directly using GHC.Generics
1654+--
1655+-- This will be unneeded after https://github.com/nick8325/quickcheck/pull/112
1656+-- is merged and released
1657+sopArbitrary :: forall a. (Generic a, SOP.GTo a, SOP.All SOP.SListI (SOP.GCode a), SOP.All2 Arbitrary (SOP.GCode a)) => Gen a
1658+sopArbitrary = fmap SOP.gto sopArbitrary'
1659+
1660+sopArbitrary' :: forall xss. (SOP.All SOP.SListI xss, SOP.All2 Arbitrary xss) => Gen (SOP.SOP SOP.I xss)
1661+sopArbitrary' = SOP.hsequence =<< elements (SOP.apInjs_POP $ SOP.hcpure p arbitrary)
1662+ where
1663+ p :: Proxy Arbitrary
1664+ p = Proxy
tests/doctests.hsdeleted−4
@@ -1,4 +0,0 @@
1−import Test.DocTest
2−
3−main :: IO ()
4−main = doctest ["-i src", "Database.Bloodhound"]