git.iko.soy/typeable/bloodhound
on master help sign in
Both V1 and V5 modules and tests

A new module structure for both V1 and V5, and seperate test suites. Also added a new cabal flag for travis testing.

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