git.iko.soy/typeable/bloodhound
on master help sign in
Merge pull request #82 from Soostone/date-range-agg

Date range agg

commit0b116a02849fbeab89fbbd3da3c808c40e64d0ed authorChris Allen <cma@bitemyapp.com> dateWed 18 Nov 2015, 19:50 (11 years ago) parentd1b4b7c10666e6
4 files changed+1006−174
bloodhound.cabal+1−1 src/Database/Bloodhound/Client.hs+89−27 src/Database/Bloodhound/Types.hs+607−135 tests/tests.hs+309−11
bloodhound.cabal+1−1
@@ -71,7 +71,7 @@ test-suite tests
7171 mtl,
7272 quickcheck-properties,
7373 derive,
74− quickcheck-instances
74+ errors
7575 default-language: Haskell2010
7676
7777test-suite doctests
src/Database/Bloodhound/Client.hs+89−27
@@ -24,6 +24,8 @@ module Database.Bloodhound.Client
2424 withBH
2525 , createIndex
2626 , deleteIndex
27+ , updateIndexSettings
28+ , getIndexSettings
2729 , indexExists
2830 , openIndex
2931 , closeIndex
@@ -42,6 +44,8 @@ module Database.Bloodhound.Client
4244 , searchByIndex
4345 , searchByType
4446 , scanSearch
47+ , getInitialScroll
48+ , advanceScroll
4549 , refreshIndex
4650 , mkSearch
4751 , mkAggregateSearch
@@ -70,14 +74,16 @@ import Data.Aeson
7074import Data.ByteString.Lazy.Builder
7175import qualified Data.ByteString.Lazy.Char8 as L
7276import Data.Foldable (toList)
77+import qualified Data.HashMap.Strict as HM
7378import Data.Ix
74−import qualified Data.List as LS (filter)
79+import qualified Data.List as LS (filter, foldl')
7580import Data.List.NonEmpty (NonEmpty (..))
7681import Data.Maybe (fromMaybe, isJust)
7782import Data.Monoid
7883import Data.Text (Text)
7984import qualified Data.Text as T
8085import qualified Data.Text.Encoding as T
86+import Data.Time.Clock
8187import qualified Data.Vector as V
8288import Network.HTTP.Client
8389import qualified Network.HTTP.Types.Method as NHTM
@@ -261,6 +267,7 @@ createIndex indexSettings (IndexName indexName) =
261267 where url = joinPath [indexName]
262268 body = Just $ encode indexSettings
263269
270+
264271-- | 'deleteIndex' will delete an index given a 'Server', and an 'IndexName'.
265272--
266273-- >>> _ <- runBH' $ createIndex defaultIndexSettings (IndexName "didimakeanindex")
@@ -273,6 +280,35 @@ deleteIndex :: MonadBH m => IndexName -> m Reply
273280deleteIndex (IndexName indexName) =
274281 delete =<< joinPath [indexName]
275282
283+-- | 'updateIndexSettings' will apply a non-empty list of setting updates to an index
284+--
285+-- >>> _ <- runBH' $ createIndex defaultIndexSettings (IndexName "unconfiguredindex")
286+-- >>> response <- runBH' $ updateIndexSettings (BlocksWrite False :| []) (IndexName "unconfiguredindex")
287+-- >>> respIsTwoHunna response
288+-- True
289+updateIndexSettings :: MonadBH m => NonEmpty UpdatableIndexSetting -> IndexName -> m Reply
290+updateIndexSettings updates (IndexName indexName) =
291+ bindM2 put url (return body)
292+ where url = joinPath [indexName, "_settings"]
293+ body = Just (encode jsonBody)
294+ jsonBody = Object (deepMerge [u | Object u <- toJSON <$> toList updates])
295+
296+
297+getIndexSettings :: (MonadBH m, MonadThrow m) => IndexName
298+ -> m (Either EsError IndexSettingsSummary)
299+getIndexSettings (IndexName indexName) = do
300+ parseEsResponse =<< get =<< url
301+ where url = joinPath [indexName, "_settings"]
302+
303+
304+deepMerge :: [Object] -> Object
305+deepMerge = LS.foldl' go mempty
306+ where go acc = LS.foldl' go' acc . HM.toList
307+ go' acc (k, v) = HM.insertWith merge k v acc
308+ merge (Object a) (Object b) = Object (deepMerge [a, b])
309+ merge _ b = b
310+
311+
276312statusCodeIs :: Int -> Reply -> Bool
277313statusCodeIs n resp = NHTS.statusCode (responseStatus resp) == n
278314
@@ -288,8 +324,10 @@ existentialQuery url = do
288324-- | Tries to parse a response body as the expected type @a@ and
289325-- failing that tries to parse it as an EsError. All well-formed, JSON
290326-- responses from elasticsearch should fall into these two
291−-- categories. If they don't, a 'StatusCodeException' will be thrown.
292−parseEsResponse :: (MonadBH m, MonadThrow m, FromJSON a) => Reply
327+-- categories. If they don't, a 'EsProtocolException' will be
328+-- thrown. If you encounter this, please report the full body it
329+-- reports along with your ElasticSearch verison.
330+parseEsResponse :: (MonadThrow m, FromJSON a) => Reply
293331 -> m (Either EsError a)
294332parseEsResponse reply
295333 | respIsTwoHunna reply = case eitherDecode body of
@@ -297,14 +335,11 @@ parseEsResponse reply
297335 Left _ -> tryParseError
298336 | otherwise = tryParseError
299337 where body = responseBody reply
300− stat = responseStatus reply
301− hdrs = responseHeaders reply
302− cookies = responseCookieJar reply
303338 tryParseError = case eitherDecode body of
304339 Right e -> return (Left e)
305340 -- this case should not be possible
306341 Left _ -> explode
307− explode = throwM (StatusCodeException stat hdrs cookies)
342+ explode = throwM (EsProtocolException body)
308343
309344-- | 'indexExists' enables you to check if an index exists. Returns 'Bool'
310345-- in IO
@@ -608,8 +643,12 @@ searchByType (IndexName indexName)
608643 (MappingName mappingName) = bindM2 dispatchSearch url . return
609644 where url = joinPath [indexName, mappingName, "_search"]
610645
611−scanSearch' :: MonadBH m => IndexName -> MappingName -> Search -> m (Maybe ScrollId)
612−scanSearch' (IndexName indexName) (MappingName mappingName) search = do
646+-- | For a given scearch, request a scroll for efficient streaming of
647+-- search results. Note that the search is put into 'SearchTypeScan'
648+-- mode and thus results will not be sorted. Combine this with
649+-- 'advanceScroll' to efficiently stream through the full result set
650+getInitialScroll :: MonadBH m => IndexName -> MappingName -> Search -> m (Maybe ScrollId)
651+getInitialScroll (IndexName indexName) (MappingName mappingName) search = do
613652 let url = joinPath [indexName, mappingName, "_search"]
614653 search' = search { searchType = SearchTypeScan }
615654 resp' <- bindM2 dispatchSearch url (return search')
@@ -617,31 +656,54 @@ scanSearch' (IndexName indexName) (MappingName mappingName) search = do
617656 msid = maybe Nothing scrollId msr
618657 return msid
619658
620−scroll' :: (FromJSON a, MonadBH m) => Maybe ScrollId -> m ([Hit a], Maybe ScrollId)
659+scroll' :: (FromJSON a, MonadBH m, MonadThrow m) => Maybe ScrollId -> m ([Hit a], Maybe ScrollId)
621660scroll' Nothing = return ([], Nothing)
622661scroll' (Just sid) = do
623− url <- joinPath ["_search/scroll?scroll=1m"]
624− resp' <- post url (Just . L.fromStrict $ T.encodeUtf8 sid)
625− let msr = decode' $ responseBody resp' :: FromJSON a => Maybe (SearchResult a)
626− resp = case msr of
627− Just sr -> (hits $ searchHits sr, scrollId sr)
628− _ -> ([], Nothing)
629− return resp
630−
631−simpleAccumilator :: (FromJSON a, MonadBH m) => [Hit a] -> ([Hit a], Maybe ScrollId) -> m ([Hit a], Maybe ScrollId)
632−simpleAccumilator oldHits (newHits, Nothing) = return (oldHits ++ newHits, Nothing)
633−simpleAccumilator oldHits ([], _) = return (oldHits, Nothing)
634−simpleAccumilator oldHits (newHits, msid) = do
662+ res <- advanceScroll sid 60
663+ case res of
664+ Right SearchResult {..} -> return (hits searchHits, scrollId)
665+ Left _ -> return ([], Nothing)
666+
667+-- | Use the given scroll to fetch the next page of documents. If
668+-- there are still further pages, there will be a value in the
669+-- 'scrollId' field of the 'SearchResult'
670+advanceScroll
671+ :: ( FromJSON a
672+ , MonadBH m
673+ , MonadThrow m
674+ )
675+ => ScrollId
676+ -> NominalDiffTime
677+ -- ^ 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.
678+ -> m (Either EsError (SearchResult a))
679+advanceScroll (ScrollId sid) scroll = do
680+ url <- joinPath ["_search/scroll?scroll=" <> scrollTime]
681+ parseEsResponse =<< post url (Just . L.fromStrict $ T.encodeUtf8 sid)
682+ where scrollTime = showText secs <> "s"
683+ secs :: Integer
684+ secs = round scroll
685+
686+simpleAccumulator :: (FromJSON a, MonadBH m, MonadThrow m) => [Hit a] -> ([Hit a], Maybe ScrollId) -> m ([Hit a], Maybe ScrollId)
687+simpleAccumulator oldHits (newHits, Nothing) = return (oldHits ++ newHits, Nothing)
688+simpleAccumulator oldHits ([], _) = return (oldHits, Nothing)
689+simpleAccumulator oldHits (newHits, msid) = do
635690 (newHits', msid') <- scroll' msid
636− simpleAccumilator (oldHits ++ newHits) (newHits', msid')
691+ simpleAccumulator (oldHits ++ newHits) (newHits', msid')
637692
638693-- | 'scanSearch' uses the 'scan&scroll' API of elastic,
639−-- for a given 'IndexName' and 'MappingName',
640−scanSearch :: (FromJSON a, MonadBH m) => IndexName -> MappingName -> Search -> m [Hit a]
694+-- for a given 'IndexName' and 'MappingName'. Note that this will
695+-- consume the entire search result set and will be doing O(n) list
696+-- appends so this may not be suitable for large result sets. In that
697+-- case, 'getInitialScroll' and 'advanceScroll' are good low level
698+-- tools. You should be able to hook them up trivially to conduit,
699+-- pipes, or your favorite streaming IO abstraction of choice. Note
700+-- that ordering on the search would destroy performance and thus is
701+-- ignored.
702+scanSearch :: (FromJSON a, MonadBH m, MonadThrow m) => IndexName -> MappingName -> Search -> m [Hit a]
641703scanSearch indexName mappingName search = do
642− msid <- scanSearch' indexName mappingName search
704+ msid <- getInitialScroll indexName mappingName search
643705 (hits, msid') <- scroll' msid
644− (totalHits, _) <- simpleAccumilator [] (hits, msid')
706+ (totalHits, _) <- simpleAccumulator [] (hits, msid')
645707 return totalHits
646708
647709-- | 'mkSearch' is a helper function for defaulting additional fields of a 'Search'
src/Database/Bloodhound/Types.hs+607−135
@@ -58,12 +58,23 @@ module Database.Bloodhound.Types
5858 , Existence(..)
5959 , NullValue(..)
6060 , IndexSettings(..)
61+ , UpdatableIndexSetting(..)
62+ , IndexSettingsSummary(..)
63+ , AllocationPolicy(..)
64+ , ReplicaBounds(..)
65+ , Bytes(..)
66+ , FSType(..)
67+ , InitialShardCount(..)
68+ , NodeAttrFilter(..)
69+ , NodeAttrName(..)
70+ , CompoundFormat(..)
6171 , IndexTemplate(..)
6272 , Server(..)
6373 , Reply
6474 , EsResult(..)
6575 , EsResultFound(..)
6676 , EsError(..)
77+ , EsProtocolException(..)
6778 , IndexAlias(..)
6879 , IndexAliasName(..)
6980 , IndexAliasAction(..)
@@ -83,7 +94,7 @@ module Database.Bloodhound.Types
8394 , Search(..)
8495 , SearchType(..)
8596 , SearchResult(..)
86− , ScrollId
97+ , ScrollId(..)
8798 , SearchHits(..)
8899 , TrackSortScores
89100 , From(..)
@@ -225,6 +236,12 @@ module Database.Bloodhound.Types
225236 , ValueCountAggregation(..)
226237 , FilterAggregation(..)
227238 , DateHistogramAggregation(..)
239+ , DateRangeAggregation(..)
240+ , DateRangeAggRange(..)
241+ , DateMathExpr(..)
242+ , DateMathAnchor(..)
243+ , DateMathModifier(..)
244+ , DateMathUnit(..)
228245
229246 , Highlights(..)
230247 , FieldHighlight(..)
@@ -240,6 +257,7 @@ module Database.Bloodhound.Types
240257
241258 , TermsResult(..)
242259 , DateHistogramResult(..)
260+ , DateRangeResult(..)
243261 ) where
244262
245263import Control.Applicative
@@ -261,7 +279,9 @@ import qualified Data.Map.Strict as M
261279import Data.Maybe
262280import Data.Text (Text)
263281import qualified Data.Text as T
264−import Data.Time.Clock (UTCTime)
282+import Data.Time.Calendar
283+import Data.Time.Clock (NominalDiffTime, UTCTime)
284+import Data.Time.Clock.POSIX
265285import qualified Data.Traversable as DT
266286import Data.Typeable (Typeable)
267287import qualified Data.Vector as V
@@ -349,7 +369,7 @@ data Status = Status { ok :: Maybe Bool
349369 , status :: Int
350370 , name :: Text
351371 , version :: Version
352− , tagline :: Text } deriving (Eq, Show)
372+ , tagline :: Text } deriving (Eq, Show, Generic)
353373
354374{-| 'IndexSettings' is used to configure the shards and replicas when you create
355375 an Elasticsearch Index.
@@ -359,12 +379,111 @@ data Status = Status { ok :: Maybe Bool
359379
360380data IndexSettings =
361381 IndexSettings { indexShards :: ShardCount
362− , indexReplicas :: ReplicaCount } deriving (Eq, Show)
382+ , indexReplicas :: ReplicaCount } deriving (Eq, Show, Generic, Typeable)
363383
364384{-| 'defaultIndexSettings' is an 'IndexSettings' with 3 shards and 2 replicas. -}
365385defaultIndexSettings :: IndexSettings
366386defaultIndexSettings = IndexSettings (ShardCount 3) (ReplicaCount 2)
367387
388+{-| 'UpdatableIndexSetting' are settings which may be updated after an index is created.
389+
390+ <https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-update-settings.html>
391+-}
392+data UpdatableIndexSetting = NumberOfReplicas ReplicaCount
393+ -- ^ The number of replicas each shard has.
394+ | AutoExpandReplicas ReplicaBounds
395+ | BlocksReadOnly Bool
396+ -- ^ Set to True to have the index read only. False to allow writes and metadata changes.
397+ | BlocksRead Bool
398+ -- ^ Set to True to disable read operations against the index.
399+ | BlocksWrite Bool
400+ -- ^ Set to True to disable write operations against the index.
401+ | BlocksMetaData Bool
402+ -- ^ Set to True to disable metadata operations against the index.
403+ | RefreshInterval NominalDiffTime
404+ -- ^ The async refresh interval of a shard
405+ | IndexConcurrency Int
406+ | FailOnMergeFailure Bool
407+ | TranslogFlushThresholdOps Int
408+ -- ^ When to flush on operations.
409+ | TranslogFlushThresholdSize Bytes
410+ -- ^ When to flush based on translog (bytes) size.
411+ | TranslogFlushThresholdPeriod NominalDiffTime
412+ -- ^ When to flush based on a period of not flushing.
413+ | TranslogDisableFlush Bool
414+ -- ^ Disables flushing. Note, should be set for a short interval and then enabled.
415+ | CacheFilterMaxSize (Maybe Bytes)
416+ -- ^ The maximum size of filter cache (per segment in shard).
417+ | CacheFilterExpire (Maybe NominalDiffTime)
418+ -- ^ The expire after access time for filter cache.
419+ | GatewaySnapshotInterval NominalDiffTime
420+ -- ^ The gateway snapshot interval (only applies to shared gateways).
421+ | RoutingAllocationInclude (NonEmpty NodeAttrFilter)
422+ -- ^ A node matching any rule will be allowed to host shards from the index.
423+ | RoutingAllocationExclude (NonEmpty NodeAttrFilter)
424+ -- ^ A node matching any rule will NOT be allowed to host shards from the index.
425+ | RoutingAllocationRequire (NonEmpty NodeAttrFilter)
426+ -- ^ Only nodes matching all rules will be allowed to host shards from the index.
427+ | RoutingAllocationEnable AllocationPolicy
428+ -- ^ Enables shard allocation for a specific index.
429+ | RoutingAllocationShardsPerNode ShardCount
430+ -- ^ Controls the total number of shards (replicas and primaries) allowed to be allocated on a single node.
431+ | RecoveryInitialShards InitialShardCount
432+ -- ^ When using local gateway a particular shard is recovered only if there can be allocated quorum shards in the cluster.
433+ | GCDeletes NominalDiffTime
434+ | TTLDisablePurge Bool
435+ -- ^ Disables temporarily the purge of expired docs.
436+ | TranslogFSType FSType
437+ | IndexCompoundFormat CompoundFormat
438+ | IndexCompoundOnFlush Bool
439+ | WarmerEnabled Bool
440+ deriving (Eq, Show, Generic, Typeable)
441+
442+data AllocationPolicy = AllocAll
443+ -- ^ Allows shard allocation for all shards.
444+ | AllocPrimaries
445+ -- ^ Allows shard allocation only for primary shards.
446+ | AllocNewPrimaries
447+ -- ^ Allows shard allocation only for primary shards for new indices.
448+ | AllocNone
449+ -- ^ No shard allocation is allowed
450+ deriving (Eq, Show, Generic, Typeable)
451+
452+data ReplicaBounds = ReplicasBounded Int Int
453+ | ReplicasLowerBounded Int
454+ | ReplicasUnbounded
455+ deriving (Eq, Show, Generic, Typeable)
456+
457+newtype Bytes = Bytes Int deriving (Eq, Show, Generic, Typeable, Ord, ToJSON, FromJSON)
458+
459+data FSType = FSSimple
460+ | FSBuffered deriving (Eq, Show, Generic, Typeable, Ord)
461+
462+data InitialShardCount = QuorumShards
463+ | QuorumMinus1Shards
464+ | FullShards
465+ | FullMinus1Shards
466+ | ExplicitShards Int
467+ deriving (Eq, Show, Generic, Typeable)
468+
469+data NodeAttrFilter = NodeAttrFilter { nodeAttrFilterName :: NodeAttrName
470+ , nodeAttrFilterValues :: NonEmpty Text}
471+ deriving (Eq, Show, Generic, Ord)
472+
473+newtype NodeAttrName = NodeAttrName Text deriving (Eq, Show, Ord, Generic, Typeable)
474+
475+data CompoundFormat = CompoundFileFormat Bool
476+ | MergeSegmentVsTotalIndex Double
477+ -- ^ percentage between 0 and 1 where 0 is false, 1 is true
478+ deriving (Eq, Show, Generic, Typeable)
479+
480+newtype NominalDiffTimeJSON = NominalDiffTimeJSON { ndtJSON :: NominalDiffTime }
481+
482+data IndexSettingsSummary = IndexSettingsSummary { sSummaryIndexName :: IndexName
483+ , sSummaryFixedSettings :: IndexSettings
484+ , sSummaryUpdateable :: [UpdatableIndexSetting]}
485+ deriving (Eq, Show, Generic, Typeable)
486+
368487{-| 'Reply' and 'Method' are type synonyms from 'Network.HTTP.Types.Method.Method' -}
369488type Reply = Network.HTTP.Client.Response L.ByteString
370489type Method = NHTM.Method
@@ -373,7 +492,7 @@ type Method = NHTM.Method
373492
374493 <http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-open-close.html>
375494-}
376−data OpenCloseIndex = OpenIndex | CloseIndex deriving (Eq, Show)
495+data OpenCloseIndex = OpenIndex | CloseIndex deriving (Eq, Show, Generic, Typeable)
377496
378497data FieldType = GeoPointType
379498 | GeoShapeType
@@ -381,10 +500,10 @@ data FieldType = GeoPointType
381500 | IntegerType
382501 | LongType
383502 | ShortType
384− | ByteType deriving (Eq, Show)
503+ | ByteType deriving (Eq, Show, Generic, Typeable)
385504
386505data FieldDefinition =
387− FieldDefinition { fieldType :: FieldType } deriving (Eq, Show)
506+ FieldDefinition { fieldType :: FieldType } deriving (Eq, Show, Generic, Typeable)
388507
389508{-| An 'IndexTemplate' defines a template that will automatically be
390509 applied to new indices created. The templates include both
@@ -402,7 +521,7 @@ data IndexTemplate =
402521
403522data MappingField =
404523 MappingField { mappingFieldName :: FieldName
405− , fieldDefinition :: FieldDefinition } deriving (Eq, Show)
524+ , fieldDefinition :: FieldDefinition } deriving (Eq, Show, Generic, Typeable)
406525
407526{-| Support for type reification of 'Mapping's is currently incomplete, for
408527 now the mapping API verbiage expects a 'ToJSON'able blob.
@@ -412,7 +531,7 @@ data MappingField =
412531 and keeping different kinds of documents separated if possible.
413532-}
414533data Mapping = Mapping { typeName :: TypeName
415− , mappingFields :: [MappingField] } deriving (Eq, Show)
534+ , mappingFields :: [MappingField] } deriving (Eq, Show, Generic, Typeable)
416535
417536{-| 'BulkOperation' is a sum type for expressing the four kinds of bulk
418537 operation index, create, delete, and update. 'BulkIndex' behaves like an
@@ -424,7 +543,7 @@ data BulkOperation =
424543 BulkIndex IndexName MappingName DocId Value
425544 | BulkCreate IndexName MappingName DocId Value
426545 | BulkDelete IndexName MappingName DocId
427− | BulkUpdate IndexName MappingName DocId Value deriving (Eq, Show)
546+ | BulkUpdate IndexName MappingName DocId Value deriving (Eq, Show, Generic, Typeable)
428547
429548{-| 'EsResult' describes the standard wrapper JSON document that you see in
430549 successful Elasticsearch lookups or lookups that couldn't find the document.
@@ -432,55 +551,65 @@ data BulkOperation =
432551data EsResult a = EsResult { _index :: Text
433552 , _type :: Text
434553 , _id :: Text
435− , foundResult :: Maybe (EsResultFound a)} deriving (Eq, Show)
554+ , foundResult :: Maybe (EsResultFound a)} deriving (Eq, Show, Generic, Typeable)
436555
437556{-| 'EsResultFound' contains the document and its metadata inside of an
438557 'EsResult' when the document was successfully found.
439558-}
440559data EsResultFound a = EsResultFound { _version :: DocVersion
441− , _source :: a } deriving (Eq, Show)
560+ , _source :: a } deriving (Eq, Show, Generic, Typeable)
442561
443562{-| 'EsError' is the generic type that will be returned when there was a
444563 problem. If you can't parse the expected response, its a good idea to
445564 try parsing this.
446565-}
447566data EsError = EsError { errorStatus :: Int
448− , errorMessage :: Text } deriving (Eq, Show)
567+ , errorMessage :: Text } deriving (Eq, Show, Generic, Typeable)
568+
569+{-| 'EsProtocolException' ideally should never be thrown. It will only
570+be thrown when Bloodhound can't parse a response returned by the
571+ElasticSearch server. This should be reported as a bug to the bug
572+tracker. Be sure to include the body.
573+-}
574+data EsProtocolException = EsProtocolException { esProtoExBody :: L.ByteString }
575+ deriving (Eq, Show, Generic, Typeable)
576+
577+instance Exception EsProtocolException
449578
450579data IndexAlias = IndexAlias { srcIndex :: IndexName
451− , indexAlias :: IndexAliasName } deriving (Eq, Show)
580+ , indexAlias :: IndexAliasName } deriving (Eq, Show, Generic, Typeable)
452581
453−newtype IndexAliasName = IndexAliasName { indexAliasName :: IndexName } deriving (Eq, Show, ToJSON)
582+newtype IndexAliasName = IndexAliasName { indexAliasName :: IndexName } deriving (Eq, Show, Generic, ToJSON)
454583
455584data IndexAliasAction = AddAlias IndexAlias IndexAliasCreate
456− | RemoveAlias IndexAlias deriving (Show, Eq, Typeable)
585+ | RemoveAlias IndexAlias deriving (Show, Eq, Generic, Typeable)
457586
458587data IndexAliasCreate = IndexAliasCreate { aliasCreateRouting :: Maybe AliasRouting
459588 , aliasCreateFilter :: Maybe Filter}
460− deriving (Show, Eq, Typeable)
589+ deriving (Show, Eq, Generic, Typeable)
461590
462591data AliasRouting = AllAliasRouting RoutingValue
463592 | GranularAliasRouting (Maybe SearchAliasRouting) (Maybe IndexAliasRouting)
464− deriving (Show, Eq, Typeable)
593+ deriving (Show, Eq, Generic, Typeable)
465594
466−newtype SearchAliasRouting = SearchAliasRouting (NonEmpty RoutingValue) deriving (Show, Eq, Typeable)
595+newtype SearchAliasRouting = SearchAliasRouting (NonEmpty RoutingValue) deriving (Show, Eq, Generic, Typeable)
467596
468−newtype IndexAliasRouting = IndexAliasRouting RoutingValue deriving (Show, Eq, ToJSON, FromJSON, Typeable)
597+newtype IndexAliasRouting = IndexAliasRouting RoutingValue deriving (Show, Eq, Generic, ToJSON, FromJSON, Typeable)
469598
470−newtype RoutingValue = RoutingValue { routingValue :: Text } deriving (Show, Eq, ToJSON, FromJSON, Typeable)
599+newtype RoutingValue = RoutingValue { routingValue :: Text } deriving (Show, Eq, Generic, ToJSON, FromJSON, Typeable)
471600
472−newtype IndexAliasesSummary = IndexAliasesSummary { indexAliasesSummary :: [IndexAliasSummary] } deriving (Show, Eq, Typeable)
601+newtype IndexAliasesSummary = IndexAliasesSummary { indexAliasesSummary :: [IndexAliasSummary] } deriving (Show, Eq, Generic, Typeable)
473602
474603{-| 'IndexAliasSummary' is a summary of an index alias configured for a server. -}
475604data IndexAliasSummary = IndexAliasSummary { indexAliasSummaryAlias :: IndexAlias
476− , indexAliasSummaryCreate :: IndexAliasCreate} deriving (Show, Eq)
605+ , indexAliasSummaryCreate :: IndexAliasCreate} deriving (Show, Eq, Generic, Typeable)
477606
478607{-| 'DocVersion' is an integer version number for a document between 1
479608and 9.2e+18 used for <<https://www.elastic.co/guide/en/elasticsearch/guide/current/optimistic-concurrency-control.html optimistic concurrency control>>.
480609-}
481610newtype DocVersion = DocVersion {
482611 docVersionNumber :: Int
483− } deriving (Eq, Show, Ord, ToJSON)
612+ } deriving (Eq, Show, Generic, Ord, ToJSON)
484613
485614-- | Smart constructor for in-range doc version
486615mkDocVersion :: Int -> Maybe DocVersion
@@ -494,7 +623,7 @@ mkDocVersion i
494623own version numbers instead of ones from ES.
495624-}
496625newtype ExternalDocVersion = ExternalDocVersion DocVersion
497− deriving (Eq, Show, Ord, Bounded, Enum, ToJSON)
626+ deriving (Eq, Show, Generic, Ord, Bounded, Enum, ToJSON)
498627
499628{-| 'VersionControl' is specified when indexing documents as a
500629optimistic concurrency control.
@@ -528,12 +657,12 @@ data VersionControl = NoVersionControl
528657 -- given version will be the new version. This is
529658 -- typically used for correcting errors. Use with
530659 -- care, as this could result in data loss.
531− deriving (Show, Eq, Ord)
660+ deriving (Show, Eq, Generic, Ord)
532661
533662{-| 'DocumentParent' is used to specify a parent document.
534663-}
535664newtype DocumentParent = DocumentParent DocId
536− deriving (Eq, Show)
665+ deriving (Eq, Show, Generic, Typeable)
537666
538667{-| 'IndexDocumentSettings' are special settings supplied when indexing
539668a document. For the best backwards compatiblity when new fields are
@@ -542,7 +671,7 @@ added, you should probably prefer to start with 'defaultIndexDocumentSettings'
542671data IndexDocumentSettings =
543672 IndexDocumentSettings { idsVersionControl :: VersionControl
544673 , idsParent :: Maybe DocumentParent
545− } deriving (Eq, Show)
674+ } deriving (Eq, Show, Generic, Typeable)
546675
547676{-| Reasonable default settings. Chooses no version control and no parent.
548677-}
@@ -562,7 +691,7 @@ type Sort = [SortSpec]
562691<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-sort.html#search-request-sort>
563692-}
564693data SortSpec = DefaultSortSpec DefaultSort
565− | GeoDistanceSortSpec SortOrder GeoPoint DistanceUnit deriving (Eq, Show)
694+ | GeoDistanceSortSpec SortOrder GeoPoint DistanceUnit deriving (Eq, Show, Generic, Typeable)
566695
567696{-| 'DefaultSort' is usually the kind of 'SortSpec' you'll want. There's a
568697 'mkSort' convenience function for when you want to specify only the most
@@ -577,7 +706,7 @@ data DefaultSort =
577706 , ignoreUnmapped :: Bool
578707 , sortMode :: Maybe SortMode
579708 , missingSort :: Maybe Missing
580− , nestedFilter :: Maybe Filter } deriving (Eq, Show)
709+ , nestedFilter :: Maybe Filter } deriving (Eq, Show, Generic, Typeable)
581710
582711{-| 'SortOrder' is 'Ascending' or 'Descending', as you might expect. These get
583712 encoded into "asc" or "desc" when turned into JSON.
@@ -585,7 +714,7 @@ data DefaultSort =
585714<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-sort.html#search-request-sort>
586715-}
587716data SortOrder = Ascending
588− | Descending deriving (Eq, Show)
717+ | Descending deriving (Eq, Show, Generic, Typeable)
589718
590719{-| 'Missing' prescribes how to handle missing fields. A missing field can be
591720 sorted last, first, or using a custom value as a substitute.
@@ -594,7 +723,7 @@ data SortOrder = Ascending
594723-}
595724data Missing = LastMissing
596725 | FirstMissing
597− | CustomMissing Text deriving (Eq, Show)
726+ | CustomMissing Text deriving (Eq, Show, Generic, Typeable)
598727
599728{-| 'SortMode' prescribes how to handle sorting array/multi-valued fields.
600729
@@ -603,7 +732,7 @@ http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-req
603732data SortMode = SortMin
604733 | SortMax
605734 | SortSum
606− | SortAvg deriving (Eq, Show)
735+ | SortAvg deriving (Eq, Show, Generic, Typeable)
607736
608737{-| 'mkSort' defaults everything but the 'FieldName' and the 'SortOrder' so
609738 that you can concisely describe the usual kind of 'SortSpec's you want.
@@ -625,19 +754,19 @@ type PrefixValue = Text
625754{-| 'BooleanOperator' is the usual And/Or operators with an ES compatible
626755 JSON encoding baked in. Used all over the place.
627756-}
628−data BooleanOperator = And | Or deriving (Eq, Show, Typeable)
757+data BooleanOperator = And | Or deriving (Eq, Show, Generic, Typeable)
629758
630759{-| 'ShardCount' is part of 'IndexSettings'
631760-}
632−newtype ShardCount = ShardCount Int deriving (Eq, Show, Generic, ToJSON, FromJSON, Typeable)
761+newtype ShardCount = ShardCount Int deriving (Eq, Show, Generic, ToJSON, Typeable)
633762
634763{-| 'ReplicaCount' is part of 'IndexSettings'
635764-}
636−newtype ReplicaCount = ReplicaCount Int deriving (Eq, Show, Generic, ToJSON, FromJSON, Typeable)
765+newtype ReplicaCount = ReplicaCount Int deriving (Eq, Show, Generic, ToJSON, Typeable)
637766
638767{-| 'Server' is used with the client functions to point at the ES instance
639768-}
640−newtype Server = Server Text deriving (Eq, Show)
769+newtype Server = Server Text deriving (Eq, Show, Generic, Typeable)
641770
642771{-| 'IndexName' is used to describe which index to query/create/delete
643772-}
@@ -669,27 +798,27 @@ newtype QueryString = QueryString Text deriving (Eq, Generic, Show, ToJSON, From
669798{-| 'FieldName' is used all over the place wherever a specific field within
670799 a document needs to be specified, usually in 'Query's or 'Filter's.
671800-}
672−newtype FieldName = FieldName Text deriving (Eq, Show, ToJSON, FromJSON, Typeable)
801+newtype FieldName = FieldName Text deriving (Eq, Show, Generic, ToJSON, FromJSON, Typeable)
673802
674803
675804{-| 'Script' is often used in place of 'FieldName' to specify more
676805complex ways of extracting a value from a document.
677806-}
678−newtype Script = Script { scriptText :: Text } deriving (Eq, Show)
807+newtype Script = Script { scriptText :: Text } deriving (Eq, Show, Generic, Typeable)
679808
680809{-| 'CacheName' is used in 'RegexpFilter' for describing the
681810 'CacheKey' keyed caching behavior.
682811-}
683−newtype CacheName = CacheName Text deriving (Eq, Show, ToJSON, FromJSON, Typeable)
812+newtype CacheName = CacheName Text deriving (Eq, Show, Generic, ToJSON, FromJSON, Typeable)
684813
685814{-| 'CacheKey' is used in 'RegexpFilter' to key regex caching.
686815-}
687816newtype CacheKey =
688− CacheKey Text deriving (Eq, Show, ToJSON, FromJSON, Typeable)
817+ CacheKey Text deriving (Eq, Show, Generic, ToJSON, FromJSON, Typeable)
689818newtype Existence =
690− Existence Bool deriving (Eq, Show, ToJSON, FromJSON, Typeable)
819+ Existence Bool deriving (Eq, Show, Generic, ToJSON, FromJSON, Typeable)
691820newtype NullValue =
692− NullValue Bool deriving (Eq, Show, ToJSON, FromJSON, Typeable)
821+ NullValue Bool deriving (Eq, Show, Generic, ToJSON, FromJSON, Typeable)
693822newtype CutoffFrequency =
694823 CutoffFrequency Double deriving (Eq, Show, Generic, ToJSON, FromJSON, Typeable)
695824newtype Analyzer =
@@ -773,14 +902,17 @@ newtype PhraseSlop = PhraseSlop Int deriving (Eq, Show, Generic, ToJSO
773902newtype MinDocFrequency = MinDocFrequency Int deriving (Eq, Show, Generic, ToJSON, FromJSON, Typeable)
774903newtype MaxDocFrequency = MaxDocFrequency Int deriving (Eq, Show, Generic, ToJSON, FromJSON, Typeable)
775904
905+-- | Newtype wrapper to parse ES's concerning tendency to in some APIs return a floating point number of milliseconds since epoch ಠ_ಠ
906+newtype POSIXMS = POSIXMS { posixMS :: UTCTime }
907+
776908{-| 'unpackId' is a silly convenience function that gets used once.
777909-}
778910unpackId :: DocId -> Text
779911unpackId (DocId docId) = docId
780912
781913type TrackSortScores = Bool
782−newtype From = From Int deriving (Eq, Show, ToJSON)
783−newtype Size = Size Int deriving (Eq, Show, ToJSON)
914+newtype From = From Int deriving (Eq, Show, Generic, ToJSON)
915+newtype Size = Size Int deriving (Eq, Show, Generic, ToJSON)
784916
785917data Search = Search { queryBody :: Maybe Query
786918 , filterBody :: Maybe Filter
@@ -793,7 +925,7 @@ data Search = Search { queryBody :: Maybe Query
793925 , size :: Size
794926 , searchType :: SearchType
795927 , fields :: Maybe [FieldName]
796− , source :: Maybe Source } deriving (Eq, Show)
928+ , source :: Maybe Source } deriving (Eq, Show, Generic, Typeable)
797929
798930data SearchType = SearchTypeQueryThenFetch
799931 | SearchTypeDfsQueryThenFetch
@@ -801,40 +933,40 @@ data SearchType = SearchTypeQueryThenFetch
801933 | SearchTypeScan
802934 | SearchTypeQueryAndFetch
803935 | SearchTypeDfsQueryAndFetch
804− deriving (Eq, Show)
936+ deriving (Eq, Show, Generic, Typeable)
805937
806938data Source =
807939 NoSource
808940 | SourcePatterns PatternOrPatterns
809941 | SourceIncludeExclude Include Exclude
810− deriving (Show, Eq)
942+ deriving (Show, Eq, Generic, Typeable)
811943
812944data PatternOrPatterns = PopPattern Pattern
813− | PopPatterns [Pattern] deriving (Eq, Show)
945+ | PopPatterns [Pattern] deriving (Eq, Show, Generic, Typeable)
814946
815−data Include = Include [Pattern] deriving (Eq, Show)
816−data Exclude = Exclude [Pattern] deriving (Eq, Show)
947+data Include = Include [Pattern] deriving (Eq, Show, Generic, Typeable)
948+data Exclude = Exclude [Pattern] deriving (Eq, Show, Generic, Typeable)
817949
818−newtype Pattern = Pattern Text deriving (Eq, Show)
950+newtype Pattern = Pattern Text deriving (Eq, Show, Generic, Typeable)
819951
820952data Highlights = Highlights { globalsettings :: Maybe HighlightSettings
821953 , highlightFields :: [FieldHighlight]
822− } deriving (Show, Eq)
954+ } deriving (Show, Eq, Generic, Typeable)
823955
824956data FieldHighlight = FieldHighlight FieldName (Maybe HighlightSettings)
825− deriving (Show, Eq)
957+ deriving (Show, Eq, Generic, Typeable)
826958
827959
828960data HighlightSettings = Plain PlainHighlight
829961 | Postings PostingsHighlight
830962 | FastVector FastVectorHighlight
831− deriving (Show, Eq)
963+ deriving (Show, Eq, Generic, Typeable)
832964data PlainHighlight =
833965 PlainHighlight { plainCommon :: Maybe CommonHighlight
834− , plainNonPost :: Maybe NonPostings } deriving (Show, Eq)
966+ , plainNonPost :: Maybe NonPostings } deriving (Show, Eq, Generic, Typeable)
835967
836968 -- This requires that index_options are set to 'offset' in the mapping.
837−data PostingsHighlight = PostingsHighlight (Maybe CommonHighlight) deriving (Show, Eq)
969+data PostingsHighlight = PostingsHighlight (Maybe CommonHighlight) deriving (Show, Eq, Generic, Typeable)
838970
839971-- This requires that term_vector is set to 'with_positions_offsets' in the mapping.
840972data FastVectorHighlight =
@@ -845,7 +977,7 @@ data FastVectorHighlight =
845977 , fragmentOffset :: Maybe Int
846978 , matchedFields :: [Text]
847979 , phraseLimit :: Maybe Int
848− } deriving (Show, Eq)
980+ } deriving (Show, Eq, Generic, Typeable)
849981
850982data CommonHighlight =
851983 CommonHighlight { order :: Maybe Text
@@ -855,21 +987,21 @@ data CommonHighlight =
855987 , noMatchSize :: Maybe Int
856988 , highlightQuery :: Maybe Query
857989 , requireFieldMatch :: Maybe Bool
858− } deriving (Show, Eq)
990+ } deriving (Show, Eq, Generic, Typeable)
859991
860992-- Settings that are only applicable to FastVector and Plain highlighters.
861993data NonPostings =
862994 NonPostings { fragmentSize :: Maybe Int
863− , numberOfFragments :: Maybe Int} deriving (Show, Eq)
995+ , numberOfFragments :: Maybe Int} deriving (Show, Eq, Generic, Typeable)
864996
865997data HighlightEncoder = DefaultEncoder
866998 | HTMLEncoder
867− deriving (Show, Eq)
999+ deriving (Show, Eq, Generic, Typeable)
8681000
8691001-- NOTE: Should the tags use some kind of HTML type, rather than Text?
8701002data HighlightTag = TagSchema Text
8711003 | CustomTags ([Text], [Text]) -- Only uses more than the first value in the lists if fvh
872− deriving (Show, Eq)
1004+ deriving (Show, Eq, Generic, Typeable)
8731005
8741006
8751007data Query =
@@ -900,19 +1032,19 @@ data Query =
9001032 | QuerySimpleQueryStringQuery SimpleQueryStringQuery
9011033 | QueryRangeQuery RangeQuery
9021034 | QueryRegexpQuery RegexpQuery
903− deriving (Eq, Show, Typeable)
1035+ deriving (Eq, Show, Generic, Typeable)
9041036
9051037data RegexpQuery =
9061038 RegexpQuery { regexpQueryField :: FieldName
9071039 , regexpQuery :: Regexp
9081040 , regexpQueryFlags :: RegexpFlags
9091041 , regexpQueryBoost :: Maybe Boost
910− } deriving (Eq, Show, Typeable)
1042+ } deriving (Eq, Show, Generic, Typeable)
9111043
9121044data RangeQuery =
9131045 RangeQuery { rangeQueryField :: FieldName
9141046 , rangeQueryRange :: RangeValue
915− , rangeQueryBoost :: Boost } deriving (Eq, Show, Typeable)
1047+ , rangeQueryBoost :: Boost } deriving (Eq, Show, Generic, Typeable)
9161048
9171049mkRangeQuery :: FieldName -> RangeValue -> RangeQuery
9181050mkRangeQuery f r = RangeQuery f r (Boost 1.0)
@@ -926,7 +1058,7 @@ data SimpleQueryStringQuery =
9261058 , simpleQueryStringFlags :: Maybe (NonEmpty SimpleQueryFlag)
9271059 , simpleQueryStringLowercaseExpanded :: Maybe LowercaseExpanded
9281060 , simpleQueryStringLocale :: Maybe Locale
929− } deriving (Eq, Show, Typeable)
1061+ } deriving (Eq, Show, Generic, Typeable)
9301062
9311063data SimpleQueryFlag =
9321064 SimpleQueryAll
@@ -940,7 +1072,7 @@ data SimpleQueryFlag =
9401072 | SimpleQueryWhitespace
9411073 | SimpleQueryFuzzy
9421074 | SimpleQueryNear
943− | SimpleQuerySlop deriving (Eq, Show, Typeable)
1075+ | SimpleQuerySlop deriving (Eq, Show, Generic, Typeable)
9441076
9451077-- use_dis_max and tie_breaker when fields are plural?
9461078data QueryStringQuery =
@@ -962,7 +1094,7 @@ data QueryStringQuery =
9621094 , queryStringMinimumShouldMatch :: Maybe MinimumMatch
9631095 , queryStringLenient :: Maybe Lenient
9641096 , queryStringLocale :: Maybe Locale
965− } deriving (Eq, Show, Typeable)
1097+ } deriving (Eq, Show, Generic, Typeable)
9661098
9671099mkQueryStringQuery :: QueryString -> QueryStringQuery
9681100mkQueryStringQuery qs =
@@ -973,19 +1105,19 @@ mkQueryStringQuery qs =
9731105 Nothing Nothing
9741106
9751107data FieldOrFields = FofField FieldName
976− | FofFields (NonEmpty FieldName) deriving (Eq, Show, Typeable)
1108+ | FofFields (NonEmpty FieldName) deriving (Eq, Show, Generic, Typeable)
9771109
9781110data PrefixQuery =
9791111 PrefixQuery
9801112 { prefixQueryField :: FieldName
9811113 , prefixQueryPrefixValue :: Text
982− , prefixQueryBoost :: Maybe Boost } deriving (Eq, Show, Typeable)
1114+ , prefixQueryBoost :: Maybe Boost } deriving (Eq, Show, Generic, Typeable)
9831115
9841116data NestedQuery =
9851117 NestedQuery
9861118 { nestedQueryPath :: QueryPath
9871119 , nestedQueryScoreType :: ScoreType
988− , nestedQuery :: Query } deriving (Eq, Show, Typeable)
1120+ , nestedQuery :: Query } deriving (Eq, Show, Generic, Typeable)
9891121
9901122data MoreLikeThisFieldQuery =
9911123 MoreLikeThisFieldQuery
@@ -1003,7 +1135,7 @@ data MoreLikeThisFieldQuery =
10031135 , moreLikeThisFieldBoostTerms :: Maybe BoostTerms
10041136 , moreLikeThisFieldBoost :: Maybe Boost
10051137 , moreLikeThisFieldAnalyzer :: Maybe Analyzer
1006− } deriving (Eq, Show, Typeable)
1138+ } deriving (Eq, Show, Generic, Typeable)
10071139
10081140data MoreLikeThisQuery =
10091141 MoreLikeThisQuery
@@ -1021,32 +1153,32 @@ data MoreLikeThisQuery =
10211153 , moreLikeThisBoostTerms :: Maybe BoostTerms
10221154 , moreLikeThisBoost :: Maybe Boost
10231155 , moreLikeThisAnalyzer :: Maybe Analyzer
1024− } deriving (Eq, Show, Typeable)
1156+ } deriving (Eq, Show, Generic, Typeable)
10251157
10261158data IndicesQuery =
10271159 IndicesQuery
10281160 { indicesQueryIndices :: [IndexName]
10291161 , indicesQuery :: Query
10301162 -- default "all"
1031− , indicesQueryNoMatch :: Maybe Query } deriving (Eq, Show, Typeable)
1163+ , indicesQueryNoMatch :: Maybe Query } deriving (Eq, Show, Generic, Typeable)
10321164
10331165data HasParentQuery =
10341166 HasParentQuery
10351167 { hasParentQueryType :: TypeName
10361168 , hasParentQuery :: Query
1037− , hasParentQueryScoreType :: Maybe ScoreType } deriving (Eq, Show, Typeable)
1169+ , hasParentQueryScoreType :: Maybe ScoreType } deriving (Eq, Show, Generic, Typeable)
10381170
10391171data HasChildQuery =
10401172 HasChildQuery
10411173 { hasChildQueryType :: TypeName
10421174 , hasChildQuery :: Query
1043− , hasChildQueryScoreType :: Maybe ScoreType } deriving (Eq, Show, Typeable)
1175+ , hasChildQueryScoreType :: Maybe ScoreType } deriving (Eq, Show, Generic, Typeable)
10441176
10451177data ScoreType =
10461178 ScoreTypeMax
10471179 | ScoreTypeSum
10481180 | ScoreTypeAvg
1049− | ScoreTypeNone deriving (Eq, Show, Typeable)
1181+ | ScoreTypeNone deriving (Eq, Show, Generic, Typeable)
10501182
10511183data FuzzyQuery =
10521184 FuzzyQuery { fuzzyQueryField :: FieldName
@@ -1055,7 +1187,7 @@ data FuzzyQuery =
10551187 , fuzzyQueryMaxExpansions :: MaxExpansions
10561188 , fuzzyQueryFuzziness :: Fuzziness
10571189 , fuzzyQueryBoost :: Maybe Boost
1058− } deriving (Eq, Show, Typeable)
1190+ } deriving (Eq, Show, Generic, Typeable)
10591191
10601192data FuzzyLikeFieldQuery =
10611193 FuzzyLikeFieldQuery
@@ -1068,7 +1200,7 @@ data FuzzyLikeFieldQuery =
10681200 , fuzzyLikeFieldPrefixLength :: PrefixLength
10691201 , fuzzyLikeFieldBoost :: Boost
10701202 , fuzzyLikeFieldAnalyzer :: Maybe Analyzer
1071− } deriving (Eq, Show, Typeable)
1203+ } deriving (Eq, Show, Generic, Typeable)
10721204
10731205data FuzzyLikeThisQuery =
10741206 FuzzyLikeThisQuery
@@ -1080,19 +1212,19 @@ data FuzzyLikeThisQuery =
10801212 , fuzzyLikePrefixLength :: PrefixLength
10811213 , fuzzyLikeBoost :: Boost
10821214 , fuzzyLikeAnalyzer :: Maybe Analyzer
1083− } deriving (Eq, Show, Typeable)
1215+ } deriving (Eq, Show, Generic, Typeable)
10841216
10851217data FilteredQuery =
10861218 FilteredQuery
10871219 { filteredQuery :: Query
1088− , filteredFilter :: Filter } deriving (Eq, Show, Typeable)
1220+ , filteredFilter :: Filter } deriving (Eq, Show, Generic, Typeable)
10891221
10901222data DisMaxQuery =
10911223 DisMaxQuery { disMaxQueries :: [Query]
10921224 -- default 0.0
10931225 , disMaxTiebreaker :: Tiebreaker
10941226 , disMaxBoost :: Maybe Boost
1095− } deriving (Eq, Show, Typeable)
1227+ } deriving (Eq, Show, Generic, Typeable)
10961228
10971229data MatchQuery =
10981230 MatchQuery { matchQueryField :: FieldName
@@ -1104,7 +1236,7 @@ data MatchQuery =
11041236 , matchQueryAnalyzer :: Maybe Analyzer
11051237 , matchQueryMaxExpansions :: Maybe MaxExpansions
11061238 , matchQueryLenient :: Maybe Lenient
1107− , matchQueryBoost :: Maybe Boost } deriving (Eq, Show, Typeable)
1239+ , matchQueryBoost :: Maybe Boost } deriving (Eq, Show, Generic, Typeable)
11081240
11091241{-| 'mkMatchQuery' is a convenience function that defaults the less common parameters,
11101242 enabling you to provide only the 'FieldName' and 'QueryString' to make a 'MatchQuery'
@@ -1114,7 +1246,7 @@ mkMatchQuery field query = MatchQuery field query Or ZeroTermsNone Nothing Nothi
11141246
11151247data MatchQueryType =
11161248 MatchPhrase
1117− | MatchPhrasePrefix deriving (Eq, Show, Typeable)
1249+ | MatchPhrasePrefix deriving (Eq, Show, Generic, Typeable)
11181250
11191251data MultiMatchQuery =
11201252 MultiMatchQuery { multiMatchQueryFields :: [FieldName]
@@ -1126,7 +1258,7 @@ data MultiMatchQuery =
11261258 , multiMatchQueryCutoffFrequency :: Maybe CutoffFrequency
11271259 , multiMatchQueryAnalyzer :: Maybe Analyzer
11281260 , multiMatchQueryMaxExpansions :: Maybe MaxExpansions
1129− , multiMatchQueryLenient :: Maybe Lenient } deriving (Eq, Show, Typeable)
1261+ , multiMatchQueryLenient :: Maybe Lenient } deriving (Eq, Show, Generic, Typeable)
11301262
11311263{-| 'mkMultiMatchQuery' is a convenience function that defaults the less common parameters,
11321264 enabling you to provide only the list of 'FieldName's and 'QueryString' to
@@ -1143,7 +1275,7 @@ data MultiMatchQueryType =
11431275 | MultiMatchMostFields
11441276 | MultiMatchCrossFields
11451277 | MultiMatchPhrase
1146− | MultiMatchPhrasePrefix deriving (Eq, Show, Typeable)
1278+ | MultiMatchPhrasePrefix deriving (Eq, Show, Generic, Typeable)
11471279
11481280data BoolQuery =
11491281 BoolQuery { boolQueryMustMatch :: [Query]
@@ -1152,7 +1284,7 @@ data BoolQuery =
11521284 , boolQueryMinimumShouldMatch :: Maybe MinimumMatch
11531285 , boolQueryBoost :: Maybe Boost
11541286 , boolQueryDisableCoord :: Maybe DisableCoord
1155− } deriving (Eq, Show, Typeable)
1287+ } deriving (Eq, Show, Generic, Typeable)
11561288
11571289mkBoolQuery :: [Query] -> [Query] -> [Query] -> BoolQuery
11581290mkBoolQuery must mustNot should =
@@ -1161,7 +1293,7 @@ mkBoolQuery must mustNot should =
11611293data BoostingQuery =
11621294 BoostingQuery { positiveQuery :: Query
11631295 , negativeQuery :: Query
1164− , negativeBoost :: Boost } deriving (Eq, Show, Typeable)
1296+ , negativeBoost :: Boost } deriving (Eq, Show, Generic, Typeable)
11651297
11661298data CommonTermsQuery =
11671299 CommonTermsQuery { commonField :: FieldName
@@ -1173,16 +1305,16 @@ data CommonTermsQuery =
11731305 , commonBoost :: Maybe Boost
11741306 , commonAnalyzer :: Maybe Analyzer
11751307 , commonDisableCoord :: Maybe DisableCoord
1176− } deriving (Eq, Show, Typeable)
1308+ } deriving (Eq, Show, Generic, Typeable)
11771309
11781310data CommonMinimumMatch =
11791311 CommonMinimumMatchHighLow MinimumMatchHighLow
11801312 | CommonMinimumMatch MinimumMatch
1181− deriving (Eq, Show, Typeable)
1313+ deriving (Eq, Show, Generic, Typeable)
11821314
11831315data MinimumMatchHighLow =
11841316 MinimumMatchHighLow { lowFreq :: MinimumMatch
1185− , highFreq :: MinimumMatch } deriving (Eq, Show, Typeable)
1317+ , highFreq :: MinimumMatch } deriving (Eq, Show, Generic, Typeable)
11861318
11871319data Filter = AndFilter [Filter] Cache
11881320 | OrFilter [Filter] Cache
@@ -1202,36 +1334,36 @@ data Filter = AndFilter [Filter] Cache
12021334 | RangeFilter FieldName RangeValue RangeExecution Cache
12031335 | RegexpFilter FieldName Regexp RegexpFlags CacheName Cache CacheKey
12041336 | TermFilter Term Cache
1205− deriving (Eq, Show, Typeable)
1337+ deriving (Eq, Show, Generic, Typeable)
12061338
12071339data ZeroTermsQuery = ZeroTermsNone
1208− | ZeroTermsAll deriving (Eq, Show, Typeable)
1340+ | ZeroTermsAll deriving (Eq, Show, Generic, Typeable)
12091341
12101342data RangeExecution = RangeExecutionIndex
1211− | RangeExecutionFielddata deriving (Eq, Show, Typeable)
1343+ | RangeExecutionFielddata deriving (Eq, Show, Generic, Typeable)
12121344
1213−newtype Regexp = Regexp Text deriving (Eq, Show, FromJSON)
1345+newtype Regexp = Regexp Text deriving (Eq, Show, Generic, Typeable, FromJSON)
12141346
12151347data RegexpFlags = AllRegexpFlags
12161348 | NoRegexpFlags
1217− | SomeRegexpFlags (NonEmpty RegexpFlag) deriving (Eq, Show)
1349+ | SomeRegexpFlags (NonEmpty RegexpFlag) deriving (Eq, Show, Generic, Typeable)
12181350
12191351data RegexpFlag = AnyString
12201352 | Automaton
12211353 | Complement
12221354 | Empty
12231355 | Intersection
1224− | Interval deriving (Eq, Show)
1356+ | Interval deriving (Eq, Show, Generic, Typeable)
12251357
1226−newtype LessThan = LessThan Double deriving (Eq, Show)
1227−newtype LessThanEq = LessThanEq Double deriving (Eq, Show)
1228−newtype GreaterThan = GreaterThan Double deriving (Eq, Show)
1229−newtype GreaterThanEq = GreaterThanEq Double deriving (Eq, Show)
1358+newtype LessThan = LessThan Double deriving (Eq, Show, Generic, Typeable)
1359+newtype LessThanEq = LessThanEq Double deriving (Eq, Show, Generic, Typeable)
1360+newtype GreaterThan = GreaterThan Double deriving (Eq, Show, Generic, Typeable)
1361+newtype GreaterThanEq = GreaterThanEq Double deriving (Eq, Show, Generic, Typeable)
12301362
1231−newtype LessThanD = LessThanD UTCTime deriving (Eq, Show)
1232−newtype LessThanEqD = LessThanEqD UTCTime deriving (Eq, Show)
1233−newtype GreaterThanD = GreaterThanD UTCTime deriving (Eq, Show)
1234−newtype GreaterThanEqD = GreaterThanEqD UTCTime deriving (Eq, Show)
1363+newtype LessThanD = LessThanD UTCTime deriving (Eq, Show, Generic, Typeable)
1364+newtype LessThanEqD = LessThanEqD UTCTime deriving (Eq, Show, Generic, Typeable)
1365+newtype GreaterThanD = GreaterThanD UTCTime deriving (Eq, Show, Generic, Typeable)
1366+newtype GreaterThanEqD = GreaterThanEqD UTCTime deriving (Eq, Show, Generic, Typeable)
12351367
12361368data RangeValue = RangeDateLte LessThanEqD
12371369 | RangeDateLt LessThanD
@@ -1249,7 +1381,7 @@ data RangeValue = RangeDateLte LessThanEqD
12491381 | RangeDoubleGteLte GreaterThanEq LessThanEq
12501382 | RangeDoubleGteLt GreaterThanEq LessThan
12511383 | RangeDoubleGtLte GreaterThan LessThanEq
1252− deriving (Eq, Show)
1384+ deriving (Eq, Show, Generic, Typeable)
12531385
12541386rangeValueToPair :: RangeValue -> [Pair]
12551387rangeValueToPair rv = case rv of
@@ -1271,33 +1403,33 @@ rangeValueToPair rv = case rv of
12711403 RangeDoubleGtLt (GreaterThan l) (LessThan g) -> ["gt" .= l, "lt" .= g]
12721404
12731405data Term = Term { termField :: Text
1274− , termValue :: Text } deriving (Eq, Show, Typeable)
1406+ , termValue :: Text } deriving (Eq, Show, Generic, Typeable)
12751407
12761408data BoolMatch = MustMatch Term Cache
12771409 | MustNotMatch Term Cache
1278− | ShouldMatch [Term] Cache deriving (Eq, Show, Typeable)
1410+ | ShouldMatch [Term] Cache deriving (Eq, Show, Generic, Typeable)
12791411
12801412-- "memory" or "indexed"
12811413data GeoFilterType = GeoFilterMemory
1282− | GeoFilterIndexed deriving (Eq, Show, Typeable)
1414+ | GeoFilterIndexed deriving (Eq, Show, Generic, Typeable)
12831415
12841416data LatLon = LatLon { lat :: Double
1285− , lon :: Double } deriving (Eq, Show, Typeable)
1417+ , lon :: Double } deriving (Eq, Show, Generic, Typeable)
12861418
12871419data GeoBoundingBox =
12881420 GeoBoundingBox { topLeft :: LatLon
1289− , bottomRight :: LatLon } deriving (Eq, Show, Typeable)
1421+ , bottomRight :: LatLon } deriving (Eq, Show, Generic, Typeable)
12901422
12911423data GeoBoundingBoxConstraint =
12921424 GeoBoundingBoxConstraint { geoBBField :: FieldName
12931425 , constraintBox :: GeoBoundingBox
12941426 , bbConstraintcache :: Cache
12951427 , geoType :: GeoFilterType
1296− } deriving (Eq, Show, Typeable)
1428+ } deriving (Eq, Show, Generic, Typeable)
12971429
12981430data GeoPoint =
12991431 GeoPoint { geoField :: FieldName
1300− , latLon :: LatLon} deriving (Eq, Show, Typeable)
1432+ , latLon :: LatLon} deriving (Eq, Show, Generic, Typeable)
13011433
13021434data DistanceUnit = Miles
13031435 | Yards
@@ -1307,22 +1439,22 @@ data DistanceUnit = Miles
13071439 | Meters
13081440 | Centimeters
13091441 | Millimeters
1310− | NauticalMiles deriving (Eq, Show, Typeable)
1442+ | NauticalMiles deriving (Eq, Show, Generic, Typeable)
13111443
13121444data DistanceType = Arc
13131445 | SloppyArc -- doesn't exist <1.0
1314− | Plane deriving (Eq, Show, Typeable)
1446+ | Plane deriving (Eq, Show, Generic, Typeable)
13151447
13161448data OptimizeBbox = OptimizeGeoFilterType GeoFilterType
1317− | NoOptimizeBbox deriving (Eq, Show, Typeable)
1449+ | NoOptimizeBbox deriving (Eq, Show, Generic, Typeable)
13181450
13191451data Distance =
13201452 Distance { coefficient :: Double
1321− , unit :: DistanceUnit } deriving (Eq, Show, Typeable)
1453+ , unit :: DistanceUnit } deriving (Eq, Show, Generic, Typeable)
13221454
13231455data DistanceRange =
13241456 DistanceRange { distanceFrom :: Distance
1325− , distanceTo :: Distance } deriving (Eq, Show)
1457+ , distanceTo :: Distance } deriving (Eq, Show, Generic, Typeable)
13261458
13271459data SearchResult a =
13281460 SearchResult { took :: Int
@@ -1330,16 +1462,16 @@ data SearchResult a =
13301462 , shards :: ShardResult
13311463 , searchHits :: SearchHits a
13321464 , aggregations :: Maybe AggregationResults
1333− , scrollId :: Maybe ScrollId } deriving (Eq, Show)
1465+ , scrollId :: Maybe ScrollId } deriving (Eq, Show, Generic, Typeable)
13341466
1335−type ScrollId = Text -- Fixme: Newtype
1467+newtype ScrollId = ScrollId Text deriving (Eq, Show, Generic, Ord, ToJSON, FromJSON)
13361468
13371469type Score = Maybe Double
13381470
13391471data SearchHits a =
13401472 SearchHits { hitsTotal :: Int
13411473 , maxScore :: Score
1342− , hits :: [Hit a] } deriving (Eq, Show)
1474+ , hits :: [Hit a] } deriving (Eq, Show, Generic, Typeable)
13431475
13441476
13451477instance Monoid (SearchHits a) where
@@ -1354,18 +1486,26 @@ data Hit a =
13541486 , hitDocId :: DocId
13551487 , hitScore :: Score
13561488 , hitSource :: Maybe a
1357− , hitHighlight :: Maybe HitHighlight } deriving (Eq, Show)
1489+ , hitHighlight :: Maybe HitHighlight } deriving (Eq, Show, Generic, Typeable)
13581490
13591491data ShardResult =
13601492 ShardResult { shardTotal :: Int
13611493 , shardsSuccessful :: Int
1362− , shardsFailed :: Int } deriving (Eq, Show, Generic)
1494+ , shardsFailed :: Int } deriving (Eq, Show, Generic, Typeable)
13631495
13641496type HitHighlight = M.Map Text [Text]
13651497
13661498showText :: Show a => a -> Text
13671499showText = T.pack . show
13681500
1501+readMay :: Read a => String -> Maybe a
1502+readMay s = case reads s of
1503+ (a, ""):_ -> Just a
1504+ _ -> Nothing
1505+
1506+parseReadText :: Read a => Text -> Parser a
1507+parseReadText = maybe mzero return . readMay . T.unpack
1508+
13691509type Aggregations = M.Map Text Aggregation
13701510
13711511emptyAggregations :: Aggregations
@@ -1375,19 +1515,19 @@ mkAggregations :: Text -> Aggregation -> Aggregations
13751515mkAggregations name aggregation = M.insert name aggregation emptyAggregations
13761516
13771517data TermOrder = TermOrder{ termSortField :: Text
1378− , termSortOrder :: SortOrder } deriving (Eq, Show)
1518+ , termSortOrder :: SortOrder } deriving (Eq, Show, Generic, Typeable)
13791519
13801520data TermInclusion = TermInclusion Text
1381− | TermPattern Text Text deriving (Eq, Show)
1521+ | TermPattern Text Text deriving (Eq, Show, Generic, Typeable)
13821522
13831523data CollectionMode = BreadthFirst
1384− | DepthFirst deriving (Eq, Show)
1524+ | DepthFirst deriving (Eq, Show, Generic, Typeable)
13851525
13861526data ExecutionHint = Ordinals
13871527 | GlobalOrdinals
13881528 | GlobalOrdinalsHash
13891529 | GlobalOrdinalsLowCardinality
1390− | Map deriving (Eq, Show)
1530+ | Map deriving (Eq, Show, Generic, Typeable)
13911531
13921532data TimeInterval = Weeks
13931533 | Days
@@ -1403,12 +1543,13 @@ data Interval = Year
14031543 | Hour
14041544 | Minute
14051545 | Second
1406− | FractionalInterval Float TimeInterval deriving (Eq, Show)
1546+ | FractionalInterval Float TimeInterval deriving (Eq, Show, Generic, Typeable)
14071547
14081548data Aggregation = TermsAgg TermsAggregation
14091549 | DateHistogramAgg DateHistogramAggregation
14101550 | ValueCountAgg ValueCountAggregation
1411− | FilterAgg FilterAggregation deriving (Eq, Show)
1551+ | FilterAgg FilterAggregation
1552+ | DateRangeAgg DateRangeAggregation deriving (Eq, Show, Generic, Typeable)
14121553
14131554
14141555data TermsAggregation = TermsAggregation { term :: Either Text Text
@@ -1421,7 +1562,7 @@ data TermsAggregation = TermsAggregation { term :: Either Text Text
14211562 , termCollectMode :: Maybe CollectionMode
14221563 , termExecutionHint :: Maybe ExecutionHint
14231564 , termAggs :: Maybe Aggregations
1424− } deriving (Eq, Show)
1565+ } deriving (Eq, Show, Generic, Typeable)
14251566
14261567data DateHistogramAggregation = DateHistogramAggregation { dateField :: FieldName
14271568 , dateInterval :: Interval
@@ -1432,15 +1573,45 @@ data DateHistogramAggregation = DateHistogramAggregation { dateField :: Fie
14321573 , datePreOffset :: Maybe Text
14331574 , datePostOffset :: Maybe Text
14341575 , dateAggs :: Maybe Aggregations
1435− } deriving (Eq, Show)
1576+ } deriving (Eq, Show, Generic, Typeable)
1577+
1578+
1579+data DateRangeAggregation = DateRangeAggregation { draField :: FieldName
1580+ , draFormat :: Maybe Text
1581+ , draRanges :: NonEmpty DateRangeAggRange
1582+ } deriving (Eq, Show, Generic, Typeable)
1583+
1584+data DateRangeAggRange = DateRangeFrom DateMathExpr
1585+ | DateRangeTo DateMathExpr
1586+ | DateRangeFromAndTo DateMathExpr DateMathExpr deriving (Eq, Show, Generic, Typeable)
1587+
1588+-- | See <https://www.elastic.co/guide/en/elasticsearch/reference/current/common-options.html#date-math> for more information.
1589+data DateMathExpr = DateMathExpr DateMathAnchor [DateMathModifier] deriving (Eq, Show, Generic, Typeable)
1590+
1591+
1592+-- | Starting point for a date range. This along with the 'DateMathModifiers' gets you the date ES will start from.
1593+data DateMathAnchor = DMNow
1594+ | DMDate Day deriving (Eq, Show, Generic, Typeable)
1595+
1596+data DateMathModifier = AddTime Int DateMathUnit
1597+ | SubtractTime Int DateMathUnit
1598+ | RoundDownTo DateMathUnit deriving (Eq, Show, Generic, Typeable)
1599+
1600+data DateMathUnit = DMYear
1601+ | DMMonth
1602+ | DMWeek
1603+ | DMDay
1604+ | DMHour
1605+ | DMMinute
1606+ | DMSecond deriving (Eq, Show, Generic, Typeable)
14361607
14371608-- | See <https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-valuecount-aggregation.html> for more information.
14381609data ValueCountAggregation = FieldValueCount FieldName
1439− | ScriptValueCount Script deriving (Eq, Show)
1610+ | ScriptValueCount Script deriving (Eq, Show, Generic, Typeable)
14401611
14411612-- | 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.
14421613data FilterAggregation = FilterAggregation { faFilter :: Filter
1443− , faAggs :: Maybe Aggregations} deriving (Eq, Show)
1614+ , faAggs :: Maybe Aggregations} deriving (Eq, Show, Generic, Typeable)
14441615
14451616mkTermsAggregation :: Text -> TermsAggregation
14461617mkTermsAggregation t = TermsAggregation (Left t) Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
@@ -1537,6 +1708,39 @@ instance ToJSON Aggregation where
15371708 toJSON (FilterAgg (FilterAggregation filt ags)) =
15381709 omitNulls [ "filter" .= filt
15391710 , "aggs" .= ags]
1711+ toJSON (DateRangeAgg a) = object [ "date_range" .= a
1712+ ]
1713+
1714+instance ToJSON DateRangeAggregation where
1715+ toJSON DateRangeAggregation {..} =
1716+ omitNulls [ "field" .= draField
1717+ , "format" .= draFormat
1718+ , "ranges" .= toList draRanges
1719+ ]
1720+
1721+instance ToJSON DateRangeAggRange where
1722+ toJSON (DateRangeFrom e) = object [ "from" .= e ]
1723+ toJSON (DateRangeTo e) = object [ "to" .= e ]
1724+ toJSON (DateRangeFromAndTo f t) = object [ "from" .= f, "to" .= t ]
1725+
1726+instance ToJSON DateMathExpr where
1727+ toJSON (DateMathExpr a mods) = String (fmtA a <> mconcat (fmtMod <$> mods))
1728+ where fmtA DMNow = "now"
1729+ fmtA (DMDate date) = case toGregorian date of
1730+ (y,m,d) -> showText y <> "-" <>
1731+ showText m <> "-" <>
1732+ showText d <> "||"
1733+ fmtMod (AddTime n u) = "+" <> showText n <> fmtU u
1734+ fmtMod (SubtractTime n u) = "-" <> showText n <> fmtU u
1735+ fmtMod (RoundDownTo u) = "/" <> fmtU u
1736+ fmtU DMYear = "y"
1737+ fmtU DMMonth = "M"
1738+ fmtU DMWeek = "w"
1739+ fmtU DMDay = "d"
1740+ fmtU DMHour = "h"
1741+ fmtU DMMinute = "m"
1742+ fmtU DMSecond = "s"
1743+
15401744
15411745type AggregationResults = M.Map Text Value
15421746
@@ -1557,6 +1761,14 @@ data DateHistogramResult = DateHistogramResult { dateKey :: Int
15571761 , dateDocCount :: Int
15581762 , dateHistogramAggs :: Maybe AggregationResults } deriving (Show)
15591763
1764+data DateRangeResult = DateRangeResult { dateRangeKey :: Text
1765+ , dateRangeFrom :: Maybe UTCTime
1766+ , dateRangeFromAsString :: Maybe Text
1767+ , dateRangeTo :: Maybe UTCTime
1768+ , dateRangeToAsString :: Maybe Text
1769+ , dateRangeDocCount :: Int
1770+ , dateRangeAggs :: Maybe AggregationResults } deriving (Show, Eq, Generic, Typeable)
1771+
15601772toTerms :: Text -> AggregationResults -> Maybe (Bucket TermsResult)
15611773toTerms t a = M.lookup t a >>= deserialize
15621774 where deserialize = parseMaybe parseJSON
@@ -1575,6 +1787,11 @@ instance BucketAggregation DateHistogramResult where
15751787 docCount = dateDocCount
15761788 aggs = dateHistogramAggs
15771789
1790+instance BucketAggregation DateRangeResult where
1791+ key = dateRangeKey
1792+ docCount = dateRangeDocCount
1793+ aggs = dateRangeAggs
1794+
15781795instance (FromJSON a, BucketAggregation a) => FromJSON (Bucket a) where
15791796 parseJSON (Object v) = Bucket <$>
15801797 v .: "buckets"
@@ -1595,6 +1812,22 @@ instance FromJSON DateHistogramResult where
15951812 v .:? "aggregations"
15961813 parseJSON _ = mempty
15971814
1815+instance FromJSON DateRangeResult where
1816+ parseJSON = withObject "DateRangeResult" parse
1817+ where parse v = DateRangeResult <$>
1818+ v .: "key" <*>
1819+ (fmap posixMS <$> v .:? "from") <*>
1820+ v .:? "from_as_string" <*>
1821+ (fmap posixMS <$> v .:? "to") <*>
1822+ v .:? "to_as_string" <*>
1823+ v .: "doc_count" <*>
1824+ v .:? "aggregations"
1825+
1826+instance FromJSON POSIXMS where
1827+ parseJSON = withScientific "POSIXMS" (return . parse)
1828+ where parse n = let n' = truncate n :: Integer
1829+ in POSIXMS (posixSecondsToUTCTime (fromInteger (n' `div` 1000)))
1830+
15981831instance Monoid Filter where
15991832 mempty = IdentityFilter
16001833 mappend a b = AndFilter [a, b] defaultCache
@@ -1890,7 +2123,7 @@ instance FromJSON Query where
18902123 <|> idsQuery `taggedWith` "ids"
18912124 <|> queryQueryStringQuery `taggedWith` "query_string"
18922125 <|> queryMatchQuery `taggedWith` "match"
1893− <|> queryMultiMatchQuery --TODO: is this a precedence issue?
2126+ <|> queryMultiMatchQuery
18942127 <|> queryBoolQuery `taggedWith` "bool"
18952128 <|> queryBoostingQuery `taggedWith` "boosting"
18962129 <|> queryCommonTermsQuery `taggedWith` "common"
@@ -2571,6 +2804,232 @@ instance ToJSON IndexSettings where
25712804 ]
25722805 ]
25732806
2807+instance FromJSON IndexSettings where
2808+ parseJSON = withObject "IndexSettings" parse
2809+ where parse o = do s <- o .: "settings"
2810+ i <- s .: "index"
2811+ IndexSettings <$> i .: "number_of_shards"
2812+ <*> i .: "number_of_replicas"
2813+
2814+instance ToJSON UpdatableIndexSetting where
2815+ toJSON (NumberOfReplicas x) = oPath ("index" :| ["number_of_replicas"]) x
2816+ toJSON (AutoExpandReplicas x) = oPath ("index" :| ["auto_expand_replicas"]) x
2817+ toJSON (RefreshInterval x) = oPath ("index" :| ["refresh_interval"]) (NominalDiffTimeJSON x)
2818+ toJSON (IndexConcurrency x) = oPath ("index" :| ["concurrency"]) x
2819+ toJSON (FailOnMergeFailure x) = oPath ("index" :| ["fail_on_merge_failure"]) x
2820+ toJSON (TranslogFlushThresholdOps x) = oPath ("index" :| ["translog", "flush_threshold_ops"]) x
2821+ toJSON (TranslogFlushThresholdSize x) = oPath ("index" :| ["translog", "flush_threshold_size"]) x
2822+ toJSON (TranslogFlushThresholdPeriod x) = oPath ("index" :| ["translog", "flush_threshold_period"]) (NominalDiffTimeJSON x)
2823+ toJSON (TranslogDisableFlush x) = oPath ("index" :| ["translog", "disable_flush"]) x
2824+ toJSON (CacheFilterMaxSize x) = oPath ("index" :| ["cache", "filter", "max_size"]) x
2825+ toJSON (CacheFilterExpire x) = oPath ("index" :| ["cache", "filter", "expire"]) (NominalDiffTimeJSON <$> x)
2826+ toJSON (GatewaySnapshotInterval x) = oPath ("index" :| ["gateway", "snapshot_interval"]) (NominalDiffTimeJSON x)
2827+ toJSON (RoutingAllocationInclude fs) = oPath ("index" :| ["routing", "allocation", "include"]) (attrFilterJSON fs)
2828+ toJSON (RoutingAllocationExclude fs) = oPath ("index" :| ["routing", "allocation", "exclude"]) (attrFilterJSON fs)
2829+ toJSON (RoutingAllocationRequire fs) = oPath ("index" :| ["routing", "allocation", "require"]) (attrFilterJSON fs)
2830+ toJSON (RoutingAllocationEnable x) = oPath ("index" :| ["routing", "allocation", "enable"]) x
2831+ toJSON (RoutingAllocationShardsPerNode x) = oPath ("index" :| ["routing", "allocation", "total_shards_per_node"]) x
2832+ toJSON (RecoveryInitialShards x) = oPath ("index" :| ["recovery", "initial_shards"]) x
2833+ toJSON (GCDeletes x) = oPath ("index" :| ["gc_deletes"]) (NominalDiffTimeJSON x)
2834+ toJSON (TTLDisablePurge x) = oPath ("index" :| ["ttl", "disable_purge"]) x
2835+ toJSON (TranslogFSType x) = oPath ("index" :| ["translog", "fs", "type"]) x
2836+ toJSON (IndexCompoundFormat x) = oPath ("index" :| ["compound_format"]) x
2837+ toJSON (IndexCompoundOnFlush x) = oPath ("index" :| ["compound_on_flush"]) x
2838+ toJSON (WarmerEnabled x) = oPath ("index" :| ["warmer", "enabled"]) x
2839+ toJSON (BlocksReadOnly x) = oPath ("blocks" :| ["read_only"]) x
2840+ toJSON (BlocksRead x) = oPath ("blocks" :| ["read"]) x
2841+ toJSON (BlocksWrite x) = oPath ("blocks" :| ["write"]) x
2842+ toJSON (BlocksMetaData x) = oPath ("blocks" :| ["metadata"]) x
2843+
2844+instance FromJSON UpdatableIndexSetting where
2845+ parseJSON = withObject "UpdatableIndexSetting" parse
2846+ where parse o = numberOfReplicas `taggedAt` ["index", "number_of_replicas"]
2847+ <|> autoExpandReplicas `taggedAt` ["index", "auto_expand_replicas"]
2848+ <|> refreshInterval `taggedAt` ["index", "refresh_interval"]
2849+ <|> indexConcurrency `taggedAt` ["index", "concurrency"]
2850+ <|> failOnMergeFailure `taggedAt` ["index", "fail_on_merge_failure"]
2851+ <|> translogFlushThresholdOps `taggedAt` ["index", "translog", "flush_threshold_ops"]
2852+ <|> translogFlushThresholdSize `taggedAt` ["index", "translog", "flush_threshold_size"]
2853+ <|> translogFlushThresholdPeriod `taggedAt` ["index", "translog", "flush_threshold_period"]
2854+ <|> translogDisableFlush `taggedAt` ["index", "translog", "disable_flush"]
2855+ <|> cacheFilterMaxSize `taggedAt` ["index", "cache", "filter", "max_size"]
2856+ <|> cacheFilterExpire `taggedAt` ["index", "cache", "filter", "expire"]
2857+ <|> gatewaySnapshotInterval `taggedAt` ["index", "gateway", "snapshot_interval"]
2858+ <|> routingAllocationInclude `taggedAt` ["index", "routing", "allocation", "include"]
2859+ <|> routingAllocationExclude `taggedAt` ["index", "routing", "allocation", "exclude"]
2860+ <|> routingAllocationRequire `taggedAt` ["index", "routing", "allocation", "require"]
2861+ <|> routingAllocationEnable `taggedAt` ["index", "routing", "allocation", "enable"]
2862+ <|> routingAllocationShardsPerNode `taggedAt` ["index", "routing", "allocation", "total_shards_per_node"]
2863+ <|> recoveryInitialShards `taggedAt` ["index", "recovery", "initial_shards"]
2864+ <|> gcDeletes `taggedAt` ["index", "gc_deletes"]
2865+ <|> ttlDisablePurge `taggedAt` ["index", "ttl", "disable_purge"]
2866+ <|> translogFSType `taggedAt` ["index", "translog", "fs", "type"]
2867+ <|> compoundFormat `taggedAt` ["index", "compound_format"]
2868+ <|> compoundOnFlush `taggedAt` ["index", "compound_on_flush"]
2869+ <|> warmerEnabled `taggedAt` ["index", "warmer", "enabled"]
2870+ <|> blocksReadOnly `taggedAt` ["blocks", "read_only"]
2871+ <|> blocksRead `taggedAt` ["blocks", "read"]
2872+ <|> blocksWrite `taggedAt` ["blocks", "write"]
2873+ <|> blocksMetaData `taggedAt` ["blocks", "metadata"]
2874+ where taggedAt f ks = taggedAt' f (Object o) ks
2875+ taggedAt' f v [] = f =<< (parseJSON v <|> (parseJSON =<< unStringlyTypeJSON v))
2876+ taggedAt' f v (k:ks) = withObject "Object" (\o -> do v' <- o .: k
2877+ taggedAt' f v' ks) v
2878+ numberOfReplicas = pure . NumberOfReplicas
2879+ autoExpandReplicas = pure . AutoExpandReplicas
2880+ refreshInterval = pure . RefreshInterval . ndtJSON
2881+ indexConcurrency = pure . IndexConcurrency
2882+ failOnMergeFailure = pure . FailOnMergeFailure
2883+ translogFlushThresholdOps = pure . TranslogFlushThresholdOps
2884+ translogFlushThresholdSize = pure . TranslogFlushThresholdSize
2885+ translogFlushThresholdPeriod = pure . TranslogFlushThresholdPeriod . ndtJSON
2886+ translogDisableFlush = pure . TranslogDisableFlush
2887+ cacheFilterMaxSize = pure . CacheFilterMaxSize
2888+ cacheFilterExpire = pure . CacheFilterExpire . fmap ndtJSON
2889+ gatewaySnapshotInterval = pure . GatewaySnapshotInterval . ndtJSON
2890+ routingAllocationInclude = fmap RoutingAllocationInclude . parseAttrFilter
2891+ routingAllocationExclude = fmap RoutingAllocationExclude . parseAttrFilter
2892+ routingAllocationRequire = fmap RoutingAllocationRequire . parseAttrFilter
2893+ routingAllocationEnable = pure . RoutingAllocationEnable
2894+ routingAllocationShardsPerNode = pure . RoutingAllocationShardsPerNode
2895+ recoveryInitialShards = pure . RecoveryInitialShards
2896+ gcDeletes = pure . GCDeletes . ndtJSON
2897+ ttlDisablePurge = pure . TTLDisablePurge
2898+ translogFSType = pure . TranslogFSType
2899+ compoundFormat = pure . IndexCompoundFormat
2900+ compoundOnFlush = pure . IndexCompoundOnFlush
2901+ warmerEnabled = pure . WarmerEnabled
2902+ blocksReadOnly = pure . BlocksReadOnly
2903+ blocksRead = pure . BlocksRead
2904+ blocksWrite = pure . BlocksWrite
2905+ blocksMetaData = pure . BlocksMetaData
2906+
2907+instance FromJSON IndexSettingsSummary where
2908+ parseJSON = withObject "IndexSettingsSummary" parse
2909+ where parse o = case HM.toList o of
2910+ [(ixn, v@(Object o'))] -> IndexSettingsSummary (IndexName ixn)
2911+ <$> parseJSON v
2912+ <*> (fmap (filter (not . redundant)) . parseSettings =<< o' .: "settings")
2913+ _ -> fail "Expected single-key object with index name"
2914+ redundant (NumberOfReplicas _) = True
2915+ redundant _ = False
2916+
2917+-- | For some reason in the settings API, all leaf values get returned
2918+-- as strings. This function attepmts to recover from this for all
2919+-- non-recursive JSON types. If nothing can be done or the same value
2920+-- would be return, it returns 'mzero'
2921+unStringlyTypeJSON :: MonadPlus m => Value -> m Value
2922+unStringlyTypeJSON (String "true") = return (Bool True)
2923+unStringlyTypeJSON (String "false") = return (Bool False)
2924+unStringlyTypeJSON (String "null") = return Null
2925+unStringlyTypeJSON (String t) = case readMay (T.unpack t) of
2926+ Just n -> return (Number n)
2927+ Nothing -> mzero
2928+unStringlyTypeJSON _ = mzero
2929+
2930+
2931+parseSettings :: Object -> Parser [UpdatableIndexSetting]
2932+parseSettings o = do
2933+ o' <- o .: "index"
2934+ -- slice the index object into singleton hashmaps and try to parse each
2935+ parses <- forM (HM.toList o') $ \(k, v) -> do
2936+ -- blocks are now nested into the "index" key, which is not how they're serialized
2937+ let atRoot = Object (HM.singleton k v)
2938+ let atIndex = Object (HM.singleton "index" atRoot)
2939+ optional (parseJSON atRoot <|> parseJSON atIndex)
2940+ return (catMaybes parses)
2941+
2942+oPath :: ToJSON a => NonEmpty Text -> a -> Value
2943+oPath (k :| []) v = object [k .= v]
2944+oPath (k:| (h:t)) v = object [k .= oPath (h :| t) v]
2945+
2946+attrFilterJSON :: NonEmpty NodeAttrFilter -> Value
2947+attrFilterJSON fs = object [ n .= T.intercalate "," (toList vs)
2948+ | NodeAttrFilter (NodeAttrName n) vs <- toList fs]
2949+
2950+parseAttrFilter :: Value -> Parser (NonEmpty NodeAttrFilter)
2951+parseAttrFilter = withObject "NonEmpty NodeAttrFilter" parse
2952+ where parse o = case HM.toList o of
2953+ [] -> fail "Expected non-empty list of NodeAttrFilters"
2954+ x:xs -> DT.mapM (uncurry parse') (x :| xs)
2955+ parse' n = withText "Text" $ \t ->
2956+ case T.splitOn "," t of
2957+ fv:fvs -> return (NodeAttrFilter (NodeAttrName n) (fv :| fvs))
2958+ [] -> fail "Expected non-empty list of filter values"
2959+
2960+instance ToJSON ReplicaBounds where
2961+ toJSON (ReplicasBounded a b) = String (showText a <> "-" <> showText b)
2962+ toJSON (ReplicasLowerBounded a) = String (showText a <> "-all")
2963+ toJSON ReplicasUnbounded = Bool False
2964+
2965+instance FromJSON ReplicaBounds where
2966+ parseJSON v = withText "ReplicaBounds" parseText v
2967+ <|> withBool "ReplicaBounds" parseBool v
2968+ where parseText t = case T.splitOn "-" t of
2969+ [a, "all"] -> ReplicasLowerBounded <$> parseReadText a
2970+ [a, b] -> ReplicasBounded <$> parseReadText a
2971+ <*> parseReadText b
2972+ _ -> fail ("Could not parse ReplicaBounds: " <> show t)
2973+ parseBool False = pure ReplicasUnbounded
2974+ parseBool _ = fail "ReplicasUnbounded cannot be represented with True"
2975+
2976+instance ToJSON AllocationPolicy where
2977+ toJSON AllocAll = String "all"
2978+ toJSON AllocPrimaries = String "primaries"
2979+ toJSON AllocNewPrimaries = String "new_primaries"
2980+ toJSON AllocNone = String "none"
2981+
2982+instance FromJSON AllocationPolicy where
2983+ parseJSON = withText "AllocationPolicy" parse
2984+ where parse "all" = pure AllocAll
2985+ parse "primaries" = pure AllocPrimaries
2986+ parse "new_primaries" = pure AllocNewPrimaries
2987+ parse "none" = pure AllocNone
2988+ parse t = fail ("Invlaid AllocationPolicy: " <> show t)
2989+
2990+instance ToJSON InitialShardCount where
2991+ toJSON QuorumShards = String "quorum"
2992+ toJSON QuorumMinus1Shards = String "quorum-1"
2993+ toJSON FullShards = String "full"
2994+ toJSON FullMinus1Shards = String "full-1"
2995+ toJSON (ExplicitShards x) = toJSON x
2996+
2997+instance FromJSON InitialShardCount where
2998+ parseJSON v = withText "InitialShardCount" parseText v
2999+ <|> ExplicitShards <$> parseJSON v
3000+ where parseText "quorum" = pure QuorumShards
3001+ parseText "quorum-1" = pure QuorumMinus1Shards
3002+ parseText "full" = pure FullShards
3003+ parseText "full-1" = pure FullMinus1Shards
3004+ parseText _ = mzero
3005+
3006+instance ToJSON FSType where
3007+ toJSON FSSimple = "simple"
3008+ toJSON FSBuffered = "buffered"
3009+
3010+instance FromJSON FSType where
3011+ parseJSON = withText "FSType" parse
3012+ where parse "simple" = pure FSSimple
3013+ parse "buffered" = pure FSBuffered
3014+ parse t = fail ("Invalid FSType: " <> show t)
3015+
3016+instance ToJSON CompoundFormat where
3017+ toJSON (CompoundFileFormat x) = Bool x
3018+ toJSON (MergeSegmentVsTotalIndex x) = toJSON x
3019+
3020+instance FromJSON CompoundFormat where
3021+ parseJSON v = CompoundFileFormat <$> parseJSON v
3022+ <|> MergeSegmentVsTotalIndex <$> parseJSON v
3023+
3024+instance ToJSON NominalDiffTimeJSON where
3025+ toJSON (NominalDiffTimeJSON t) = String (showText (round t :: Integer) <> "s")
3026+
3027+instance FromJSON NominalDiffTimeJSON where
3028+ parseJSON = withText "NominalDiffTime" parse
3029+ where parse t = case T.takeEnd 1 t of
3030+ "s" -> NominalDiffTimeJSON . fromInteger <$> parseReadText (T.dropEnd 1 t)
3031+ _ -> fail "Invalid or missing NominalDiffTime unit (expected s)"
3032+
25743033instance ToJSON IndexTemplate where
25753034 toJSON (IndexTemplate p s m) = merge
25763035 (object [ "template" .= p
@@ -2610,7 +3069,7 @@ instance FromJSON IndexAliasesSummary where
26103069 parseJSON = withObject "IndexAliasesSummary" parse
26113070 where parse o = IndexAliasesSummary . mconcat <$> mapM (uncurry go) (HM.toList o)
26123071 go ixn = withObject "index aliases" $ \ia -> do
2613− aliases <- ia .: "aliases"
3072+ aliases <- ia .:? "aliases" .!= mempty
26143073 forM (HM.toList aliases) $ \(aName, v) -> do
26153074 let indexAlias = IndexAlias (IndexName ixn) (IndexAliasName (IndexName aName))
26163075 IndexAliasSummary indexAlias <$> parseJSON v
@@ -3056,6 +3515,19 @@ instance FromJSON DocVersion where
30563515 i <- parseJSON v
30573516 maybe (fail "DocVersion out of range") return $ mkDocVersion i
30583517
3518+-- This insanity is because ES *sometimes* returns Replica/Shard counts as strings
3519+instance FromJSON ReplicaCount where
3520+ parseJSON v = parseAsInt v
3521+ <|> parseAsString v
3522+ where parseAsInt = fmap ReplicaCount . parseJSON
3523+ parseAsString = withText "ReplicaCount" (fmap ReplicaCount . parseReadText)
3524+
3525+instance FromJSON ShardCount where
3526+ parseJSON v = parseAsInt v
3527+ <|> parseAsString v
3528+ where parseAsInt = fmap ShardCount . parseJSON
3529+ parseAsString = withText "ShardCount" (fmap ShardCount . parseReadText)
3530+
30593531instance Bounded DocVersion where
30603532 minBound = DocVersion 1
30613533 maxBound = DocVersion 9200000000000000000 -- 9.2e+18
tests/tests.hs+309−11
@@ -1,17 +1,22 @@
1+{-# LANGUAGE DefaultSignatures #-}
12{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE FlexibleContexts #-}
24{-# LANGUAGE GeneralizedNewtypeDeriving #-}
35{-# LANGUAGE OverloadedStrings #-}
46{-# LANGUAGE ScopedTypeVariables #-}
57{-# LANGUAGE TemplateHaskell #-}
8+{-# LANGUAGE TypeOperators #-}
69{-# OPTIONS_GHC -fno-warn-orphans #-}
710module Main where
811
912import Control.Applicative
13+import Control.Error
1014import Control.Exception
1115import Control.Monad
1216import Control.Monad.Reader
1317import Data.Aeson
1418import Data.Aeson.Types (parseEither)
19+import qualified Data.ByteString.Lazy.Char8 as BL8
1520import Data.DeriveTH
1621import qualified Data.HashMap.Strict as HM
1722import Data.List (nub)
@@ -23,13 +28,13 @@ import Data.Monoid
2328import Data.Proxy
2429import Data.Text (Text)
2530import qualified Data.Text as T
26−import Data.Time.Calendar (Day (..))
27−import Data.Time.Clock (UTCTime (..),
31+import Data.Time.Calendar (Day (..), fromGregorian)
32+import Data.Time.Clock (NominalDiffTime, UTCTime (..),
2833 secondsToDiffTime)
2934import Data.Typeable
3035import qualified Data.Vector as V
3136import Database.Bloodhound
32−import GHC.Generics (Generic)
37+import GHC.Generics as G
3338import Network.HTTP.Client hiding (Proxy)
3439import qualified Network.HTTP.Types.Status as NHTS
3540import Prelude hiding (filter)
@@ -38,7 +43,6 @@ import Test.QuickCheck.Property.Monoid (T (..), eq, prop_Monoid)
3843
3944import Test.Hspec.QuickCheck (prop)
4045import Test.QuickCheck
41−import Test.QuickCheck.Instances ()
4246
4347testServer :: Server
4448testServer = Server "http://localhost:9200"
@@ -109,9 +113,13 @@ is v = testServerBranch >>= \x -> return $ x == Just (serverBranch v)
109113when' :: Monad m => m Bool -> m () -> m ()
110114when' b f = b >>= \x -> when x f
111115
112−propJSON :: forall a. (Arbitrary a, ToJSON a, FromJSON a, Show a, Eq a, Typeable a) => Proxy a -> Spec
116+(==~) :: (ApproxEq a, Show a) => a -> a -> Property
117+a ==~ b = counterexample (show a ++ " !=~ " ++ show b) (a =~ b)
118+
119+propJSON :: forall a. (Arbitrary a, ToJSON a, FromJSON a, Show a, ApproxEq a, Typeable a) => Proxy a -> Spec
113120propJSON _ = prop testName $ \(a :: a) ->
114− parseEither parseJSON (toJSON a) === Right a
121+ let jsonStr = "via " <> BL8.unpack (encode a)
122+ in counterexample jsonStr (parseEither parseJSON (toJSON a) ==~ Right a)
115123 where testName = show ty <> " FromJSON/ToJSON roundtrips"
116124 ty = typeOf (undefined :: a)
117125
@@ -276,30 +284,224 @@ instance FromJSON BulkTest where
276284instance ToJSON BulkTest where
277285 toJSON = genericToJSON defaultOptions
278286
287+class GApproxEq f where
288+ gApproxEq :: f a -> f a -> Bool
289+
290+-- | Unit type
291+instance GApproxEq U1 where
292+ gApproxEq U1 U1 = True
293+
294+-- | Sum type, ensure same constructors, recurse
295+instance (GApproxEq a, GApproxEq b) => GApproxEq (a :+: b) where
296+ gApproxEq (L1 a) (L1 b) = gApproxEq a b
297+ gApproxEq (R1 a) (R1 b) = gApproxEq a b
298+ gApproxEq _ _ = False
299+
300+-- | Product type, ensure each field is approx eq
301+instance (GApproxEq a, GApproxEq b) => GApproxEq (a :*: b) where
302+ gApproxEq (a1 :*: b1) (a2 :*: b2) = gApproxEq a1 a2 && gApproxEq b1 b2
303+
304+-- | Value type, actually check the values for approx equality
305+instance (ApproxEq a) => GApproxEq (K1 i a) where
306+ gApproxEq (K1 a) (K1 b) = a =~ b
307+
308+instance (GApproxEq f) => GApproxEq (M1 i t f) where
309+ gApproxEq (M1 a) (M1 b) = gApproxEq a b
310+
311+-- | Typeclass for "equal where it matters". Use this to specify
312+-- less-strict equivalence for things such as lists that can wind up
313+-- in an unpredictable order
314+class ApproxEq a where
315+ (=~) :: a -> a -> Bool
316+ default (=~) :: (Generic a, GApproxEq (Rep a)) => a -> a -> Bool
317+ a =~ b = gApproxEq (G.from a) (G.from b)
318+
319+instance ApproxEq NominalDiffTime where (=~) = (==)
320+instance ApproxEq UTCTime where (=~) = (==)
321+instance ApproxEq Text where (=~) = (==)
322+instance ApproxEq Bool where (=~) = (==)
323+instance ApproxEq Int where (=~) = (==)
324+instance ApproxEq Double where (=~) = (==)
325+instance ApproxEq a => ApproxEq (NonEmpty a)
326+instance ApproxEq a => ApproxEq (Maybe a)
327+instance ApproxEq GeoPoint
328+instance ApproxEq Regexp
329+instance ApproxEq RangeValue
330+instance ApproxEq LessThan
331+instance ApproxEq LessThanEq
332+instance ApproxEq LessThanD
333+instance ApproxEq LessThanEqD
334+instance ApproxEq GreaterThan
335+instance ApproxEq GreaterThanEq
336+instance ApproxEq GreaterThanD
337+instance ApproxEq GreaterThanEqD
338+instance ApproxEq MinimumMatchHighLow
339+instance ApproxEq RegexpFlag
340+instance ApproxEq RegexpFlags
341+instance ApproxEq NullValue
342+instance ApproxEq Version
343+instance ApproxEq DistanceRange
344+instance ApproxEq IndexName
345+instance ApproxEq MappingName
346+instance ApproxEq DocId
347+instance ApproxEq IndexAliasRouting
348+instance ApproxEq RoutingValue
349+instance ApproxEq ShardCount
350+instance ApproxEq ReplicaCount
351+instance ApproxEq TemplateName
352+instance ApproxEq TemplatePattern
353+instance ApproxEq QueryString
354+instance ApproxEq FieldName
355+instance ApproxEq CacheName
356+instance ApproxEq CacheKey
357+instance ApproxEq Existence
358+instance ApproxEq CutoffFrequency
359+instance ApproxEq Analyzer
360+instance ApproxEq Lenient
361+instance ApproxEq Tiebreaker
362+instance ApproxEq Boost
363+instance ApproxEq BoostTerms
364+instance ApproxEq MaxExpansions
365+instance ApproxEq MinimumMatch
366+instance ApproxEq DisableCoord
367+instance ApproxEq IgnoreTermFrequency
368+instance ApproxEq MinimumTermFrequency
369+instance ApproxEq MaxQueryTerms
370+instance ApproxEq Fuzziness
371+instance ApproxEq PrefixLength
372+instance ApproxEq TypeName
373+instance ApproxEq PercentMatch
374+instance ApproxEq StopWord
375+instance ApproxEq QueryPath
376+instance ApproxEq AllowLeadingWildcard
377+instance ApproxEq LowercaseExpanded
378+instance ApproxEq EnablePositionIncrements
379+instance ApproxEq AnalyzeWildcard
380+instance ApproxEq GeneratePhraseQueries
381+instance ApproxEq Locale
382+instance ApproxEq MaxWordLength
383+instance ApproxEq MinWordLength
384+instance ApproxEq PhraseSlop
385+instance ApproxEq MinDocFrequency
386+instance ApproxEq MaxDocFrequency
387+instance ApproxEq Filter
388+instance ApproxEq Query
389+instance ApproxEq SimpleQueryStringQuery
390+instance ApproxEq FieldOrFields
391+instance ApproxEq SimpleQueryFlag
392+instance ApproxEq RegexpQuery
393+instance ApproxEq QueryStringQuery
394+instance ApproxEq RangeQuery
395+instance ApproxEq PrefixQuery
396+instance ApproxEq NestedQuery
397+instance ApproxEq MoreLikeThisFieldQuery
398+instance ApproxEq MoreLikeThisQuery
399+instance ApproxEq IndicesQuery
400+instance ApproxEq HasParentQuery
401+instance ApproxEq HasChildQuery
402+instance ApproxEq FuzzyQuery
403+instance ApproxEq FuzzyLikeFieldQuery
404+instance ApproxEq FuzzyLikeThisQuery
405+instance ApproxEq FilteredQuery
406+instance ApproxEq DisMaxQuery
407+instance ApproxEq CommonTermsQuery
408+instance ApproxEq CommonMinimumMatch
409+instance ApproxEq BoostingQuery
410+instance ApproxEq BoolQuery
411+instance ApproxEq MatchQuery
412+instance ApproxEq MultiMatchQueryType
413+instance ApproxEq BooleanOperator
414+instance ApproxEq ZeroTermsQuery
415+instance ApproxEq MatchQueryType
416+instance ApproxEq AliasRouting
417+instance ApproxEq IndexAliasCreate
418+instance ApproxEq SearchAliasRouting
419+instance ApproxEq ScoreType
420+instance ApproxEq Distance
421+instance ApproxEq DistanceUnit
422+instance ApproxEq DistanceType
423+instance ApproxEq OptimizeBbox
424+instance ApproxEq GeoBoundingBoxConstraint
425+instance ApproxEq GeoFilterType
426+instance ApproxEq GeoBoundingBox
427+instance ApproxEq LatLon
428+instance ApproxEq RangeExecution
429+instance ApproxEq FSType
430+instance ApproxEq CompoundFormat
431+instance ApproxEq InitialShardCount
432+instance ApproxEq Bytes
433+instance ApproxEq ReplicaBounds
434+instance ApproxEq Term
435+instance ApproxEq BoolMatch
436+instance ApproxEq MultiMatchQuery
437+instance ApproxEq IndexSettings
438+instance ApproxEq AllocationPolicy
439+instance ApproxEq Char
440+instance ApproxEq a => ApproxEq [a] where
441+ as =~ bs = and (zipWith (=~) as bs)
442+instance (ApproxEq l, ApproxEq r) => ApproxEq (Either l r) where
443+ Left a =~ Left b = a =~ b
444+ Right a =~ Right b = a =~ b
445+ _ =~ _ = False
446+instance ApproxEq NodeAttrFilter
447+instance ApproxEq NodeAttrName
448+
449+-- | Due to the way nodeattrfilters get serialized here, they may come
450+-- out in a different order, but they are morally equivalent
451+instance ApproxEq UpdatableIndexSetting where
452+ RoutingAllocationInclude a =~ RoutingAllocationInclude b =
453+ NE.sort a =~ NE.sort b
454+ RoutingAllocationExclude a =~ RoutingAllocationExclude b =
455+ NE.sort a =~ NE.sort b
456+ RoutingAllocationRequire a =~ RoutingAllocationRequire b =
457+ NE.sort a =~ NE.sort b
458+ a =~ b = a == b
459+
460+
279461noDuplicates :: Eq a => [a] -> Bool
280462noDuplicates xs = nub xs == xs
281463
464+instance Arbitrary NominalDiffTime where
465+ arbitrary = fromInteger <$> arbitrary
466+
467+instance (Arbitrary k, Ord k, Arbitrary v) => Arbitrary (M.Map k v) where
468+ arbitrary = M.fromList <$> arbitrary
469+
470+instance Arbitrary Text where
471+ arbitrary = T.pack <$> arbitrary
472+
473+instance Arbitrary UTCTime where
474+ arbitrary = UTCTime
475+ <$> arbitrary
476+ <*> (fromRational . toRational <$> choose (0::Double, 86400))
477+
478+instance Arbitrary Day where
479+ arbitrary = ModifiedJulianDay <$> (2000 +) <$> arbitrary
480+ shrink = (ModifiedJulianDay <$>) . shrink . toModifiedJulianDay
481+
282482instance Arbitrary a => Arbitrary (NonEmpty a) where
283483 arbitrary = liftA2 (:|) arbitrary arbitrary
284484
285485arbitraryScore :: Gen Score
286486arbitraryScore = fmap getPositive <$> arbitrary
287487
288−instance Arbitrary a => Arbitrary (Hit a) where
488+instance (Arbitrary a, Typeable a) => Arbitrary (Hit a) where
289489 arbitrary = Hit <$> arbitrary
290490 <*> arbitrary
291491 <*> arbitrary
292492 <*> arbitraryScore
293493 <*> arbitrary
294494 <*> arbitrary
495+ shrink = genericShrink
295496
296497
297−instance Arbitrary a => Arbitrary (SearchHits a) where
498+instance (Arbitrary a, Typeable a) => Arbitrary (SearchHits a) where
298499 arbitrary = reduceSize $ do
299500 tot <- getPositive <$> arbitrary
300501 score <- arbitraryScore
301502 hs <- arbitrary
302503 return $ SearchHits tot score hs
504+ shrink = genericShrink
303505
304506reduceSize :: Gen a -> Gen a
305507reduceSize f = sized $ \n -> resize (n `div` 2) f
@@ -337,9 +539,13 @@ instance Arbitrary AliasRouting where
337539 <$> (Just <$> arbitrary)
338540 <*> (Just <$> arbitrary)
339541 allAlias = AllAliasRouting <$> arbitrary
542+ shrink = genericShrink
543+
340544
341545instance Arbitrary FieldName where
342546 arbitrary = FieldName . T.pack <$> listOf1 arbitraryAlphaNum
547+ shrink = genericShrink
548+
343549
344550instance Arbitrary RegexpFlags where
345551 arbitrary = oneof [ pure AllRegexpFlags
@@ -347,9 +553,12 @@ instance Arbitrary RegexpFlags where
347553 , SomeRegexpFlags <$> genUniqueFlags
348554 ]
349555 where genUniqueFlags = NE.fromList . nub <$> listOf1 arbitrary
556+ shrink = genericShrink
557+
350558
351559instance Arbitrary IndexAliasCreate where
352560 arbitrary = IndexAliasCreate <$> arbitrary <*> reduceSize arbitrary
561+ shrink = genericShrink
353562
354563instance Arbitrary Query where
355564 arbitrary = reduceSize $ oneof [ TermQuery <$> arbitrary <*> arbitrary
@@ -380,6 +589,7 @@ instance Arbitrary Query where
380589 , QueryRangeQuery <$> arbitrary
381590 , QueryRegexpQuery <$> arbitrary
382591 ]
592+ shrink = genericShrink
383593
384594instance Arbitrary Filter where
385595 arbitrary = reduceSize $ oneof [ AndFilter <$> arbitrary <*> arbitrary
@@ -400,6 +610,29 @@ instance Arbitrary Filter where
400610 , RangeFilter <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
401611 , RegexpFilter <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
402612 , TermFilter <$> arbitrary <*> arbitrary]
613+ shrink = genericShrink
614+
615+instance Arbitrary ReplicaBounds where
616+ arbitrary = oneof [ replicasBounded
617+ , replicasLowerBounded
618+ , pure ReplicasUnbounded
619+ ]
620+ where replicasBounded = do Positive a <- arbitrary
621+ Positive b <- arbitrary
622+ return (ReplicasBounded a b)
623+ replicasLowerBounded = do Positive a <- arbitrary
624+ return (ReplicasLowerBounded a)
625+
626+instance Arbitrary NodeAttrName where
627+ arbitrary = NodeAttrName . T.pack . getNonEmpty <$> arbitrary
628+
629+
630+instance Arbitrary NodeAttrFilter where
631+ arbitrary = do
632+ n <- arbitrary
633+ s:ss <- listOf1 (listOf1 arbitraryAlphaNum)
634+ let ts = T.pack <$> s :| ss
635+ return (NodeAttrFilter n ts)
403636
404637$(derive makeArbitrary ''IndexName)
405638$(derive makeArbitrary ''MappingName)
@@ -499,6 +732,13 @@ $(derive makeArbitrary ''RangeExecution)
499732$(derive makeArbitrary ''RegexpFlag)
500733$(derive makeArbitrary ''BoolMatch)
501734$(derive makeArbitrary ''Term)
735+$(derive makeArbitrary ''IndexSettings)
736+$(derive makeArbitrary ''UpdatableIndexSetting)
737+$(derive makeArbitrary ''Bytes)
738+$(derive makeArbitrary ''AllocationPolicy)
739+$(derive makeArbitrary ''InitialShardCount)
740+$(derive makeArbitrary ''FSType)
741+$(derive makeArbitrary ''CompoundFormat)
502742
503743
504744main :: IO ()
@@ -917,6 +1157,43 @@ main = hspec $ do
9171157 fmap aggregations res `shouldBe` Right (Just (M.fromList [ docCountPair "bitemyapps" 1
9181158 , docCountPair "notmyapps" 1
9191159 ]))
1160+ it "can execute date_range aggregations" $ withTestEnv $ do
1161+ let now = fromGregorian 2015 3 14
1162+ let ltAMonthAgo = UTCTime (fromGregorian 2015 3 1) 0
1163+ let ltAWeekAgo = UTCTime (fromGregorian 2015 3 10) 0
1164+ let oldDoc = exampleTweet { postDate = ltAMonthAgo }
1165+ let newDoc = exampleTweet { postDate = ltAWeekAgo }
1166+ _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings oldDoc (DocId "1")
1167+ _ <- indexDocument testIndex testMapping defaultIndexDocumentSettings newDoc (DocId "2")
1168+ _ <- refreshIndex testIndex
1169+ let thisMonth = DateRangeFrom (DateMathExpr (DMDate now) [SubtractTime 1 DMMonth])
1170+ let thisWeek = DateRangeFrom (DateMathExpr (DMDate now) [SubtractTime 1 DMWeek])
1171+ let agg = DateRangeAggregation (FieldName "postDate") Nothing (thisMonth :| [thisWeek])
1172+ let ags = mkAggregations "date_ranges" (DateRangeAgg agg)
1173+ let search = mkAggregateSearch Nothing ags
1174+ res <- searchTweets search
1175+ liftIO $ hitsTotal . searchHits <$> res `shouldBe` Right 2
1176+ let bucks = do magrs <- fmapL show (aggregations <$> res)
1177+ agrs <- note "no aggregations returned" magrs
1178+ rawBucks <- note "no date_ranges aggregation" $ M.lookup "date_ranges" agrs
1179+ parseEither parseJSON rawBucks
1180+ let fromMonthT = UTCTime (fromGregorian 2015 2 14) 0
1181+ let fromWeekT = UTCTime (fromGregorian 2015 3 7) 0
1182+ liftIO $ buckets <$> bucks `shouldBe` Right [ DateRangeResult "2015-02-14T00:00:00.000Z-*"
1183+ (Just fromMonthT)
1184+ (Just "2015-02-14T00:00:00.000Z")
1185+ Nothing
1186+ Nothing
1187+ 2
1188+ Nothing
1189+ , DateRangeResult "2015-03-07T00:00:00.000Z-*"
1190+ (Just fromWeekT)
1191+ (Just "2015-03-07T00:00:00.000Z")
1192+ Nothing
1193+ Nothing
1194+ 1
1195+ Nothing
1196+ ]
9201197
9211198 it "returns date histogram aggregation results" $ withTestEnv $ do
9221199 _ <- insertData
@@ -1073,7 +1350,7 @@ main = hspec $ do
10731350 withTestEnv $ do
10741351 resetIndex
10751352 resp <- updateIndexAliases (action :| [])
1076− liftIO $ NHTS.statusCode (responseStatus resp) `shouldBe` 200
1353+ liftIO $ validateStatus resp 200
10771354 let cleanup = withTestEnv (updateIndexAliases (RemoveAlias alias :| []))
10781355 (do aliases <- withTestEnv getIndexAliases
10791356 let expected = IndexAliasSummary alias create
@@ -1094,7 +1371,7 @@ main = hspec $ do
10941371 withTestEnv $ do
10951372 resetIndex
10961373 resp <- updateIndexAliases (action :| [])
1097− liftIO $ NHTS.statusCode (responseStatus resp) `shouldBe` 200
1374+ liftIO $ validateStatus resp 200
10981375 let cleanup = withTestEnv (updateIndexAliases (RemoveAlias alias :| []))
10991376 (do aliases <- withTestEnv getIndexAliases
11001377 let expected = IndexAliasSummary alias create
@@ -1103,6 +1380,20 @@ main = hspec $ do
11031380 L.find ((== alias) . indexAliasSummaryAlias) summs `shouldBe` Just expected
11041381 Left e -> expectationFailure ("Expected an IndexAliasesSummary but got " <> show e)) `finally` cleanup
11051382
1383+ describe "Index Settings" $ do
1384+ it "persists settings" $ withTestEnv $ do
1385+ _ <- deleteExampleIndex
1386+ _ <- createExampleIndex
1387+ let updates = BlocksWrite False :| []
1388+ updateResp <- updateIndexSettings updates testIndex
1389+ liftIO $ validateStatus updateResp 200
1390+ getResp <- getIndexSettings testIndex
1391+ liftIO $
1392+ getResp `shouldBe` Right (IndexSettingsSummary
1393+ testIndex
1394+ (IndexSettings (ShardCount 1) (ReplicaCount 0))
1395+ (NE.toList updates))
1396+
11061397 describe "JSON instances" $ do
11071398 propJSON (Proxy :: Proxy Version)
11081399 propJSON (Proxy :: Proxy IndexName)
@@ -1126,7 +1417,6 @@ main = hspec $ do
11261417 propJSON (Proxy :: Proxy Tiebreaker)
11271418 propJSON (Proxy :: Proxy Boost)
11281419 propJSON (Proxy :: Proxy BoostTerms)
1129− propJSON (Proxy :: Proxy MaxExpansions)
11301420 propJSON (Proxy :: Proxy MinimumMatch)
11311421 propJSON (Proxy :: Proxy DisableCoord)
11321422 propJSON (Proxy :: Proxy IgnoreTermFrequency)
@@ -1199,3 +1489,11 @@ main = hspec $ do
11991489 propJSON (Proxy :: Proxy BoolMatch)
12001490 propJSON (Proxy :: Proxy Term)
12011491 propJSON (Proxy :: Proxy MultiMatchQuery)
1492+ propJSON (Proxy :: Proxy IndexSettings)
1493+ propJSON (Proxy :: Proxy UpdatableIndexSetting)
1494+ propJSON (Proxy :: Proxy ReplicaBounds)
1495+ propJSON (Proxy :: Proxy Bytes)
1496+ propJSON (Proxy :: Proxy AllocationPolicy)
1497+ propJSON (Proxy :: Proxy InitialShardCount)
1498+ propJSON (Proxy :: Proxy FSType)
1499+ propJSON (Proxy :: Proxy CompoundFormat)