git.iko.soy/typeable/bloodhound
on v0.10.0.0 help sign in
976 lines 29 kB history raw
1Bloodhound [![TravisCI](https://travis-ci.org/bitemyapp/bloodhound.svg)](https://travis-ci.org/bitemyapp/bloodhound) [![Hackage](https://img.shields.io/hackage/v/bloodhound.svg?style=flat)](https://hackage.haskell.org/package/bloodhound)
2==========
4![Bloodhound (dog)](./bloodhound.jpg)
6Elasticsearch client and query DSL for Haskell
7==============================================
9Why?
10----
12Search doesn't have to be hard. Let the dog do it.
14Endorsements
15------------
17"Bloodhound makes Elasticsearch almost tolerable!" - Almost-gruntled user
19"ES is a nightmare but Bloodhound at least makes it tolerable." - Same user, later opinion.
21Version compatibility
22---------------------
24Elasticsearch \>= 1.0 is recommended. Bloodhound mostly works with 0.9.x, but I don't recommend it if you expect everything to work. As of Bloodhound 0.3 all \>=1.0 versions of Elasticsearch work.
26Current versions we test against are 1.0.3, 1.1.2, 1.2.3, 1.3.2, and 1.4.0. We also check that GHC 7.6 and 7.8 both build and pass tests. See our [TravisCI](https://travis-ci.org/bitemyapp/bloodhound) to learn more.
28Stability
29---------
31Bloodhound is stable for production use. I will strive to avoid breaking API compatibility from here on forward, but dramatic features like a type-safe, fully integrated mapping API may require breaking things in the future.
33Hackage page and Haddock documentation
34======================================
36<http://hackage.haskell.org/package/bloodhound>
38Elasticsearch Tutorial
39======================
41It's not using Bloodhound, but if you need an introduction to or overview of Elasticsearch and how to use it, you can use [this screencast](http://vimeo.com/106463167).
43Examples
44========
46Index Operations
47----------------
49### Create Index
51``` {.haskell}
53-- Formatted for use in ghci, so there are "let"s in front of the decls.
55-- if you see :{ and :}, they're so you can copy-paste
56-- the multi-line examples into your ghci REPL.
58:set -XDeriveGeneric
59:{
60import Control.Applicative
61import Database.Bloodhound
62import Data.Aeson
63import Data.Either (Either(..))
64import Data.Maybe (fromJust)
65import Data.Time.Calendar (Day(..))
66import Data.Time.Clock (secondsToDiffTime, UTCTime(..))
67import Data.Text (Text)
68import GHC.Generics (Generic)
69import Network.HTTP.Client
70import qualified Network.HTTP.Types.Status as NHTS
72-- no trailing slashes in servers, library handles building the path.
73let testServer = (Server "http://localhost:9200")
74let testIndex = IndexName "twitter"
75let testMapping = MappingName "tweet"
76let withBH' = withBH defaultManagerSettings testServer
78-- defaultIndexSettings is exported by Database.Bloodhound as well
79let defaultIndexSettings = IndexSettings (ShardCount 3) (ReplicaCount 2)
81-- createIndex returns MonadBH m => m Reply. You can use withBH for
82 one-off commands or you can use runBH to group together commands
83 and to pass in your own HTTP manager for pipelining.
85-- response :: Reply, Reply is a synonym for Network.HTTP.Conduit.Response
86response <- withBH' $ createIndex defaultIndexSettings testIndex
87:}
89```
91### Delete Index
93#### Code
95``` {.haskell}
97-- response :: Reply
98response <- withBH' $ deleteIndex testIndex
100```
102#### Example Response
104``` {.haskell}
106-- print response if it was a success
107Response {responseStatus = Status {statusCode = 200, statusMessage = "OK"}
108 , responseVersion = HTTP/1.1
109 , responseHeaders = [("Content-Type", "application/json; charset=UTF-8")
110 , ("Content-Length", "21")]
111 , responseBody = "{\"acknowledged\":true}"
112 , responseCookieJar = CJ {expose = []}
113 , responseClose' = ResponseClose}
115-- if the index to be deleted didn't exist anyway
116Response {responseStatus = Status {statusCode = 404, statusMessage = "Not Found"}
117 , responseVersion = HTTP/1.1
118 , responseHeaders = [("Content-Type", "application/json; charset=UTF-8")
119 , ("Content-Length","65")]
120 , responseBody = "{\"error\":\"IndexMissingException[[twitter] missing]\",\"status\":404}"
121 , responseCookieJar = CJ {expose = []}
122 , responseClose' = ResponseClose}
124```
126### Refresh Index
128#### Note, you **have** to do this if you expect to read what you just wrote
130``` {.haskell}
132resp <- withBH' $ refreshIndex testIndex
134```
136#### Example Response
138``` {.haskell}
140-- print resp on success
141Response {responseStatus = Status {statusCode = 200, statusMessage = "OK"}
142 , responseVersion = HTTP/1.1
143 , responseHeaders = [("Content-Type", "application/json; charset=UTF-8")
144 , ("Content-Length","50")]
145 , responseBody = "{\"_shards\":{\"total\":10,\"successful\":5,\"failed\":0}}"
146 , responseCookieJar = CJ {expose = []}
147 , responseClose' = ResponseClose}
149```
151Mapping Operations
152------------------
154### Create Mapping
156``` {.haskell}
158-- don't forget imports and the like at the top.
160data TweetMapping = TweetMapping deriving (Eq, Show)
162-- I know writing the JSON manually sucks.
163-- I don't have a proper data type for Mappings yet.
164-- Let me know if this is something you need.
166:{
167instance ToJSON TweetMapping where
168 toJSON TweetMapping =
169 object ["tweet" .=
170 object ["properties" .=
171 object ["location" .=
172 object ["type" .= ("geo_point" :: Text)]]]]
173:}
175resp <- withBH' $ putMapping testIndex testMapping TweetMapping
177```
179### Delete Mapping
181``` {.haskell}
183resp <- withBH' $ deleteMapping testIndex testMapping
185```
187Document Operations
188-------------------
190### Indexing Documents
192``` {.haskell}
194-- don't forget the imports and derive generic setting for ghci
195-- at the beginning of the examples.
197:{
198data Location = Location { lat :: Double
199 , lon :: Double } deriving (Eq, Generic, Show)
201data Tweet = Tweet { user :: Text
202 , postDate :: UTCTime
203 , message :: Text
204 , age :: Int
205 , location :: Location } deriving (Eq, Generic, Show)
207exampleTweet = Tweet { user = "bitemyapp"
208 , postDate = UTCTime
209 (ModifiedJulianDay 55000)
210 (secondsToDiffTime 10)
211 , message = "Use haskell!"
212 , age = 10000
213 , location = Location 40.12 (-71.34) }
215-- automagic (generic) derivation of instances because we're lazy.
216instance ToJSON Tweet
217instance FromJSON Tweet
218instance ToJSON Location
219instance FromJSON Location
220:}
222-- Should be able to toJSON and encode the data structures like this:
223-- λ> toJSON $ Location 10.0 10.0
224-- Object fromList [("lat",Number 10.0),("lon",Number 10.0)]
225-- λ> encode $ Location 10.0 10.0
226-- "{\"lat\":10,\"lon\":10}"
228resp <- withBH' $ indexDocument testIndex testMapping defaultIndexDocumentSettings exampleTweet (DocId "1")
230```
232#### Example Response
234``` {.haskell}
236Response {responseStatus =
237 Status {statusCode = 200, statusMessage = "OK"}
238 , responseVersion = HTTP/1.1, responseHeaders =
239 [("Content-Type","application/json; charset=UTF-8"),
240 ("Content-Length","75")]
241 , responseBody = "{\"_index\":\"twitter\",\"_type\":\"tweet\",\"_id\":\"1\",\"_version\":2,\"created\":false}"
242 , responseCookieJar = CJ {expose = []}, responseClose' = ResponseClose}
244```
246### Deleting Documents
248``` {.haskell}
250resp <- withBH' $ deleteDocument testIndex testMapping (DocId "1")
252```
254### Getting Documents
256``` {.haskell}
258-- n.b., you'll need the earlier imports. responseBody is from http-conduit
260resp <- withBH' $ getDocument testIndex testMapping (DocId "1")
262-- responseBody :: Response body -> body
263let body = responseBody resp
265-- you have two options, you use decode and just get Maybe (EsResult Tweet)
266-- or you can use eitherDecode and get Either String (EsResult Tweet)
268let maybeResult = decode body :: Maybe (EsResult Tweet)
269-- the explicit typing is so Aeson knows how to parse the JSON.
271-- use either if you want to know why something failed to parse.
272-- (string errors, sadly)
273let eitherResult = eitherDecode body :: Either String (EsResult Tweet)
275-- print eitherResult should look like:
276Right (EsResult {_index = "twitter"
277 , _type = "tweet"
278 , _id = "1"
279 , foundResult = Just (EsResultFound { _version = 2
280 , _source = Tweet {user = "bitemyapp"
281 , postDate = 2009-06-18 00:00:10 UTC
282 , message = "Use haskell!"
283 , age = 10000
284 , location = Location {lat = 40.12, lon = -71.34}}})})
286-- _source in EsResultFound is parametric, we dispatch the type by passing in what we expect (Tweet) as a parameter to EsResult.
288-- use the _source record accessor to get at your document
289fmap (fmap _source . foundResult) eitherResult
290Right (Just (Tweet {user = "bitemyapp"
291 , postDate = 2009-06-18 00:00:10 UTC
292 , message = "Use haskell!"
293 , age = 10000
294 , location = Location {lat = 40.12, lon = -71.34}}))
296```
298Bulk Operations
299---------------
301### Bulk create, index
303``` {.haskell}
305-- don't forget the imports and derive generic setting for ghci
306-- at the beginning of the examples.
308:{
309-- Using the earlier Tweet datatype and exampleTweet data
311-- just changing up the data a bit.
312let bulkTest = exampleTweet { user = "blah" }
313let bulkTestTwo = exampleTweet { message = "woohoo!" }
315-- create only bulk operation
316-- BulkCreate :: IndexName -> MappingName -> DocId -> Value -> BulkOperation
317let firstOp = BulkCreate testIndex
318 testMapping (DocId "3") (toJSON bulkTest)
320-- index operation "create or update"
321let sndOp = BulkIndex testIndex
322 testMapping (DocId "4") (toJSON bulkTestTwo)
324-- Some explanation, the final "Value" type that BulkIndex,
325-- BulkCreate, and BulkUpdate accept is the actual document
326-- data that your operation applies to. BulkDelete doesn't
327-- take a value because it's just deleting whatever DocId
328-- you pass.
330-- list of bulk operations
331let stream = [firstDoc, secondDoc]
333-- Fire off the actual bulk request
334-- bulk :: Vector BulkOperation -> IO Reply
335resp <- withBH' $ bulk stream
336:}
338```
340### Encoding individual bulk API operations
342``` {.haskell}
343-- the following functions are exported in Bloodhound so
344-- you can build up bulk operations yourself
345encodeBulkOperations :: V.Vector BulkOperation -> L.ByteString
346encodeBulkOperation :: BulkOperation -> L.ByteString
348-- How to use the above:
349data BulkTest = BulkTest { name :: Text } deriving (Eq, Generic, Show)
350instance FromJSON BulkTest
351instance ToJSON BulkTest
353_ <- insertData
354let firstTest = BulkTest "blah"
355let secondTest = BulkTest "bloo"
356let firstDoc = BulkIndex testIndex
357 testMapping (DocId "2") (toJSON firstTest)
358let secondDoc = BulkCreate testIndex
359 testMapping (DocId "3") (toJSON secondTest)
360let stream = V.fromList [firstDoc, secondDoc] :: V.Vector BulkOperation
362-- to encode yourself
363let firstDocEncoded = encode firstDoc :: L.ByteString
365-- to encode a vector of bulk operations
366let encodedOperations = encodeBulkOperations stream
368-- to insert into a particular server
369-- bulk :: V.Vector BulkOperation -> IO Reply
370_ <- withBH' $ bulk streamp
372```
374Search
375------
377### Querying
379#### Term Query
381``` {.haskell}
383-- exported by the Client module, just defaults some stuff.
384-- mkSearch :: Maybe Query -> Maybe Filter -> Search
385-- mkSearch query filter = Search query filter Nothing False (From 0) (Size 10) Nothing
387let query = TermQuery (Term "user" "bitemyapp") Nothing
389-- AND'ing identity filter with itself and then tacking it onto a query
390-- search should be a null-operation. I include it for the sake of example.
391-- <||> (or/plus) should make it into a search that returns everything.
393let filter = IdentityFilter <&&> IdentityFilter
395-- constructing the search object the searchByIndex function dispatches on.
396let search = mkSearch (Just query) (Just filter)
398-- you can also searchByType and specify the mapping name.
399reply <- withBH' $ searchByIndex testIndex search
401let result = eitherDecode (responseBody reply) :: Either String (SearchResult Tweet)
403λ> fmap (hits . searchHits) result
404Right [Hit {hitIndex = IndexName "twitter"
405 , hitType = MappingName "tweet"
406 , hitDocId = DocId "1"
407 , hitScore = 0.30685282
408 , hitSource = Tweet {user = "bitemyapp"
409 , postDate = 2009-06-18 00:00:10 UTC
410 , message = "Use haskell!"
411 , age = 10000
412 , location = Location {lat = 40.12, lon = -71.34}}}]
414```
416#### Match Query
418``` {.haskell}
420let query = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
421let search = mkSearch (Just query) Nothing
423```
425#### Multi-Match Query
427``` {.haskell}
429let fields = [FieldName "user", FieldName "message"]
430let query = QueryMultiMatchQuery $ mkMultiMatchQuery fields (QueryString "bitemyapp")
431let search = mkSearch (Just query) Nothing
433```
435#### Bool Query
437``` {.haskell}
439let innerQuery = QueryMatchQuery $
440 mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
441let query = QueryBoolQuery $
442 mkBoolQuery [innerQuery] [] []
443let search = mkSearch (Just query) Nothing
445```
447#### Boosting Query
449``` {.haskell}
451let posQuery = QueryMatchQuery $
452 mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
453let negQuery = QueryMatchQuery $
454 mkMatchQuery (FieldName "user") (QueryString "notmyapp")
455let query = QueryBoostingQuery $
456 BoostingQuery posQuery negQuery (Boost 0.2)
458```
460#### Rest of the query/filter types
462Just follow the pattern you've seen here and check the Hackage API documentation.
464### Sorting
466``` {.haskell}
468let sortSpec = DefaultSortSpec $ mkSort (FieldName "age") Ascending
470-- mkSort is a shortcut function that takes a FieldName and a SortOrder
471-- to generate a vanilla DefaultSort.
472-- checkt the DefaultSort type for the full list of customizable options.
474-- From and size are integers for pagination.
476-- When sorting on a field, scores are not computed. By setting TrackSortScores to true, scores will still be computed and tracked.
478-- type Sort = [SortSpec]
479-- type TrackSortScores = Bool
480-- type From = Int
481-- type Size = Int
483-- Search takes Maybe Query
484-- -> Maybe Filter
485-- -> Maybe Sort
486-- -> TrackSortScores
487-- -> From -> Size
488-- -> Maybe [FieldName]
490-- just add more sortspecs to the list if you want tie-breakers.
491let search = Search Nothing (Just IdentityFilter) (Just [sortSpec]) False (From 0) (Size 10) Nothing
493```
495### Field selection
497If you only want certain fields from the source document returned, you can
498set the "fields" field of the Search record.
500``` {.haskell}
502let search' = mkSearch (Just (MatchAllQuery Nothing)) Nothing
503 search = search' { fields = Just [FieldName "updated"] }
505```
507### Filtering
509#### And, Not, and Or filters
511Filters form a monoid and seminearring.
513``` {.haskell}
515instance Monoid Filter where
516 mempty = IdentityFilter
517 mappend a b = AndFilter [a, b] defaultCache
519instance Seminearring Filter where
520 a <||> b = OrFilter [a, b] defaultCache
522-- AndFilter and OrFilter take [Filter] as an argument.
524-- This will return anything, because IdentityFilter returns everything
525OrFilter [IdentityFilter, someOtherFilter] False
527-- This will return exactly what someOtherFilter returns
528AndFilter [IdentityFilter, someOtherFilter] False
530-- Thanks to the seminearring and monoid, the above can be expressed as:
532-- "and"
533IdentityFilter <&&> someOtherFilter
535-- "or"
536IdentityFilter <||> someOtherFilter
538-- Also there is a NotFilter, it only accepts a single filter, not a list.
540NotFilter someOtherFilter False
542```
544#### Identity Filter
546``` {.haskell}
548-- And'ing two Identity
549let queryFilter = IdentityFilter <&&> IdentityFilter
551let search = mkSearch Nothing (Just queryFilter)
553reply <- withBH' $ searchByType testIndex testMapping search
555```
557#### Boolean Filter
559Similar to boolean queries.
561``` {.haskell}
563-- Will return only items whose "user" field contains the term "bitemyapp"
564let queryFilter = BoolFilter (MustMatch (Term "user" "bitemyapp") False)
566-- Will return only items whose "user" field does not contain the term "bitemyapp"
567let queryFilter = BoolFilter (MustNotMatch (Term "user" "bitemyapp") False)
569-- The clause (query) should appear in the matching document.
570-- In a boolean query with no must clauses, one or more should
571-- clauses must match a document. The minimum number of should
572-- clauses to match can be set using the minimum_should_match parameter.
573let queryFilter = BoolFilter (ShouldMatch [(Term "user" "bitemyapp")] False)
575```
577#### Exists Filter
579``` {.haskell}
581-- Will filter for documents that have the field "user"
582let existsFilter = ExistsFilter (FieldName "user")
584```
586#### Geo BoundingBox Filter
588``` {.haskell}
590-- topLeft and bottomRight
591let box = GeoBoundingBox (LatLon 40.73 (-74.1)) (LatLon 40.10 (-71.12))
593let constraint = GeoBoundingBoxConstraint (FieldName "tweet.location") box False GeoFilterMemory
595```
597#### Geo Distance Filter
599``` {.haskell}
601let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
603-- coefficient and units
604let distance = Distance 10.0 Miles
606-- GeoFilterType or NoOptimizeBbox
607let optimizeBbox = OptimizeGeoFilterType GeoFilterMemory
609-- SloppyArc is the usual/default optimization in Elasticsearch today
610-- but pre-1.0 versions will need to pick Arc or Plane.
612let geoFilter = GeoDistanceFilter geoPoint distance SloppyArc optimizeBbox False
614```
616#### Geo Distance Range Filter
618Think of a donut and you won't be far off.
620``` {.haskell}
622let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
624let distanceRange = DistanceRange (Distance 0.0 Miles) (Distance 10.0 Miles)
626let geoFilter = GeoDistanceRangeFilter geoPoint distanceRange
628```
630#### Geo Polygon Filter
632``` {.haskell}
634-- I think I drew a square here.
635let points = [LatLon 40.0 (-70.00),
636 LatLon 40.0 (-72.00),
637 LatLon 41.0 (-70.00),
638 LatLon 41.0 (-72.00)]
640let geoFilter = GeoPolygonFilter (FieldName "tweet.location") points
642```
644#### Document IDs filter
646``` {.haskell}
648-- takes a mapping name and a list of DocIds
649IdsFilter (MappingName "tweet") [DocId "1"]
651```
653#### Range Filter
655``` {.haskell}
657-- RangeFilter :: FieldName
658-- -> RangeValue
659-- -> RangeExecution
660-- -> Cache -> Filter
662let filter = RangeFilter (FieldName "age")
663 (RangeGtLt (GreaterThan 1000.0) (LessThan 100000.0))
664 RangeExecutionIndex False
666```
668``` {.haskell}
670let filter = RangeFilter (FieldName "age")
671 (RangeLte (LessThanEq 100000.0))
672 RangeExecutionIndex False
674```
676##### Date Ranges
678Date ranges are expressed in UTCTime. Date ranges use the same range bound constructors as numerics, except that they end in "D".
680Note that compatibility with ES is tested only down to seconds.
682``` {.haskell}
684let filter = RangeFilter (FieldName "postDate")
685 (RangeDateGtLte
686 (GreaterThanD (UTCTime
687 (ModifiedJulianDay 55000)
688 (secondsToDiffTime 9)))
689 (LessThanEqD (UTCTime
690 (ModifiedJulianDay 55000)
691 (secondsToDiffTime 11))))
692 RangeExecutionIndex False
693```
695#### Regexp Filter
697``` {.haskell}
699-- RegexpFilter
700-- :: FieldName
701-- -> Regexp
702-- -> RegexpFlags
703-- -> CacheName
704-- -> Cache
705-- -> CacheKey
706-- -> Filter
707let filter = RegexpFilter (FieldName "user") (Regexp "bite.*app")
708 AllRegexpFlags (CacheName "test") False (CacheKey "key")
710-- n.b.
711-- data RegexpFlags = AllRegexpFlags
712-- | NoRegexpFlags
713-- | SomeRegexpFlags (NonEmpty RegexpFlag) deriving (Eq, Show)
715-- data RegexpFlag = AnyString
716-- | Automaton
717-- | Complement
718-- | Empty
719-- | Intersection
720-- | Interval deriving (Eq, Show)
722```
724### Aggregations
726#### Adding aggregations to search
728Aggregations can now be added to search queries, or made on their own.
730``` {.haskell}
731type Aggregations = M.Map Text Aggregation
732data Aggregation
733 = TermsAgg TermsAggregation
734 | DateHistogramAgg DateHistogramAggregation
735```
737For convenience, \`\`\`mkAggregations\`\`\` exists, that will create an \`\`\`Aggregations\`\`\` with the aggregation provided.
739For example:
741``` {.haskell}
742 let a = mkAggregations "users" $ TermsAgg $ mkTermsAggregation "user"
743 let search = mkAggregateSearch Nothing a
744```
746Aggregations can be added to an existing search, using the \`\`\`aggBody\`\`\` field
748``` {.haskell}
749 let search = mkSearch (Just (MatchAllQuery Nothing)) Nothing
750 let search' = search {aggBody = Just a}
751```
753Since the \`\`\`Aggregations\`\`\` structure is just a Map Text Aggregation, M.insert can be used to add additional aggregations.
755``` {.haskell}
756 let a' = M.insert "age" (TermsAgg $ mkTermsAggregation "age") a
757```
759#### Extracting aggregations from results
761Aggregations are part of the reply structure of every search, in the form of
763``` {.haskell}
764-- Lift decode and response body to be in the IO monad.
765let decode' = liftM decode
766let responseBody' = liftM responseBody
767let reply = withBH' $ searchByIndex testIndex search
768let response = decode' $ responseBody' reply :: IO (Maybe (SearchResult Tweet))
770-- Now that we have our response, we can extract our terms aggregation result -- which is a list of buckets.
772let terms = do { response' <- response; return $ response' >>= aggregations >>= toTerms "users" }
773terms
774Just (Bucket {buckets = [TermsResult {termKey = "bitemyapp", termsDocCount = 1, termsAggs = Nothing}]})
775```
777Note that bucket aggregation results, such as the TermsResult is a member of the type class :
779``` {.haskell}
780class BucketAggregation a where
781 key :: a -> Text
782 docCount :: a -> Int
783 aggs :: a -> Maybe AggregationResults
784```
786haskell
788You can use the function to get any nested results, if there were any. For example, if there were a nested terms aggregation keyed to "age" in a TermsResult named , you would call
790#### Terms Aggregation
792``` {.haskell}
793data TermsAggregation
794 = TermsAggregation {term :: Either Text Text,
795 termInclude :: Maybe TermInclusion,
796 termExclude :: Maybe TermInclusion,
797 termOrder :: Maybe TermOrder,
798 termMinDocCount :: Maybe Int,
799 termSize :: Maybe Int,
800 termShardSize :: Maybe Int,
801 termCollectMode :: Maybe CollectionMode,
802 termExecutionHint :: Maybe ExecutionHint,
803 termAggs :: Maybe Aggregations}
804```
806Term Aggregations have two factory functions, , and , and can be used as follows:
808``` {.haskell}
809let ta = TermsAgg $ mkTermsAggregation "user"
810```
812There are of course other options that can be added to a Terms Aggregation, such as the collection mode:
814``` {.haskell}
815let ta = mkTermsAggregation "user"
816let ta' = ta { termCollectMode = Just BreadthFirst }
817let ta'' = TermsAgg ta'
818```
820For more documentation on how the Terms Aggregation works, see <http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html>
822#### Date Histogram Aggregation
824``` {.haskell}
825data DateHistogramAggregation
826 = DateHistogramAggregation {dateField :: FieldName,
827 dateInterval :: Interval,
828 dateFormat :: Maybe Text,
829 datePreZone :: Maybe Text,
830 datePostZone :: Maybe Text,
831 datePreOffset :: Maybe Text,
832 datePostOffset :: Maybe Text,
833 dateAggs :: Maybe Aggregations}
834```
836haskell
838The Date Histogram Aggregation works much the same as the Terms Aggregation.
840Relevant functions include , and
842``` {.haskell}
843let dh = DateHistogramAgg (mkDateHistogram (FieldName "postDate") Minute)
844```
846Date histograms also accept a :
848``` {.haskell}
849FractionalInterval :: Float -> TimeInterval -> Interval
850-- TimeInterval is the following:
851data TimeInterval = Weeks | Days | Hours | Minutes | Seconds
852```
854It can be used as follows:
856``` {.haskell}
857let dh = DateHistogramAgg (mkDateHistogram (FieldName "postDate") (FractionalInterval 1.5 Minutes))
858```
860The is defined as:
862``` {.haskell}
863data DateHistogramResult
864 = DateHistogramResult {dateKey :: Int,
865 dateKeyStr :: Maybe Text,
866 dateDocCount :: Int,
867 dateHistogramAggs :: Maybe AggregationResults}
868```
870It is an instance of , and can have nested aggregations in each bucket.
872Buckets can be extracted from a using
874For more information on the Date Histogram Aggregation, see: <http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-aggregations-bucket-datehistogram-aggregation.html>
877Contributors
878============
880* [Chris Allen](https://github.com/bitemyapp)
881* [Liam Atkinson](https://github.com/latkins)
882* [Christopher Guiney](https://github.com/chrisguiney)
883* [Curtis Carter](https://github.com/ccarter)
884* [Michael Xavier](https://github.com/MichaelXavier)
885* [Bob Long](https://github.com/bobjflong)
886* [Maximilian Tagher](https://github.com/MaxGabriel)
887* [Anna Kopp](https://github.com/annakopp)
888* [Matvey B. Aksenov](https://github.com/supki)
889* [Jan-Philip Loos](https://github.com/MaxDaten)
891Possible future functionality
892=============================
894Span Queries
895------------
897Beginning here: <http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-span-first-query.html>
899Function Score Query
900--------------------
902<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html>
904Node discovery and failover
905---------------------------
907Might require TCP support.
909Support for TCP access to Elasticsearch
910---------------------------------------
912Pretend to be a transport client?
914Bulk cluster-join merge
915-----------------------
917Might require making a lucene index on disk with the appropriate format.
919GeoShapeQuery
920-------------
922<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-geo-shape-query.html>
924GeoShapeFilter
925--------------
927<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-geo-shape-filter.html>
929Geohash cell filter
930-------------------
932<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-geohash-cell-filter.html>
934HasChild Filter
935---------------
937<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-has-child-filter.html>
939HasParent Filter
940----------------
942<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-has-parent-filter.html>
944Indices Filter
945--------------
947<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-indices-filter.html>
949Query Filter
950------------
952<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-query-filter.html>
954Script based sorting
955--------------------
957<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-sort.html#_script_based_sorting>
959Collapsing redundantly nested and/or structures
960-----------------------------------------------
962The Seminearring instance, if deeply nested can possibly produce nested structure that is redundant. Depending on how this affects ES performance, reducing this structure might be valuable.
964Runtime checking for cycles in data structures
965----------------------------------------------
967check for n \> 1 occurrences in DFS:
969<http://hackage.haskell.org/package/stable-maps-0.0.5/docs/System-Mem-StableName-Dynamic.html>
971<http://hackage.haskell.org/package/stable-maps-0.0.5/docs/System-Mem-StableName-Dynamic-Map.html>
973Photo Origin
974============
976Photo from HA! Designs: <https://www.flickr.com/photos/hadesigns/>