@@ -29,6 +29,7 @@
29module Database.Bloodhound.Types
29module Database.Bloodhound.Types
30 ( defaultCache
30 ( defaultCache
31 , defaultIndexSettings
31 , defaultIndexSettings
32+ , defaultIndexDocumentSettings
33 , showText
34 , showText
34 , unpackId
35 , unpackId
@@ -41,6 +42,8 @@ module Database.Bloodhound.Types
41 , mkTermsAggregation
42 , mkTermsAggregation
42 , mkTermsScriptAggregation
43 , mkTermsScriptAggregation
43 , mkDateHistogram
44 , mkDateHistogram
45 , toDateHistogram
48 , toDateHistogram
46 , omitNulls
49 , omitNulls
@@ -53,9 +56,16 @@ module Database.Bloodhound.Types
53 , Existence(..)
56 , Existence(..)
54 , NullValue(..)
57 , NullValue(..)
55 , IndexSettings(..)
58 , IndexSettings(..)
56 , Server(..)
60 , Server(..)
58 , EsResult(..)
62 , EsResult(..)
65+ , ExternalDocVersion(..)
68+ , IndexDocumentSettings(..)
59 , Query(..)
69 , Query(..)
60 , Search(..)
70 , Search(..)
61 , SearchResult(..)
71 , SearchResult(..)
@@ -98,7 +108,10 @@ module Database.Bloodhound.Types
98 , RegexpFlags(..)
108 , RegexpFlags(..)
99 , RegexpFlag(..)
109 , RegexpFlag(..)
100 , FieldName(..)
110 , FieldName(..)
101 , IndexName(..)
112 , IndexName(..)
114+ , TemplatePattern(..)
102 , MappingName(..)
115 , MappingName(..)
103 , DocId(..)
116 , DocId(..)
104 , CacheName(..)
117 , CacheName(..)
@@ -194,6 +207,8 @@ module Database.Bloodhound.Types
194 , Bucket(..)
207 , Bucket(..)
195 , BucketAggregation(..)
208 , BucketAggregation(..)
196 , TermsAggregation(..)
209 , TermsAggregation(..)
210+ , ValueCountAggregation(..)
211+ , FilterAggregation(..)
197 , DateHistogramAggregation(..)
212 , DateHistogramAggregation(..)
199 , Highlights(..)
214 , Highlights(..)
@@ -220,13 +235,16 @@ import Control.Monad.Writer
220import Data.Aeson
235import Data.Aeson
221import Data.Aeson.Types (Pair, emptyObject, parseMaybe)
236import Data.Aeson.Types (Pair, emptyObject, parseMaybe)
222import qualified Data.ByteString.Lazy.Char8 as L
237import qualified Data.ByteString.Lazy.Char8 as L
223−import Data.List (nub)
238+import Data.List (nub, foldl')
224import Data.List.NonEmpty (NonEmpty (..), toList)
239import Data.List.NonEmpty (NonEmpty (..), toList)
240+import qualified Data.HashMap.Strict as HM (union)
225import qualified Data.Map.Strict as M
241import qualified Data.Map.Strict as M
226import Data.Text (Text)
243import Data.Text (Text)
227import qualified Data.Text as T
244import qualified Data.Text as T
228import Data.Time.Clock (UTCTime)
245import Data.Time.Clock (UTCTime)
229import qualified Data.Vector as V
246import qualified Data.Vector as V
230import GHC.Generics (Generic)
248import GHC.Generics (Generic)
231import Network.HTTP.Client
249import Network.HTTP.Client
232import qualified Network.HTTP.Types.Method as NHTM
250import qualified Network.HTTP.Types.Method as NHTM
@@ -344,6 +362,20 @@ data FieldType = GeoPointType
344data FieldDefinition =
362data FieldDefinition =
345 FieldDefinition { fieldType :: FieldType } deriving (Eq, Show)
363 FieldDefinition { fieldType :: FieldType } deriving (Eq, Show)
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, ...]@
371+ https://www.elastic.co/guide/en/elasticsearch/reference/1.7/indices-templates.html
374+ IndexTemplate { templatePattern :: TemplatePattern
375+ , templateSettings :: Maybe IndexSettings
376+ , templateMappings :: [Value]
347data MappingField =
379data MappingField =
348 MappingField { mappingFieldName :: FieldName
380 MappingField { mappingFieldName :: FieldName
349 , fieldDefinition :: FieldDefinition } deriving (Eq, Show)
381 , fieldDefinition :: FieldDefinition } deriving (Eq, Show)
@@ -371,14 +403,95 @@ data BulkOperation =
371 | BulkUpdate IndexName MappingName DocId Value deriving (Eq, Show)
403 | BulkUpdate IndexName MappingName DocId Value deriving (Eq, Show)
373{-| 'EsResult' describes the standard wrapper JSON document that you see in
405{-| '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.
376data EsResult a = EsResult { _index :: Text
408data EsResult a = EsResult { _index :: Text
377 , _type :: Text
409 , _type :: Text
378 , _id :: Text
410 , _id :: Text
379− , _version :: Int
411+ , foundResult :: Maybe (EsResultFound a)} deriving (Eq, Show)
380− , found :: Maybe Bool
412+
414+{-| 'EsResultFound' contains the document and its metadata inside of an
415+ 'EsResult' when the document was successfully found.
417+data EsResultFound a = EsResultFound { _version :: DocVersion
381− , _source :: a } deriving (Eq, Show)
418+ , _source :: a } deriving (Eq, Show)
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>>.
425+newtype DocVersion = DocVersion {
426+ docVersionNumber :: Int
427+ } deriving (Eq, Show, Ord, ToJSON)
429+-- | Smart constructor for in-range doc version
430+mkDocVersion :: Int -> Maybe DocVersion
432+ | i >= (docVersionNumber minBound) && i <= (docVersionNumber maxBound) =
434+ | otherwise = Nothing
437+{-| 'ExternalDocVersion' is a convenience wrapper if your code uses its
438+own version numbers instead of ones from ES.
440+newtype ExternalDocVersion = ExternalDocVersion DocVersion
441+ deriving (Eq, Show, Ord, Bounded, Enum, ToJSON)
443+{-| 'VersionControl' is specified when indexing documents as a
444+optimistic concurrency control.
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
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)
477+{-| 'DocumentParent' is used to specify a parent document.
479+newtype DocumentParent = DocumentParent DocId
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'
486+data IndexDocumentSettings =
487+ IndexDocumentSettings { idsVersionControl :: VersionControl
488+ , idsParent :: Maybe DocumentParent
489+ } deriving (Eq, Show)
491+{-| Reasonable default settings. Chooses no version control and no parent.
493+defaultIndexDocumentSettings :: IndexDocumentSettings
494+defaultIndexDocumentSettings = IndexDocumentSettings NoVersionControl Nothing
383{-| 'Sort' is a synonym for a list of 'SortSpec's. Sort behavior is order
496{-| 'Sort' is a synonym for a list of 'SortSpec's. Sort behavior is order
384 dependent with later sorts acting as tie-breakers for earlier sorts.
497 dependent with later sorts acting as tie-breakers for earlier sorts.
@@ -474,6 +587,14 @@ newtype Server = Server Text deriving (Eq, Show)
475newtype IndexName = IndexName Text deriving (Eq, Generic, Show)
588newtype IndexName = IndexName Text deriving (Eq, Generic, Show)
590+{-| 'TemplateName' is used to describe which template to query/create/delete
592+newtype TemplateName = TemplateName Text deriving (Eq, Show, Generic)
594+{-| 'TemplatePattern' represents a pattern which is matched against index names
596+newtype TemplatePattern = TemplatePattern Text deriving (Eq, Show, Generic)
477{-| 'MappingName' is part of mappings which are how ES describes and schematizes
598{-| 'MappingName' is part of mappings which are how ES describes and schematizes
478 the data in the indices.
599 the data in the indices.
@@ -494,6 +615,12 @@ newtype QueryString = QueryString Text deriving (Eq, Generic, Show)
495newtype FieldName = FieldName Text deriving (Eq, Show)
616newtype FieldName = FieldName Text deriving (Eq, Show)
619+{-| 'Script' is often used in place of 'FieldName' to specify more
620+complex ways of extracting a value from a document.
622+newtype Script = Script { scriptText :: Text } deriving (Eq, Show)
497{-| 'CacheName' is used in 'RegexpFilter' for describing the
624{-| 'CacheName' is used in 'RegexpFilter' for describing the
498 'CacheKey' keyed caching behavior.
625 'CacheKey' keyed caching behavior.
@@ -610,6 +737,7 @@ data Search = Search { queryBody :: Maybe Query
610 , trackSortScores :: TrackSortScores
737 , trackSortScores :: TrackSortScores
611 , from :: From
738 , from :: From
612 , size :: Size
739 , size :: Size
740+ , fields :: Maybe [FieldName]
613 , source :: Maybe Source } deriving (Eq, Show)
741 , source :: Maybe Source } deriving (Eq, Show)
615data Source =
743data Source =
@@ -1006,6 +1134,7 @@ data Filter = AndFilter [Filter] Cache
1006 | LimitFilter Int
1134 | LimitFilter Int
1007 | MissingFilter FieldName Existence NullValue
1135 | MissingFilter FieldName Existence NullValue
1008 | PrefixFilter FieldName PrefixValue Cache
1136 | PrefixFilter FieldName PrefixValue Cache
1137+ | QueryFilter Query Cache
1009 | RangeFilter FieldName RangeValue RangeExecution Cache
1138 | RangeFilter FieldName RangeValue RangeExecution Cache
1010 | RegexpFilter FieldName Regexp RegexpFlags CacheName Cache CacheKey
1139 | RegexpFilter FieldName Regexp RegexpFlags CacheName Cache CacheKey
1011 | TermFilter Term Cache
1140 | TermFilter Term Cache
@@ -1210,7 +1339,9 @@ data Interval = Year
1210 | FractionalInterval Float TimeInterval deriving (Eq, Show)
1339 | FractionalInterval Float TimeInterval deriving (Eq, Show)
1212data Aggregation = TermsAgg TermsAggregation
1341data Aggregation = TermsAgg TermsAggregation
1213− | DateHistogramAgg DateHistogramAggregation deriving (Eq, Show)
1342+ | DateHistogramAgg DateHistogramAggregation
1343+ | ValueCountAgg ValueCountAggregation
1344+ | FilterAgg FilterAggregation deriving (Eq, Show)
1216data TermsAggregation = TermsAggregation { term :: Either Text Text
1347data TermsAggregation = TermsAggregation { term :: Either Text Text
@@ -1228,6 +1359,7 @@ data TermsAggregation = TermsAggregation { term :: Either Text Text
1228data DateHistogramAggregation = DateHistogramAggregation { dateField :: FieldName
1359data DateHistogramAggregation = DateHistogramAggregation { dateField :: FieldName
1229 , dateInterval :: Interval
1360 , dateInterval :: Interval
1230 , dateFormat :: Maybe Text
1361 , dateFormat :: Maybe Text
1362+ -- pre and post deprecated in 1.5
1231 , datePreZone :: Maybe Text
1363 , datePreZone :: Maybe Text
1232 , datePostZone :: Maybe Text
1364 , datePostZone :: Maybe Text
1233 , datePreOffset :: Maybe Text
1365 , datePreOffset :: Maybe Text
@@ -1235,6 +1367,13 @@ data DateHistogramAggregation = DateHistogramAggregation { dateField :: Fie
1235 , dateAggs :: Maybe Aggregations
1367 , dateAggs :: Maybe Aggregations
1236 } deriving (Eq, Show)
1368 } deriving (Eq, Show)
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)
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)
1239mkTermsAggregation :: Text -> TermsAggregation
1378mkTermsAggregation :: Text -> TermsAggregation
1240mkTermsAggregation t = TermsAggregation (Left t) Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
1379mkTermsAggregation t = TermsAggregation (Left t) Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
@@ -1308,6 +1447,13 @@ instance ToJSON Aggregation where
1308 "post_offset" .= postOffset
1447 "post_offset" .= postOffset
1310 "aggs" .= dateHistoAggs ]
1449 "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
1312type AggregationResults = M.Map Text Value
1458type AggregationResults = M.Map Text Value
@@ -1447,6 +1593,13 @@ instance ToJSON Filter where
1447 object [fieldName .= fieldValue
1593 object [fieldName .= fieldValue
1448 , "_cache" .= cache]]
1594 , "_cache" .= cache]]
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 ]]
1450 toJSON (RangeFilter (FieldName fieldName) rangeValue rangeExecution cache) =
1603 toJSON (RangeFilter (FieldName fieldName) rangeValue rangeExecution cache) =
1451 object ["range" .=
1604 object ["range" .=
1452 object [ fieldName .= object (rangeValueToPair rangeValue)
1605 object [ fieldName .= object (rangeValueToPair rangeValue)
@@ -1887,6 +2040,8 @@ instance ToJSON IgnoreTermFrequency
1887instance ToJSON MaxQueryTerms
2040instance ToJSON MaxQueryTerms
1888instance ToJSON TypeName
2041instance ToJSON TypeName
1889instance ToJSON IndexName
2042instance ToJSON IndexName
2043+instance ToJSON TemplateName
2044+instance ToJSON TemplatePattern
1890instance ToJSON BoostTerms
2045instance ToJSON BoostTerms
1891instance ToJSON MaxWordLength
2046instance ToJSON MaxWordLength
1892instance ToJSON MinWordLength
2047instance ToJSON MinWordLength
@@ -1923,22 +2078,43 @@ instance FromJSON Status where
1925instance ToJSON IndexSettings where
2080instance ToJSON IndexSettings where
1926− toJSON (IndexSettings s r) = object ["settings" .= object ["shards" .= s, "replicas" .= r]]
2081+ toJSON (IndexSettings s r) = object ["settings" .=
1927−
2082+ object ["index" .=
2083+ object ["number_of_shards" .= s, "number_of_replicas" .= r]
2087+instance ToJSON IndexTemplate where
2088+ toJSON (IndexTemplate p s m) = merge
2089+ (object [ "template" .= p
2090+ , "mappings" .= foldl' merge (object []) m
2094+ merge (Object o1) (Object o2) = toJSON $ HM.union o1 o2
2096+ merge _ _ = undefined
1929instance (FromJSON a) => FromJSON (EsResult a) where
2098instance (FromJSON a) => FromJSON (EsResult a) where
1930− parseJSON (Object v) = EsResult <$>
2099+ parseJSON jsonVal@(Object v) = do
2100+ found <- v .:? "found" .!= False
2102+ then parseJSON jsonVal
2103+ else return Nothing
1931− v .: "_index" <*>
2104+ EsResult <$> v .: "_index" <*>
1932− v .: "_type" <*>
2105+ v .: "_type" <*>
1933− v .: "_id" <*>
2106+ v .: "_id" <*>
1934− v .: "_version" <*>
2107+ pure fr
1937 parseJSON _ = empty
2108 parseJSON _ = empty
2110+instance (FromJSON a) => FromJSON (EsResultFound a) where
2111+ parseJSON (Object v) = EsResultFound <$>
2112+ v .: "_version" <*>
2114+ parseJSON _ = empty
1940instance ToJSON Search where
2116instance 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) =
1942 omitNulls [ "query" .= query
2118 omitNulls [ "query" .= query
1943 , "filter" .= sFilter
2119 , "filter" .= sFilter
1944 , "sort" .= sort
2120 , "sort" .= sort
@@ -1947,6 +2123,7 @@ instance ToJSON Search where
1947 , "from" .= sFrom
2123 , "from" .= sFrom
1948 , "size" .= sSize
2124 , "size" .= sSize
1949 , "track_scores" .= sTrackSortScores
2125 , "track_scores" .= sTrackSortScores
2126+ , "fields" .= sFields
1950 , "_source" .= sSource]
2127 , "_source" .= sSource]
@@ -2212,3 +2389,26 @@ instance FromJSON ShardResult where
2212 v .: "successful" <*>
2389 v .: "successful" <*>
2213 v .: "failed"
2390 v .: "failed"
2214 parseJSON _ = empty
2391 parseJSON _ = empty
2394+instance FromJSON DocVersion where
2397+ maybe (fail "DocVersion out of range") return $ mkDocVersion i
2399+instance Bounded DocVersion where
2400+ minBound = DocVersion 1
2401+ maxBound = DocVersion 9200000000000000000 -- 9.2e+18
2403+instance Enum DocVersion where
2405+ | x /= maxBound = DocVersion (succ $ docVersionNumber x)
2406+ | otherwise = succError "DocVersion"
2408+ | x /= minBound = DocVersion (pred $ docVersionNumber x)
2409+ | otherwise = predError "DocVersion"
2411+ fromMaybe (error $ show i ++ " out of DocVersion range") $ mkDocVersion i
2412+ fromEnum = docVersionNumber
2413+ enumFrom = boundedEnumFrom
2414+ enumFromThen = boundedEnumFromThen