@@ -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 , SearchType(..)
71 , SearchType(..)
@@ -95,7 +105,10 @@ module Database.Bloodhound.Types
95 , RegexpFlags(..)
105 , RegexpFlags(..)
96 , RegexpFlag(..)
106 , RegexpFlag(..)
97 , FieldName(..)
107 , FieldName(..)
98 , IndexName(..)
109 , IndexName(..)
111+ , TemplatePattern(..)
99 , MappingName(..)
112 , MappingName(..)
100 , DocId(..)
113 , DocId(..)
101 , CacheName(..)
114 , CacheName(..)
@@ -191,6 +204,8 @@ module Database.Bloodhound.Types
191 , Bucket(..)
204 , Bucket(..)
192 , BucketAggregation(..)
205 , BucketAggregation(..)
193 , TermsAggregation(..)
206 , TermsAggregation(..)
207+ , ValueCountAggregation(..)
208+ , FilterAggregation(..)
194 , DateHistogramAggregation(..)
209 , DateHistogramAggregation(..)
196 , Highlights(..)
211 , Highlights(..)
@@ -217,13 +232,16 @@ import Control.Monad.Writer
217import Data.Aeson
232import Data.Aeson
218import Data.Aeson.Types (Pair, emptyObject, parseMaybe)
233import Data.Aeson.Types (Pair, emptyObject, parseMaybe)
219import qualified Data.ByteString.Lazy.Char8 as L
234import qualified Data.ByteString.Lazy.Char8 as L
220−import Data.List (nub)
235+import Data.List (nub, foldl')
221import Data.List.NonEmpty (NonEmpty (..), toList)
236import Data.List.NonEmpty (NonEmpty (..), toList)
237+import qualified Data.HashMap.Strict as HM (union)
222import qualified Data.Map.Strict as M
238import qualified Data.Map.Strict as M
223import Data.Text (Text)
240import Data.Text (Text)
224import qualified Data.Text as T
241import qualified Data.Text as T
225import Data.Time.Clock (UTCTime)
242import Data.Time.Clock (UTCTime)
226import qualified Data.Vector as V
243import qualified Data.Vector as V
227import GHC.Generics (Generic)
245import GHC.Generics (Generic)
228import Network.HTTP.Client
246import Network.HTTP.Client
229import qualified Network.HTTP.Types.Method as NHTM
247import qualified Network.HTTP.Types.Method as NHTM
@@ -341,6 +359,20 @@ data FieldType = GeoPointType
341data FieldDefinition =
359data FieldDefinition =
342 FieldDefinition { fieldType :: FieldType } deriving (Eq, Show)
360 FieldDefinition { fieldType :: FieldType } deriving (Eq, Show)
362+{-| An 'IndexTemplate' defines a template that will automatically be
363+ applied to new indices created. The templates include both
364+ 'IndexSettings' and mappings, and a simple 'TemplatePattern' that
365+ controls if the template will be applied to the index created.
366+ Specify mappings as follows: @[toJSON TweetMapping, ...]@
368+ https://www.elastic.co/guide/en/elasticsearch/reference/1.7/indices-templates.html
371+ IndexTemplate { templatePattern :: TemplatePattern
372+ , templateSettings :: Maybe IndexSettings
373+ , templateMappings :: [Value]
344data MappingField =
376data MappingField =
345 MappingField { mappingFieldName :: FieldName
377 MappingField { mappingFieldName :: FieldName
346 , fieldDefinition :: FieldDefinition } deriving (Eq, Show)
378 , fieldDefinition :: FieldDefinition } deriving (Eq, Show)
@@ -368,14 +400,95 @@ data BulkOperation =
368 | BulkUpdate IndexName MappingName DocId Value deriving (Eq, Show)
400 | BulkUpdate IndexName MappingName DocId Value deriving (Eq, Show)
370{-| 'EsResult' describes the standard wrapper JSON document that you see in
402{-| 'EsResult' describes the standard wrapper JSON document that you see in
371− successful Elasticsearch responses.
403+ successful Elasticsearch lookups or lookups that couldn't find the document.
373data EsResult a = EsResult { _index :: Text
405data EsResult a = EsResult { _index :: Text
374 , _type :: Text
406 , _type :: Text
375 , _id :: Text
407 , _id :: Text
376− , _version :: Int
408+ , foundResult :: Maybe (EsResultFound a)} deriving (Eq, Show)
377− , found :: Maybe Bool
409+
411+{-| 'EsResultFound' contains the document and its metadata inside of an
412+ 'EsResult' when the document was successfully found.
414+data EsResultFound a = EsResultFound { _version :: DocVersion
378− , _source :: a } deriving (Eq, Show)
415+ , _source :: a } deriving (Eq, Show)
419+{-| 'DocVersion' is an integer version number for a document between 1
420+and 9.2e+18 used for <<https://www.elastic.co/guide/en/elasticsearch/guide/current/optimistic-concurrency-control.html optimistic concurrency control>>.
422+newtype DocVersion = DocVersion {
423+ docVersionNumber :: Int
424+ } deriving (Eq, Show, Ord, ToJSON)
426+-- | Smart constructor for in-range doc version
427+mkDocVersion :: Int -> Maybe DocVersion
429+ | i >= (docVersionNumber minBound) && i <= (docVersionNumber maxBound) =
431+ | otherwise = Nothing
434+{-| 'ExternalDocVersion' is a convenience wrapper if your code uses its
435+own version numbers instead of ones from ES.
437+newtype ExternalDocVersion = ExternalDocVersion DocVersion
438+ deriving (Eq, Show, Ord, Bounded, Enum, ToJSON)
440+{-| 'VersionControl' is specified when indexing documents as a
441+optimistic concurrency control.
443+data VersionControl = NoVersionControl
444+ -- ^ Don't send a version. This is a pure overwrite.
445+ | InternalVersion DocVersion
446+ -- ^ Use the default ES versioning scheme. Only
447+ -- index the document if the version is the same
448+ -- as the one specified. Only applicable to
449+ -- updates, as you should be getting Version from
451+ | ExternalGT ExternalDocVersion
452+ -- ^ Use your own version numbering. Only index
453+ -- the document if the version is strictly higher
454+ -- OR the document doesn't exist. The given
455+ -- version will be used as the new version number
456+ -- for the stored document. N.B. All updates must
457+ -- increment this number, meaning there is some
458+ -- global, external ordering of updates.
459+ | ExternalGTE ExternalDocVersion
460+ -- ^ Use your own version numbering. Only index
461+ -- the document if the version is equal or higher
462+ -- than the stored version. Will succeed if there
463+ -- is no existing document. The given version will
464+ -- be used as the new version number for the
465+ -- stored document. Use with care, as this could
466+ -- result in data loss.
467+ | ForceVersion ExternalDocVersion
468+ -- ^ The document will always be indexed and the
469+ -- given version will be the new version. This is
470+ -- typically used for correcting errors. Use with
471+ -- care, as this could result in data loss.
472+ deriving (Show, Eq, Ord)
474+{-| 'DocumentParent' is used to specify a parent document.
476+newtype DocumentParent = DocumentParent DocId
479+{-| 'IndexDocumentSettings' are special settings supplied when indexing
480+a document. For the best backwards compatiblity when new fields are
481+added, you should probably prefer to start with 'defaultIndexDocumentSettings'
483+data IndexDocumentSettings =
484+ IndexDocumentSettings { idsVersionControl :: VersionControl
485+ , idsParent :: Maybe DocumentParent
486+ } deriving (Eq, Show)
488+{-| Reasonable default settings. Chooses no version control and no parent.
490+defaultIndexDocumentSettings :: IndexDocumentSettings
491+defaultIndexDocumentSettings = IndexDocumentSettings NoVersionControl Nothing
380{-| 'Sort' is a synonym for a list of 'SortSpec's. Sort behavior is order
493{-| 'Sort' is a synonym for a list of 'SortSpec's. Sort behavior is order
381 dependent with later sorts acting as tie-breakers for earlier sorts.
494 dependent with later sorts acting as tie-breakers for earlier sorts.
@@ -471,6 +584,14 @@ newtype Server = Server Text deriving (Eq, Show)
472newtype IndexName = IndexName Text deriving (Eq, Generic, Show)
585newtype IndexName = IndexName Text deriving (Eq, Generic, Show)
587+{-| 'TemplateName' is used to describe which template to query/create/delete
589+newtype TemplateName = TemplateName Text deriving (Eq, Show, Generic)
591+{-| 'TemplatePattern' represents a pattern which is matched against index names
593+newtype TemplatePattern = TemplatePattern Text deriving (Eq, Show, Generic)
474{-| 'MappingName' is part of mappings which are how ES describes and schematizes
595{-| 'MappingName' is part of mappings which are how ES describes and schematizes
475 the data in the indices.
596 the data in the indices.
@@ -491,6 +612,12 @@ newtype QueryString = QueryString Text deriving (Eq, Generic, Show)
492newtype FieldName = FieldName Text deriving (Eq, Show)
613newtype FieldName = FieldName Text deriving (Eq, Show)
616+{-| 'Script' is often used in place of 'FieldName' to specify more
617+complex ways of extracting a value from a document.
619+newtype Script = Script { scriptText :: Text } deriving (Eq, Show)
494{-| 'CacheName' is used in 'RegexpFilter' for describing the
621{-| 'CacheName' is used in 'RegexpFilter' for describing the
495 'CacheKey' keyed caching behavior.
622 'CacheKey' keyed caching behavior.
@@ -607,7 +734,8 @@ data Search = Search { queryBody :: Maybe Query
607 , trackSortScores :: TrackSortScores
734 , trackSortScores :: TrackSortScores
608 , from :: From
735 , from :: From
609 , size :: Size
736 , size :: Size
610− , searchType :: SearchType } deriving (Eq, Show)
737+ , searchType :: SearchType
738+ , fields :: Maybe [FieldName] } deriving (Eq, Show)
612data SearchType = SearchTypeQueryThenFetch
740data SearchType = SearchTypeQueryThenFetch
613 | SearchTypeDfsQueryThenFetch
741 | SearchTypeDfsQueryThenFetch
@@ -997,6 +1125,7 @@ data Filter = AndFilter [Filter] Cache
997 | LimitFilter Int
1125 | LimitFilter Int
998 | MissingFilter FieldName Existence NullValue
1126 | MissingFilter FieldName Existence NullValue
999 | PrefixFilter FieldName PrefixValue Cache
1127 | PrefixFilter FieldName PrefixValue Cache
1128+ | QueryFilter Query Cache
1000 | RangeFilter FieldName RangeValue RangeExecution Cache
1129 | RangeFilter FieldName RangeValue RangeExecution Cache
1001 | RegexpFilter FieldName Regexp RegexpFlags CacheName Cache CacheKey
1130 | RegexpFilter FieldName Regexp RegexpFlags CacheName Cache CacheKey
1002 | TermFilter Term Cache
1131 | TermFilter Term Cache
@@ -1204,7 +1333,9 @@ data Interval = Year
1204 | FractionalInterval Float TimeInterval deriving (Eq, Show)
1333 | FractionalInterval Float TimeInterval deriving (Eq, Show)
1206data Aggregation = TermsAgg TermsAggregation
1335data Aggregation = TermsAgg TermsAggregation
1207− | DateHistogramAgg DateHistogramAggregation deriving (Eq, Show)
1336+ | DateHistogramAgg DateHistogramAggregation
1337+ | ValueCountAgg ValueCountAggregation
1338+ | FilterAgg FilterAggregation deriving (Eq, Show)
1210data TermsAggregation = TermsAggregation { term :: Either Text Text
1341data TermsAggregation = TermsAggregation { term :: Either Text Text
@@ -1230,6 +1361,13 @@ data DateHistogramAggregation = DateHistogramAggregation { dateField :: Fie
1230 , dateAggs :: Maybe Aggregations
1361 , dateAggs :: Maybe Aggregations
1231 } deriving (Eq, Show)
1362 } deriving (Eq, Show)
1364+-- | See <https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-valuecount-aggregation.html> for more information.
1365+data ValueCountAggregation = FieldValueCount FieldName
1366+ | ScriptValueCount Script deriving (Eq, Show)
1368+-- | 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.
1369+data FilterAggregation = FilterAggregation { faFilter :: Filter
1370+ , faAggs :: Maybe Aggregations} deriving (Eq, Show)
1234mkTermsAggregation :: Text -> TermsAggregation
1372mkTermsAggregation :: Text -> TermsAggregation
1235mkTermsAggregation t = TermsAggregation (Left t) Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
1373mkTermsAggregation t = TermsAggregation (Left t) Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
@@ -1303,6 +1441,13 @@ instance ToJSON Aggregation where
1303 "post_offset" .= postOffset
1441 "post_offset" .= postOffset
1305 "aggs" .= dateHistoAggs ]
1443 "aggs" .= dateHistoAggs ]
1444+ toJSON (ValueCountAgg a) = object ["value_count" .= v]
1445+ where v = case a of
1446+ (FieldValueCount (FieldName n)) -> object ["field" .= n]
1447+ (ScriptValueCount (Script s)) -> object ["script" .= s]
1448+ toJSON (FilterAgg (FilterAggregation filt ags)) =
1449+ omitNulls [ "filter" .= filt
1307type AggregationResults = M.Map Text Value
1452type AggregationResults = M.Map Text Value
@@ -1442,6 +1587,13 @@ instance ToJSON Filter where
1442 object [fieldName .= fieldValue
1587 object [fieldName .= fieldValue
1443 , "_cache" .= cache]]
1588 , "_cache" .= cache]]
1590+ toJSON (QueryFilter query False) =
1591+ object ["query" .= toJSON query ]
1592+ toJSON (QueryFilter query True) =
1593+ object ["fquery" .=
1594+ object [ "query" .= toJSON query
1595+ , "_cache" .= True ]]
1445 toJSON (RangeFilter (FieldName fieldName) rangeValue rangeExecution cache) =
1597 toJSON (RangeFilter (FieldName fieldName) rangeValue rangeExecution cache) =
1446 object ["range" .=
1598 object ["range" .=
1447 object [ fieldName .= object (rangeValueToPair rangeValue)
1599 object [ fieldName .= object (rangeValueToPair rangeValue)
@@ -1882,6 +2034,8 @@ instance ToJSON IgnoreTermFrequency
1882instance ToJSON MaxQueryTerms
2034instance ToJSON MaxQueryTerms
1883instance ToJSON TypeName
2035instance ToJSON TypeName
1884instance ToJSON IndexName
2036instance ToJSON IndexName
2037+instance ToJSON TemplateName
2038+instance ToJSON TemplatePattern
1885instance ToJSON BoostTerms
2039instance ToJSON BoostTerms
1886instance ToJSON MaxWordLength
2040instance ToJSON MaxWordLength
1887instance ToJSON MinWordLength
2041instance ToJSON MinWordLength
@@ -1918,22 +2072,43 @@ instance FromJSON Status where
1920instance ToJSON IndexSettings where
2074instance ToJSON IndexSettings where
1921− toJSON (IndexSettings s r) = object ["settings" .= object ["shards" .= s, "replicas" .= r]]
2075+ toJSON (IndexSettings s r) = object ["settings" .=
1922−
2076+ object ["index" .=
2077+ object ["number_of_shards" .= s, "number_of_replicas" .= r]
2081+instance ToJSON IndexTemplate where
2082+ toJSON (IndexTemplate p s m) = merge
2083+ (object [ "template" .= p
2084+ , "mappings" .= foldl' merge (object []) m
2088+ merge (Object o1) (Object o2) = toJSON $ HM.union o1 o2
2090+ merge _ _ = undefined
1924instance (FromJSON a) => FromJSON (EsResult a) where
2092instance (FromJSON a) => FromJSON (EsResult a) where
1925− parseJSON (Object v) = EsResult <$>
2093+ parseJSON jsonVal@(Object v) = do
2094+ found <- v .:? "found" .!= False
2096+ then parseJSON jsonVal
2097+ else return Nothing
1926− v .: "_index" <*>
2098+ EsResult <$> v .: "_index" <*>
1927− v .: "_type" <*>
2099+ v .: "_type" <*>
1928− v .: "_id" <*>
2100+ v .: "_id" <*>
1929− v .: "_version" <*>
2101+ pure fr
1932 parseJSON _ = empty
2102 parseJSON _ = empty
2104+instance (FromJSON a) => FromJSON (EsResultFound a) where
2105+ parseJSON (Object v) = EsResultFound <$>
2106+ v .: "_version" <*>
2108+ parseJSON _ = empty
1935instance ToJSON Search where
2110instance ToJSON Search where
1936− toJSON (Search query sFilter sort searchAggs highlight sTrackSortScores sFrom sSize _) =
2111+ toJSON (Search query sFilter sort searchAggs highlight sTrackSortScores sFrom sSize _ sFields) =
1937 omitNulls [ "query" .= query
2112 omitNulls [ "query" .= query
1938 , "filter" .= sFilter
2113 , "filter" .= sFilter
1939 , "sort" .= sort
2114 , "sort" .= sort
@@ -1941,7 +2116,8 @@ instance ToJSON Search where
1941 , "highlight" .= highlight
2116 , "highlight" .= highlight
1942 , "from" .= sFrom
2117 , "from" .= sFrom
1943 , "size" .= sSize
2118 , "size" .= sSize
1944− , "track_scores" .= sTrackSortScores]
2119+ , "track_scores" .= sTrackSortScores
2120+ , "fields" .= sFields]
1947instance ToJSON FieldHighlight where
2123instance ToJSON FieldHighlight where
@@ -2188,3 +2364,26 @@ instance FromJSON ShardResult where
2188 v .: "successful" <*>
2364 v .: "successful" <*>
2189 v .: "failed"
2365 v .: "failed"
2190 parseJSON _ = empty
2366 parseJSON _ = empty
2369+instance FromJSON DocVersion where
2372+ maybe (fail "DocVersion out of range") return $ mkDocVersion i
2374+instance Bounded DocVersion where
2375+ minBound = DocVersion 1
2376+ maxBound = DocVersion 9200000000000000000 -- 9.2e+18
2378+instance Enum DocVersion where
2380+ | x /= maxBound = DocVersion (succ $ docVersionNumber x)
2381+ | otherwise = succError "DocVersion"
2383+ | x /= minBound = DocVersion (pred $ docVersionNumber x)
2384+ | otherwise = predError "DocVersion"
2386+ fromMaybe (error $ show i ++ " out of DocVersion range") $ mkDocVersion i
2387+ fromEnum = docVersionNumber
2388+ enumFrom = boundedEnumFrom
2389+ enumFromThen = boundedEnumFromThen