git.iko.soy/typeable/bloodhound
on master help sign in
Merge pull request #158 from bermanjosh/es5

Es5

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