@@ -29,6 +29,7 @@
2929module Database.Bloodhound.Types
3030 ( defaultCache
3131 , defaultIndexSettings
32+ , defaultIndexDocumentSettings
3233 , mkSort
3334 , showText
3435 , unpackId
@@ -41,6 +42,8 @@ module Database.Bloodhound.Types
4142 , mkTermsAggregation
4243 , mkTermsScriptAggregation
4344 , mkDateHistogram
45+ , mkDocVersion
46+ , docVersionNumber
4447 , toTerms
4548 , toDateHistogram
4649 , omitNulls
@@ -53,9 +56,16 @@ module Database.Bloodhound.Types
5356 , Existence(..)
5457 , NullValue(..)
5558 , IndexSettings(..)
59+ , IndexTemplate(..)
5660 , Server(..)
5761 , Reply
5862 , EsResult(..)
63+ , EsResultFound(..)
64+ , DocVersion
65+ , ExternalDocVersion(..)
66+ , VersionControl(..)
67+ , DocumentParent(..)
68+ , IndexDocumentSettings(..)
5969 , Query(..)
6070 , Search(..)
6171 , SearchResult(..)
@@ -93,7 +103,10 @@ module Database.Bloodhound.Types
93103 , RegexpFlags(..)
94104 , RegexpFlag(..)
95105 , FieldName(..)
106+ , Script(..)
96107 , IndexName(..)
108+ , TemplateName(..)
109+ , TemplatePattern(..)
97110 , MappingName(..)
98111 , DocId(..)
99112 , CacheName(..)
@@ -189,6 +202,8 @@ module Database.Bloodhound.Types
189202 , Bucket(..)
190203 , BucketAggregation(..)
191204 , TermsAggregation(..)
205+ , ValueCountAggregation(..)
206+ , FilterAggregation(..)
192207 , DateHistogramAggregation(..)
193208
194209 , Highlights(..)
@@ -215,13 +230,16 @@ import Control.Monad.Writer
215230import Data.Aeson
216231import Data.Aeson.Types (Pair, emptyObject, parseMaybe)
217232import qualified Data.ByteString.Lazy.Char8 as L
218−import Data.List (nub)
233+import Data.List (nub, foldl')
219234import Data.List.NonEmpty (NonEmpty (..), toList)
235+import qualified Data.HashMap.Strict as HM (union)
220236import qualified Data.Map.Strict as M
237+import Data.Maybe
221238import Data.Text (Text)
222239import qualified Data.Text as T
223240import Data.Time.Clock (UTCTime)
224241import qualified Data.Vector as V
242+import GHC.Enum
225243import GHC.Generics (Generic)
226244import Network.HTTP.Client
227245import qualified Network.HTTP.Types.Method as NHTM
@@ -339,6 +357,20 @@ data FieldType = GeoPointType
339357data FieldDefinition =
340358 FieldDefinition { fieldType :: FieldType } deriving (Eq, Show)
341359
360+{-| An 'IndexTemplate' defines a template that will automatically be
361+ applied to new indices created. The templates include both
362+ 'IndexSettings' and mappings, and a simple 'TemplatePattern' that
363+ controls if the template will be applied to the index created.
364+ Specify mappings as follows: @[toJSON TweetMapping, ...]@
365+
366+ https://www.elastic.co/guide/en/elasticsearch/reference/1.7/indices-templates.html
367+-}
368+data IndexTemplate =
369+ IndexTemplate { templatePattern :: TemplatePattern
370+ , templateSettings :: Maybe IndexSettings
371+ , templateMappings :: [Value]
372+ }
373+
342374data MappingField =
343375 MappingField { mappingFieldName :: FieldName
344376 , fieldDefinition :: FieldDefinition } deriving (Eq, Show)
@@ -366,14 +398,95 @@ data BulkOperation =
366398 | BulkUpdate IndexName MappingName DocId Value deriving (Eq, Show)
367399
368400{-| 'EsResult' describes the standard wrapper JSON document that you see in
369− successful Elasticsearch responses.
401+ successful Elasticsearch lookups or lookups that couldn't find the document.
370402-}
371403data EsResult a = EsResult { _index :: Text
372404 , _type :: Text
373405 , _id :: Text
374− , _version :: Int
375− , found :: Maybe Bool
376− , _source :: a } deriving (Eq, Show)
406+ , foundResult :: Maybe (EsResultFound a)} deriving (Eq, Show)
407+
408+
409+{-| 'EsResultFound' contains the document and its metadata inside of an
410+ 'EsResult' when the document was successfully found.
411+-}
412+data EsResultFound a = EsResultFound { _version :: DocVersion
413+ , _source :: a } deriving (Eq, Show)
414+
415+
416+
417+{-| 'DocVersion' is an integer version number for a document between 1
418+and 9.2e+18 used for <<https://www.elastic.co/guide/en/elasticsearch/guide/current/optimistic-concurrency-control.html optimistic concurrency control>>.
419+-}
420+newtype DocVersion = DocVersion {
421+ docVersionNumber :: Int
422+ } deriving (Eq, Show, Ord, ToJSON)
423+
424+-- | Smart constructor for in-range doc version
425+mkDocVersion :: Int -> Maybe DocVersion
426+mkDocVersion i
427+ | i >= (docVersionNumber minBound) && i <= (docVersionNumber maxBound) =
428+ Just $ DocVersion i
429+ | otherwise = Nothing
430+
431+
432+{-| 'ExternalDocVersion' is a convenience wrapper if your code uses its
433+own version numbers instead of ones from ES.
434+-}
435+newtype ExternalDocVersion = ExternalDocVersion DocVersion
436+ deriving (Eq, Show, Ord, Bounded, Enum, ToJSON)
437+
438+{-| 'VersionControl' is specified when indexing documents as a
439+optimistic concurrency control.
440+-}
441+data VersionControl = NoVersionControl
442+ -- ^ Don't send a version. This is a pure overwrite.
443+ | InternalVersion DocVersion
444+ -- ^ Use the default ES versioning scheme. Only
445+ -- index the document if the version is the same
446+ -- as the one specified. Only applicable to
447+ -- updates, as you should be getting Version from
448+ -- a search result.
449+ | ExternalGT ExternalDocVersion
450+ -- ^ Use your own version numbering. Only index
451+ -- the document if the version is strictly higher
452+ -- OR the document doesn't exist. The given
453+ -- version will be used as the new version number
454+ -- for the stored document. N.B. All updates must
455+ -- increment this number, meaning there is some
456+ -- global, external ordering of updates.
457+ | ExternalGTE ExternalDocVersion
458+ -- ^ Use your own version numbering. Only index
459+ -- the document if the version is equal or higher
460+ -- than the stored version. Will succeed if there
461+ -- is no existing document. The given version will
462+ -- be used as the new version number for the
463+ -- stored document. Use with care, as this could
464+ -- result in data loss.
465+ | ForceVersion ExternalDocVersion
466+ -- ^ The document will always be indexed and the
467+ -- given version will be the new version. This is
468+ -- typically used for correcting errors. Use with
469+ -- care, as this could result in data loss.
470+ deriving (Show, Eq, Ord)
471+
472+{-| 'DocumentParent' is used to specify a parent document.
473+-}
474+newtype DocumentParent = DocumentParent DocId
475+ deriving (Eq, Show)
476+
477+{-| 'IndexDocumentSettings' are special settings supplied when indexing
478+a document. For the best backwards compatiblity when new fields are
479+added, you should probably prefer to start with 'defaultIndexDocumentSettings'
480+-}
481+data IndexDocumentSettings =
482+ IndexDocumentSettings { idsVersionControl :: VersionControl
483+ , idsParent :: Maybe DocumentParent
484+ } deriving (Eq, Show)
485+
486+{-| Reasonable default settings. Chooses no version control and no parent.
487+-}
488+defaultIndexDocumentSettings :: IndexDocumentSettings
489+defaultIndexDocumentSettings = IndexDocumentSettings NoVersionControl Nothing
377490
378491{-| 'Sort' is a synonym for a list of 'SortSpec's. Sort behavior is order
379492 dependent with later sorts acting as tie-breakers for earlier sorts.
@@ -469,6 +582,14 @@ newtype Server = Server Text deriving (Eq, Show)
469582-}
470583newtype IndexName = IndexName Text deriving (Eq, Generic, Show)
471584
585+{-| 'TemplateName' is used to describe which template to query/create/delete
586+-}
587+newtype TemplateName = TemplateName Text deriving (Eq, Show, Generic)
588+
589+{-| 'TemplatePattern' represents a pattern which is matched against index names
590+-}
591+newtype TemplatePattern = TemplatePattern Text deriving (Eq, Show, Generic)
592+
472593{-| 'MappingName' is part of mappings which are how ES describes and schematizes
473594 the data in the indices.
474595-}
@@ -489,6 +610,12 @@ newtype QueryString = QueryString Text deriving (Eq, Generic, Show)
489610-}
490611newtype FieldName = FieldName Text deriving (Eq, Show)
491612
613+
614+{-| 'Script' is often used in place of 'FieldName' to specify more
615+complex ways of extracting a value from a document.
616+-}
617+newtype Script = Script { scriptText :: Text } deriving (Eq, Show)
618+
492619{-| 'CacheName' is used in 'RegexpFilter' for describing the
493620 'CacheKey' keyed caching behavior.
494621-}
@@ -604,7 +731,8 @@ data Search = Search { queryBody :: Maybe Query
604731 -- default False
605732 , trackSortScores :: TrackSortScores
606733 , from :: From
607− , size :: Size } deriving (Eq, Show)
734+ , size :: Size
735+ , fields :: Maybe [FieldName] } deriving (Eq, Show)
608736
609737data Highlights = Highlights { globalsettings :: Maybe HighlightSettings
610738 , highlightFields :: [FieldHighlight]
@@ -987,6 +1115,7 @@ data Filter = AndFilter [Filter] Cache
9871115 | LimitFilter Int
9881116 | MissingFilter FieldName Existence NullValue
9891117 | PrefixFilter FieldName PrefixValue Cache
1118+ | QueryFilter Query Cache
9901119 | RangeFilter FieldName RangeValue RangeExecution Cache
9911120 | RegexpFilter FieldName Regexp RegexpFlags CacheName Cache CacheKey
9921121 | TermFilter Term Cache
@@ -1191,7 +1320,9 @@ data Interval = Year
11911320 | FractionalInterval Float TimeInterval deriving (Eq, Show)
11921321
11931322data Aggregation = TermsAgg TermsAggregation
1194− | DateHistogramAgg DateHistogramAggregation deriving (Eq, Show)
1323+ | DateHistogramAgg DateHistogramAggregation
1324+ | ValueCountAgg ValueCountAggregation
1325+ | FilterAgg FilterAggregation deriving (Eq, Show)
11951326
11961327
11971328data TermsAggregation = TermsAggregation { term :: Either Text Text
@@ -1209,6 +1340,7 @@ data TermsAggregation = TermsAggregation { term :: Either Text Text
12091340data DateHistogramAggregation = DateHistogramAggregation { dateField :: FieldName
12101341 , dateInterval :: Interval
12111342 , dateFormat :: Maybe Text
1343+ -- pre and post deprecated in 1.5
12121344 , datePreZone :: Maybe Text
12131345 , datePostZone :: Maybe Text
12141346 , datePreOffset :: Maybe Text
@@ -1216,6 +1348,13 @@ data DateHistogramAggregation = DateHistogramAggregation { dateField :: Fie
12161348 , dateAggs :: Maybe Aggregations
12171349 } deriving (Eq, Show)
12181350
1351+-- | See <https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-valuecount-aggregation.html> for more information.
1352+data ValueCountAggregation = FieldValueCount FieldName
1353+ | ScriptValueCount Script deriving (Eq, Show)
1354+
1355+-- | 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.
1356+data FilterAggregation = FilterAggregation { faFilter :: Filter
1357+ , faAggs :: Maybe Aggregations} deriving (Eq, Show)
12191358
12201359mkTermsAggregation :: Text -> TermsAggregation
12211360mkTermsAggregation t = TermsAggregation (Left t) Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
@@ -1289,6 +1428,13 @@ instance ToJSON Aggregation where
12891428 "post_offset" .= postOffset
12901429 ],
12911430 "aggs" .= dateHistoAggs ]
1431+ toJSON (ValueCountAgg a) = object ["value_count" .= v]
1432+ where v = case a of
1433+ (FieldValueCount (FieldName n)) -> object ["field" .= n]
1434+ (ScriptValueCount (Script s)) -> object ["script" .= s]
1435+ toJSON (FilterAgg (FilterAggregation filt ags)) =
1436+ omitNulls [ "filter" .= filt
1437+ , "aggs" .= ags]
12921438
12931439type AggregationResults = M.Map Text Value
12941440
@@ -1428,6 +1574,13 @@ instance ToJSON Filter where
14281574 object [fieldName .= fieldValue
14291575 , "_cache" .= cache]]
14301576
1577+ toJSON (QueryFilter query False) =
1578+ object ["query" .= toJSON query ]
1579+ toJSON (QueryFilter query True) =
1580+ object ["fquery" .=
1581+ object [ "query" .= toJSON query
1582+ , "_cache" .= True ]]
1583+
14311584 toJSON (RangeFilter (FieldName fieldName) rangeValue rangeExecution cache) =
14321585 object ["range" .=
14331586 object [ fieldName .= object (rangeValueToPair rangeValue)
@@ -1869,6 +2022,8 @@ instance ToJSON IgnoreTermFrequency
18692022instance ToJSON MaxQueryTerms
18702023instance ToJSON TypeName
18712024instance ToJSON IndexName
2025+instance ToJSON TemplateName
2026+instance ToJSON TemplatePattern
18722027instance ToJSON BoostTerms
18732028instance ToJSON MaxWordLength
18742029instance ToJSON MinWordLength
@@ -1905,22 +2060,43 @@ instance FromJSON Status where
19052060
19062061
19072062instance ToJSON IndexSettings where
1908− toJSON (IndexSettings s r) = object ["settings" .= object ["shards" .= s, "replicas" .= r]]
1909−
2063+ toJSON (IndexSettings s r) = object ["settings" .=
2064+ object ["index" .=
2065+ object ["number_of_shards" .= s, "number_of_replicas" .= r]
2066+ ]
2067+ ]
2068+
2069+instance ToJSON IndexTemplate where
2070+ toJSON (IndexTemplate p s m) = merge
2071+ (object [ "template" .= p
2072+ , "mappings" .= foldl' merge (object []) m
2073+ ])
2074+ (toJSON s)
2075+ where
2076+ merge (Object o1) (Object o2) = toJSON $ HM.union o1 o2
2077+ merge o Null = o
2078+ merge _ _ = undefined
19102079
19112080instance (FromJSON a) => FromJSON (EsResult a) where
1912− parseJSON (Object v) = EsResult <$>
1913− v .: "_index" <*>
1914− v .: "_type" <*>
1915− v .: "_id" <*>
1916− v .: "_version" <*>
1917− v .:? "found" <*>
1918− v .: "_source"
2081+ parseJSON jsonVal@(Object v) = do
2082+ found <- v .:? "found" .!= False
2083+ fr <- if found
2084+ then parseJSON jsonVal
2085+ else return Nothing
2086+ EsResult <$> v .: "_index" <*>
2087+ v .: "_type" <*>
2088+ v .: "_id" <*>
2089+ pure fr
19192090 parseJSON _ = empty
19202091
2092+instance (FromJSON a) => FromJSON (EsResultFound a) where
2093+ parseJSON (Object v) = EsResultFound <$>
2094+ v .: "_version" <*>
2095+ v .: "_source"
2096+ parseJSON _ = empty
19212097
19222098instance ToJSON Search where
1923− toJSON (Search query sFilter sort searchAggs highlight sTrackSortScores sFrom sSize) =
2099+ toJSON (Search query sFilter sort searchAggs highlight sTrackSortScores sFrom sSize sFields) =
19242100 omitNulls [ "query" .= query
19252101 , "filter" .= sFilter
19262102 , "sort" .= sort
@@ -1928,7 +2104,8 @@ instance ToJSON Search where
19282104 , "highlight" .= highlight
19292105 , "from" .= sFrom
19302106 , "size" .= sSize
1931− , "track_scores" .= sTrackSortScores]
2107+ , "track_scores" .= sTrackSortScores
2108+ , "fields" .= sFields]
19322109
19332110
19342111instance ToJSON FieldHighlight where
@@ -2174,3 +2351,26 @@ instance FromJSON ShardResult where
21742351 v .: "successful" <*>
21752352 v .: "failed"
21762353 parseJSON _ = empty
2354+
2355+
2356+instance FromJSON DocVersion where
2357+ parseJSON v = do
2358+ i <- parseJSON v
2359+ maybe (fail "DocVersion out of range") return $ mkDocVersion i
2360+
2361+instance Bounded DocVersion where
2362+ minBound = DocVersion 1
2363+ maxBound = DocVersion 9200000000000000000 -- 9.2e+18
2364+
2365+instance Enum DocVersion where
2366+ succ x
2367+ | x /= maxBound = DocVersion (succ $ docVersionNumber x)
2368+ | otherwise = succError "DocVersion"
2369+ pred x
2370+ | x /= minBound = DocVersion (pred $ docVersionNumber x)
2371+ | otherwise = predError "DocVersion"
2372+ toEnum i =
2373+ fromMaybe (error $ show i ++ " out of DocVersion range") $ mkDocVersion i
2374+ fromEnum = docVersionNumber
2375+ enumFrom = boundedEnumFrom
2376+ enumFromThen = boundedEnumFromThen