@@ -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(..)
@@ -98,7 +108,10 @@ module Database.Bloodhound.Types
98108 , RegexpFlags(..)
99109 , RegexpFlag(..)
100110 , FieldName(..)
111+ , Script(..)
101112 , IndexName(..)
113+ , TemplateName(..)
114+ , TemplatePattern(..)
102115 , MappingName(..)
103116 , DocId(..)
104117 , CacheName(..)
@@ -194,6 +207,8 @@ module Database.Bloodhound.Types
194207 , Bucket(..)
195208 , BucketAggregation(..)
196209 , TermsAggregation(..)
210+ , ValueCountAggregation(..)
211+ , FilterAggregation(..)
197212 , DateHistogramAggregation(..)
198213
199214 , Highlights(..)
@@ -220,13 +235,16 @@ import Control.Monad.Writer
220235import Data.Aeson
221236import Data.Aeson.Types (Pair, emptyObject, parseMaybe)
222237import qualified Data.ByteString.Lazy.Char8 as L
223−import Data.List (nub)
238+import Data.List (nub, foldl')
224239import Data.List.NonEmpty (NonEmpty (..), toList)
240+import qualified Data.HashMap.Strict as HM (union)
225241import qualified Data.Map.Strict as M
242+import Data.Maybe
226243import Data.Text (Text)
227244import qualified Data.Text as T
228245import Data.Time.Clock (UTCTime)
229246import qualified Data.Vector as V
247+import GHC.Enum
230248import GHC.Generics (Generic)
231249import Network.HTTP.Client
232250import qualified Network.HTTP.Types.Method as NHTM
@@ -344,6 +362,20 @@ data FieldType = GeoPointType
344362data FieldDefinition =
345363 FieldDefinition { fieldType :: FieldType } deriving (Eq, Show)
346364
365+{-| An 'IndexTemplate' defines a template that will automatically be
366+ applied to new indices created. The templates include both
367+ 'IndexSettings' and mappings, and a simple 'TemplatePattern' that
368+ controls if the template will be applied to the index created.
369+ Specify mappings as follows: @[toJSON TweetMapping, ...]@
370+
371+ https://www.elastic.co/guide/en/elasticsearch/reference/1.7/indices-templates.html
372+-}
373+data IndexTemplate =
374+ IndexTemplate { templatePattern :: TemplatePattern
375+ , templateSettings :: Maybe IndexSettings
376+ , templateMappings :: [Value]
377+ }
378+
347379data MappingField =
348380 MappingField { mappingFieldName :: FieldName
349381 , fieldDefinition :: FieldDefinition } deriving (Eq, Show)
@@ -371,14 +403,95 @@ data BulkOperation =
371403 | BulkUpdate IndexName MappingName DocId Value deriving (Eq, Show)
372404
373405{-| 'EsResult' describes the standard wrapper JSON document that you see in
374− successful Elasticsearch responses.
406+ successful Elasticsearch lookups or lookups that couldn't find the document.
375407-}
376408data EsResult a = EsResult { _index :: Text
377409 , _type :: Text
378410 , _id :: Text
379− , _version :: Int
380− , found :: Maybe Bool
381− , _source :: a } deriving (Eq, Show)
411+ , foundResult :: Maybe (EsResultFound a)} deriving (Eq, Show)
412+
413+
414+{-| 'EsResultFound' contains the document and its metadata inside of an
415+ 'EsResult' when the document was successfully found.
416+-}
417+data EsResultFound a = EsResultFound { _version :: DocVersion
418+ , _source :: a } deriving (Eq, Show)
419+
420+
421+
422+{-| 'DocVersion' is an integer version number for a document between 1
423+and 9.2e+18 used for <<https://www.elastic.co/guide/en/elasticsearch/guide/current/optimistic-concurrency-control.html optimistic concurrency control>>.
424+-}
425+newtype DocVersion = DocVersion {
426+ docVersionNumber :: Int
427+ } deriving (Eq, Show, Ord, ToJSON)
428+
429+-- | Smart constructor for in-range doc version
430+mkDocVersion :: Int -> Maybe DocVersion
431+mkDocVersion i
432+ | i >= (docVersionNumber minBound) && i <= (docVersionNumber maxBound) =
433+ Just $ DocVersion i
434+ | otherwise = Nothing
435+
436+
437+{-| 'ExternalDocVersion' is a convenience wrapper if your code uses its
438+own version numbers instead of ones from ES.
439+-}
440+newtype ExternalDocVersion = ExternalDocVersion DocVersion
441+ deriving (Eq, Show, Ord, Bounded, Enum, ToJSON)
442+
443+{-| 'VersionControl' is specified when indexing documents as a
444+optimistic concurrency control.
445+-}
446+data VersionControl = NoVersionControl
447+ -- ^ Don't send a version. This is a pure overwrite.
448+ | InternalVersion DocVersion
449+ -- ^ Use the default ES versioning scheme. Only
450+ -- index the document if the version is the same
451+ -- as the one specified. Only applicable to
452+ -- updates, as you should be getting Version from
453+ -- a search result.
454+ | ExternalGT ExternalDocVersion
455+ -- ^ Use your own version numbering. Only index
456+ -- the document if the version is strictly higher
457+ -- OR the document doesn't exist. The given
458+ -- version will be used as the new version number
459+ -- for the stored document. N.B. All updates must
460+ -- increment this number, meaning there is some
461+ -- global, external ordering of updates.
462+ | ExternalGTE ExternalDocVersion
463+ -- ^ Use your own version numbering. Only index
464+ -- the document if the version is equal or higher
465+ -- than the stored version. Will succeed if there
466+ -- is no existing document. The given version will
467+ -- be used as the new version number for the
468+ -- stored document. Use with care, as this could
469+ -- result in data loss.
470+ | ForceVersion ExternalDocVersion
471+ -- ^ The document will always be indexed and the
472+ -- given version will be the new version. This is
473+ -- typically used for correcting errors. Use with
474+ -- care, as this could result in data loss.
475+ deriving (Show, Eq, Ord)
476+
477+{-| 'DocumentParent' is used to specify a parent document.
478+-}
479+newtype DocumentParent = DocumentParent DocId
480+ deriving (Eq, Show)
481+
482+{-| 'IndexDocumentSettings' are special settings supplied when indexing
483+a document. For the best backwards compatiblity when new fields are
484+added, you should probably prefer to start with 'defaultIndexDocumentSettings'
485+-}
486+data IndexDocumentSettings =
487+ IndexDocumentSettings { idsVersionControl :: VersionControl
488+ , idsParent :: Maybe DocumentParent
489+ } deriving (Eq, Show)
490+
491+{-| Reasonable default settings. Chooses no version control and no parent.
492+-}
493+defaultIndexDocumentSettings :: IndexDocumentSettings
494+defaultIndexDocumentSettings = IndexDocumentSettings NoVersionControl Nothing
382495
383496{-| 'Sort' is a synonym for a list of 'SortSpec's. Sort behavior is order
384497 dependent with later sorts acting as tie-breakers for earlier sorts.
@@ -474,6 +587,14 @@ newtype Server = Server Text deriving (Eq, Show)
474587-}
475588newtype IndexName = IndexName Text deriving (Eq, Generic, Show)
476589
590+{-| 'TemplateName' is used to describe which template to query/create/delete
591+-}
592+newtype TemplateName = TemplateName Text deriving (Eq, Show, Generic)
593+
594+{-| 'TemplatePattern' represents a pattern which is matched against index names
595+-}
596+newtype TemplatePattern = TemplatePattern Text deriving (Eq, Show, Generic)
597+
477598{-| 'MappingName' is part of mappings which are how ES describes and schematizes
478599 the data in the indices.
479600-}
@@ -494,6 +615,12 @@ newtype QueryString = QueryString Text deriving (Eq, Generic, Show)
494615-}
495616newtype FieldName = FieldName Text deriving (Eq, Show)
496617
618+
619+{-| 'Script' is often used in place of 'FieldName' to specify more
620+complex ways of extracting a value from a document.
621+-}
622+newtype Script = Script { scriptText :: Text } deriving (Eq, Show)
623+
497624{-| 'CacheName' is used in 'RegexpFilter' for describing the
498625 'CacheKey' keyed caching behavior.
499626-}
@@ -610,6 +737,7 @@ data Search = Search { queryBody :: Maybe Query
610737 , trackSortScores :: TrackSortScores
611738 , from :: From
612739 , size :: Size
740+ , fields :: Maybe [FieldName]
613741 , source :: Maybe Source } deriving (Eq, Show)
614742
615743data Source =
@@ -1006,6 +1134,7 @@ data Filter = AndFilter [Filter] Cache
10061134 | LimitFilter Int
10071135 | MissingFilter FieldName Existence NullValue
10081136 | PrefixFilter FieldName PrefixValue Cache
1137+ | QueryFilter Query Cache
10091138 | RangeFilter FieldName RangeValue RangeExecution Cache
10101139 | RegexpFilter FieldName Regexp RegexpFlags CacheName Cache CacheKey
10111140 | TermFilter Term Cache
@@ -1210,7 +1339,9 @@ data Interval = Year
12101339 | FractionalInterval Float TimeInterval deriving (Eq, Show)
12111340
12121341data Aggregation = TermsAgg TermsAggregation
1213− | DateHistogramAgg DateHistogramAggregation deriving (Eq, Show)
1342+ | DateHistogramAgg DateHistogramAggregation
1343+ | ValueCountAgg ValueCountAggregation
1344+ | FilterAgg FilterAggregation deriving (Eq, Show)
12141345
12151346
12161347data TermsAggregation = TermsAggregation { term :: Either Text Text
@@ -1228,6 +1359,7 @@ data TermsAggregation = TermsAggregation { term :: Either Text Text
12281359data DateHistogramAggregation = DateHistogramAggregation { dateField :: FieldName
12291360 , dateInterval :: Interval
12301361 , dateFormat :: Maybe Text
1362+ -- pre and post deprecated in 1.5
12311363 , datePreZone :: Maybe Text
12321364 , datePostZone :: Maybe Text
12331365 , datePreOffset :: Maybe Text
@@ -1235,6 +1367,13 @@ data DateHistogramAggregation = DateHistogramAggregation { dateField :: Fie
12351367 , dateAggs :: Maybe Aggregations
12361368 } deriving (Eq, Show)
12371369
1370+-- | See <https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-valuecount-aggregation.html> for more information.
1371+data ValueCountAggregation = FieldValueCount FieldName
1372+ | ScriptValueCount Script deriving (Eq, Show)
1373+
1374+-- | 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.
1375+data FilterAggregation = FilterAggregation { faFilter :: Filter
1376+ , faAggs :: Maybe Aggregations} deriving (Eq, Show)
12381377
12391378mkTermsAggregation :: Text -> TermsAggregation
12401379mkTermsAggregation t = TermsAggregation (Left t) Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
@@ -1308,6 +1447,13 @@ instance ToJSON Aggregation where
13081447 "post_offset" .= postOffset
13091448 ],
13101449 "aggs" .= dateHistoAggs ]
1450+ toJSON (ValueCountAgg a) = object ["value_count" .= v]
1451+ where v = case a of
1452+ (FieldValueCount (FieldName n)) -> object ["field" .= n]
1453+ (ScriptValueCount (Script s)) -> object ["script" .= s]
1454+ toJSON (FilterAgg (FilterAggregation filt ags)) =
1455+ omitNulls [ "filter" .= filt
1456+ , "aggs" .= ags]
13111457
13121458type AggregationResults = M.Map Text Value
13131459
@@ -1447,6 +1593,13 @@ instance ToJSON Filter where
14471593 object [fieldName .= fieldValue
14481594 , "_cache" .= cache]]
14491595
1596+ toJSON (QueryFilter query False) =
1597+ object ["query" .= toJSON query ]
1598+ toJSON (QueryFilter query True) =
1599+ object ["fquery" .=
1600+ object [ "query" .= toJSON query
1601+ , "_cache" .= True ]]
1602+
14501603 toJSON (RangeFilter (FieldName fieldName) rangeValue rangeExecution cache) =
14511604 object ["range" .=
14521605 object [ fieldName .= object (rangeValueToPair rangeValue)
@@ -1887,6 +2040,8 @@ instance ToJSON IgnoreTermFrequency
18872040instance ToJSON MaxQueryTerms
18882041instance ToJSON TypeName
18892042instance ToJSON IndexName
2043+instance ToJSON TemplateName
2044+instance ToJSON TemplatePattern
18902045instance ToJSON BoostTerms
18912046instance ToJSON MaxWordLength
18922047instance ToJSON MinWordLength
@@ -1923,22 +2078,43 @@ instance FromJSON Status where
19232078
19242079
19252080instance ToJSON IndexSettings where
1926− toJSON (IndexSettings s r) = object ["settings" .= object ["shards" .= s, "replicas" .= r]]
1927−
2081+ toJSON (IndexSettings s r) = object ["settings" .=
2082+ object ["index" .=
2083+ object ["number_of_shards" .= s, "number_of_replicas" .= r]
2084+ ]
2085+ ]
2086+
2087+instance ToJSON IndexTemplate where
2088+ toJSON (IndexTemplate p s m) = merge
2089+ (object [ "template" .= p
2090+ , "mappings" .= foldl' merge (object []) m
2091+ ])
2092+ (toJSON s)
2093+ where
2094+ merge (Object o1) (Object o2) = toJSON $ HM.union o1 o2
2095+ merge o Null = o
2096+ merge _ _ = undefined
19282097
19292098instance (FromJSON a) => FromJSON (EsResult a) where
1930− parseJSON (Object v) = EsResult <$>
1931− v .: "_index" <*>
1932− v .: "_type" <*>
1933− v .: "_id" <*>
1934− v .: "_version" <*>
1935− v .:? "found" <*>
1936− v .: "_source"
2099+ parseJSON jsonVal@(Object v) = do
2100+ found <- v .:? "found" .!= False
2101+ fr <- if found
2102+ then parseJSON jsonVal
2103+ else return Nothing
2104+ EsResult <$> v .: "_index" <*>
2105+ v .: "_type" <*>
2106+ v .: "_id" <*>
2107+ pure fr
19372108 parseJSON _ = empty
19382109
2110+instance (FromJSON a) => FromJSON (EsResultFound a) where
2111+ parseJSON (Object v) = EsResultFound <$>
2112+ v .: "_version" <*>
2113+ v .: "_source"
2114+ parseJSON _ = empty
19392115
19402116instance ToJSON Search where
1941− toJSON (Search query sFilter sort searchAggs highlight sTrackSortScores sFrom sSize sSource) =
2117+ toJSON (Search query sFilter sort searchAggs highlight sTrackSortScores sFrom sSize sFields sSource) =
19422118 omitNulls [ "query" .= query
19432119 , "filter" .= sFilter
19442120 , "sort" .= sort
@@ -1947,6 +2123,7 @@ instance ToJSON Search where
19472123 , "from" .= sFrom
19482124 , "size" .= sSize
19492125 , "track_scores" .= sTrackSortScores
2126+ , "fields" .= sFields
19502127 , "_source" .= sSource]
19512128
19522129
@@ -2212,3 +2389,26 @@ instance FromJSON ShardResult where
22122389 v .: "successful" <*>
22132390 v .: "failed"
22142391 parseJSON _ = empty
2392+
2393+
2394+instance FromJSON DocVersion where
2395+ parseJSON v = do
2396+ i <- parseJSON v
2397+ maybe (fail "DocVersion out of range") return $ mkDocVersion i
2398+
2399+instance Bounded DocVersion where
2400+ minBound = DocVersion 1
2401+ maxBound = DocVersion 9200000000000000000 -- 9.2e+18
2402+
2403+instance Enum DocVersion where
2404+ succ x
2405+ | x /= maxBound = DocVersion (succ $ docVersionNumber x)
2406+ | otherwise = succError "DocVersion"
2407+ pred x
2408+ | x /= minBound = DocVersion (pred $ docVersionNumber x)
2409+ | otherwise = predError "DocVersion"
2410+ toEnum i =
2411+ fromMaybe (error $ show i ++ " out of DocVersion range") $ mkDocVersion i
2412+ fromEnum = docVersionNumber
2413+ enumFrom = boundedEnumFrom
2414+ enumFromThen = boundedEnumFromThen