git.iko.soy/typeable/bloodhound
on 65d5610 help sign in
963 lines 28 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 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 , _version = 2
280 , found = Just True
281 , _source = Tweet {user = "bitemyapp"
282 , postDate = 2009-06-18 00:00:10 UTC
283 , message = "Use haskell!"
284 , age = 10000
285 , location = Location {lat = 40.12, lon = -71.34}}})
287-- _source in EsResult is parametric, we dispatch the type by passing in what we expect (Tweet) as a parameter to EsResult.
289-- use the _source record accessor to get at your document
290fmap _source eitherResult
291Right (Tweet {user = "bitemyapp"
292 , postDate = 2009-06-18 00:00:10 UTC
293 , message = "Use haskell!"
294 , age = 10000
295 , location = Location {lat = 40.12, lon = -71.34}})
297```
299Bulk Operations
300---------------
302### Bulk create, index
304``` {.haskell}
306-- don't forget the imports and derive generic setting for ghci
307-- at the beginning of the examples.
309:{
310-- Using the earlier Tweet datatype and exampleTweet data
312-- just changing up the data a bit.
313let bulkTest = exampleTweet { user = "blah" }
314let bulkTestTwo = exampleTweet { message = "woohoo!" }
316-- create only bulk operation
317-- BulkCreate :: IndexName -> MappingName -> DocId -> Value -> BulkOperation
318let firstOp = BulkCreate testIndex
319 testMapping (DocId "3") (toJSON bulkTest)
321-- index operation "create or update"
322let sndOp = BulkIndex testIndex
323 testMapping (DocId "4") (toJSON bulkTestTwo)
325-- Some explanation, the final "Value" type that BulkIndex,
326-- BulkCreate, and BulkUpdate accept is the actual document
327-- data that your operation applies to. BulkDelete doesn't
328-- take a value because it's just deleting whatever DocId
329-- you pass.
331-- list of bulk operations
332let stream = [firstDoc, secondDoc]
334-- Fire off the actual bulk request
335-- bulk :: Vector BulkOperation -> IO Reply
336resp <- withBH' $ bulk stream
337:}
339```
341### Encoding individual bulk API operations
343``` {.haskell}
344-- the following functions are exported in Bloodhound so
345-- you can build up bulk operations yourself
346encodeBulkOperations :: V.Vector BulkOperation -> L.ByteString
347encodeBulkOperation :: BulkOperation -> L.ByteString
349-- How to use the above:
350data BulkTest = BulkTest { name :: Text } deriving (Eq, Generic, Show)
351instance FromJSON BulkTest
352instance ToJSON BulkTest
354_ <- insertData
355let firstTest = BulkTest "blah"
356let secondTest = BulkTest "bloo"
357let firstDoc = BulkIndex testIndex
358 testMapping (DocId "2") (toJSON firstTest)
359let secondDoc = BulkCreate testIndex
360 testMapping (DocId "3") (toJSON secondTest)
361let stream = V.fromList [firstDoc, secondDoc] :: V.Vector BulkOperation
363-- to encode yourself
364let firstDocEncoded = encode firstDoc :: L.ByteString
366-- to encode a vector of bulk operations
367let encodedOperations = encodeBulkOperations stream
369-- to insert into a particular server
370-- bulk :: V.Vector BulkOperation -> IO Reply
371_ <- withBH' $ bulk stream
373```
375Search
376------
378### Querying
380#### Term Query
382``` {.haskell}
384-- exported by the Client module, just defaults some stuff.
385-- mkSearch :: Maybe Query -> Maybe Filter -> Search
386-- mkSearch query filter = Search query filter Nothing False (From 0) (Size 10)
388let query = TermQuery (Term "user" "bitemyapp") Nothing
390-- AND'ing identity filter with itself and then tacking it onto a query
391-- search should be a null-operation. I include it for the sake of example.
392-- <||> (or/plus) should make it into a search that returns everything.
394let filter = IdentityFilter <&&> IdentityFilter
396-- constructing the search object the searchByIndex function dispatches on.
397let search = mkSearch (Just query) (Just filter)
399-- you can also searchByType and specify the mapping name.
400reply <- withBH' $ searchByIndex testIndex search
402let result = eitherDecode (responseBody reply) :: Either String (SearchResult Tweet)
404λ> fmap (hits . searchHits) result
405Right [Hit {hitIndex = IndexName "twitter"
406 , hitType = MappingName "tweet"
407 , hitDocId = DocId "1"
408 , hitScore = 0.30685282
409 , hitSource = Tweet {user = "bitemyapp"
410 , postDate = 2009-06-18 00:00:10 UTC
411 , message = "Use haskell!"
412 , age = 10000
413 , location = Location {lat = 40.12, lon = -71.34}}}]
415```
417#### Match Query
419``` {.haskell}
421let query = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
422let search = mkSearch (Just query) Nothing
424```
426#### Multi-Match Query
428``` {.haskell}
430let fields = [FieldName "user", FieldName "message"]
431let query = QueryMultiMatchQuery $ mkMultiMatchQuery fields (QueryString "bitemyapp")
432let search = mkSearch (Just query) Nothing
434```
436#### Bool Query
438``` {.haskell}
440let innerQuery = QueryMatchQuery $
441 mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
442let query = QueryBoolQuery $
443 mkBoolQuery [innerQuery] [] []
444let search = mkSearch (Just query) Nothing
446```
448#### Boosting Query
450``` {.haskell}
452let posQuery = QueryMatchQuery $
453 mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
454let negQuery = QueryMatchQuery $
455 mkMatchQuery (FieldName "user") (QueryString "notmyapp")
456let query = QueryBoostingQuery $
457 BoostingQuery posQuery negQuery (Boost 0.2)
459```
461#### Rest of the query/filter types
463Just follow the pattern you've seen here and check the Hackage API documentation.
465### Sorting
467``` {.haskell}
469let sortSpec = DefaultSortSpec $ mkSort (FieldName "age") Ascending
471-- mkSort is a shortcut function that takes a FieldName and a SortOrder
472-- to generate a vanilla DefaultSort.
473-- checkt the DefaultSort type for the full list of customizable options.
475-- From and size are integers for pagination.
477-- When sorting on a field, scores are not computed. By setting TrackSortScores to true, scores will still be computed and tracked.
479-- type Sort = [SortSpec]
480-- type TrackSortScores = Bool
481-- type From = Int
482-- type Size = Int
484-- Search takes Maybe Query
485-- -> Maybe Filter
486-- -> Maybe Sort
487-- -> TrackSortScores
488-- -> From -> Size
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)
493```
495### Filtering
497#### And, Not, and Or filters
499Filters form a monoid and seminearring.
501``` {.haskell}
503instance Monoid Filter where
504 mempty = IdentityFilter
505 mappend a b = AndFilter [a, b] defaultCache
507instance Seminearring Filter where
508 a <||> b = OrFilter [a, b] defaultCache
510-- AndFilter and OrFilter take [Filter] as an argument.
512-- This will return anything, because IdentityFilter returns everything
513OrFilter [IdentityFilter, someOtherFilter] False
515-- This will return exactly what someOtherFilter returns
516AndFilter [IdentityFilter, someOtherFilter] False
518-- Thanks to the seminearring and monoid, the above can be expressed as:
520-- "and"
521IdentityFilter <&&> someOtherFilter
523-- "or"
524IdentityFilter <||> someOtherFilter
526-- Also there is a NotFilter, it only accepts a single filter, not a list.
528NotFilter someOtherFilter False
530```
532#### Identity Filter
534``` {.haskell}
536-- And'ing two Identity
537let queryFilter = IdentityFilter <&&> IdentityFilter
539let search = mkSearch Nothing (Just queryFilter)
541reply <- withBH' $ searchByType testIndex testMapping search
543```
545#### Boolean Filter
547Similar to boolean queries.
549``` {.haskell}
551-- Will return only items whose "user" field contains the term "bitemyapp"
552let queryFilter = BoolFilter (MustMatch (Term "user" "bitemyapp") False)
554-- Will return only items whose "user" field does not contain the term "bitemyapp"
555let queryFilter = BoolFilter (MustNotMatch (Term "user" "bitemyapp") False)
557-- The clause (query) should appear in the matching document.
558-- In a boolean query with no must clauses, one or more should
559-- clauses must match a document. The minimum number of should
560-- clauses to match can be set using the minimum_should_match parameter.
561let queryFilter = BoolFilter (ShouldMatch [(Term "user" "bitemyapp")] False)
563```
565#### Exists Filter
567``` {.haskell}
569-- Will filter for documents that have the field "user"
570let existsFilter = ExistsFilter (FieldName "user")
572```
574#### Geo BoundingBox Filter
576``` {.haskell}
578-- topLeft and bottomRight
579let box = GeoBoundingBox (LatLon 40.73 (-74.1)) (LatLon 40.10 (-71.12))
581let constraint = GeoBoundingBoxConstraint (FieldName "tweet.location") box False GeoFilterMemory
583```
585#### Geo Distance Filter
587``` {.haskell}
589let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
591-- coefficient and units
592let distance = Distance 10.0 Miles
594-- GeoFilterType or NoOptimizeBbox
595let optimizeBbox = OptimizeGeoFilterType GeoFilterMemory
597-- SloppyArc is the usual/default optimization in Elasticsearch today
598-- but pre-1.0 versions will need to pick Arc or Plane.
600let geoFilter = GeoDistanceFilter geoPoint distance SloppyArc optimizeBbox False
602```
604#### Geo Distance Range Filter
606Think of a donut and you won't be far off.
608``` {.haskell}
610let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
612let distanceRange = DistanceRange (Distance 0.0 Miles) (Distance 10.0 Miles)
614let geoFilter = GeoDistanceRangeFilter geoPoint distanceRange
616```
618#### Geo Polygon Filter
620``` {.haskell}
622-- I think I drew a square here.
623let points = [LatLon 40.0 (-70.00),
624 LatLon 40.0 (-72.00),
625 LatLon 41.0 (-70.00),
626 LatLon 41.0 (-72.00)]
628let geoFilter = GeoPolygonFilter (FieldName "tweet.location") points
630```
632#### Document IDs filter
634``` {.haskell}
636-- takes a mapping name and a list of DocIds
637IdsFilter (MappingName "tweet") [DocId "1"]
639```
641#### Range Filter
643``` {.haskell}
645-- RangeFilter :: FieldName
646-- -> RangeValue
647-- -> RangeExecution
648-- -> Cache -> Filter
650let filter = RangeFilter (FieldName "age")
651 (RangeGtLt (GreaterThan 1000.0) (LessThan 100000.0))
652 RangeExecutionIndex False
654```
656``` {.haskell}
658let filter = RangeFilter (FieldName "age")
659 (RangeLte (LessThanEq 100000.0))
660 RangeExecutionIndex False
662```
664##### Date Ranges
666Date ranges are expressed in UTCTime. Date ranges use the same range bound constructors as numerics, except that they end in "D".
668Note that compatibility with ES is tested only down to seconds.
670``` {.haskell}
672let filter = RangeFilter (FieldName "postDate")
673 (RangeDateGtLte
674 (GreaterThanD (UTCTime
675 (ModifiedJulianDay 55000)
676 (secondsToDiffTime 9)))
677 (LessThanEqD (UTCTime
678 (ModifiedJulianDay 55000)
679 (secondsToDiffTime 11))))
680 RangeExecutionIndex False
681```
683#### Regexp Filter
685``` {.haskell}
687-- RegexpFilter
688-- :: FieldName
689-- -> Regexp
690-- -> RegexpFlags
691-- -> CacheName
692-- -> Cache
693-- -> CacheKey
694-- -> Filter
695let filter = RegexpFilter (FieldName "user") (Regexp "bite.*app")
696 AllRegexpFlags (CacheName "test") False (CacheKey "key")
698-- n.b.
699-- data RegexpFlags = AllRegexpFlags
700-- | NoRegexpFlags
701-- | SomeRegexpFlags (NonEmpty RegexpFlag) deriving (Eq, Show)
703-- data RegexpFlag = AnyString
704-- | Automaton
705-- | Complement
706-- | Empty
707-- | Intersection
708-- | Interval deriving (Eq, Show)
710```
712### Aggregations
714#### Adding aggregations to search
716Aggregations can now be added to search queries, or made on their own.
718``` {.haskell}
719type Aggregations = M.Map Text Aggregation
720data Aggregation
721 = TermsAgg TermsAggregation
722 | DateHistogramAgg DateHistogramAggregation
723```
725For convenience, \`\`\`mkAggregations\`\`\` exists, that will create an \`\`\`Aggregations\`\`\` with the aggregation provided.
727For example:
729``` {.haskell}
730 let a = mkAggregations "users" $ TermsAgg $ mkTermsAggregation "user"
731 let search = mkAggregateSearch Nothing a
732```
734Aggregations can be added to an existing search, using the \`\`\`aggBody\`\`\` field
736``` {.haskell}
737 let search = mkSearch (Just (MatchAllQuery Nothing)) Nothing
738 let search' = search {aggBody = Just a}
739```
741Since the \`\`\`Aggregations\`\`\` structure is just a Map Text Aggregation, M.insert can be used to add additional aggregations.
743``` {.haskell}
744 let a' = M.insert "age" (TermsAgg $ mkTermsAggregation "age") a
745```
747#### Extracting aggregations from results
749Aggregations are part of the reply structure of every search, in the form of
751``` {.haskell}
752-- Lift decode and response body to be in the IO monad.
753let decode' = liftM decode
754let responseBody' = liftM responseBody
755let reply = withBH' $ searchByIndex testIndex search
756let response = decode' $ responseBody' reply :: IO (Maybe (SearchResult Tweet))
758-- Now that we have our response, we can extract our terms aggregation result -- which is a list of buckets.
760let terms = do { response' <- response; return $ response' >>= aggregations >>= toTerms "users" }
761terms
762Just (Bucket {buckets = [TermsResult {termKey = "bitemyapp", termsDocCount = 1, termsAggs = Nothing}]})
763```
765Note that bucket aggregation results, such as the TermsResult is a member of the type class :
767``` {.haskell}
768class BucketAggregation a where
769 key :: a -> Text
770 docCount :: a -> Int
771 aggs :: a -> Maybe AggregationResults
772```
774haskell
776You 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
778#### Terms Aggregation
780``` {.haskell}
781data TermsAggregation
782 = TermsAggregation {term :: Either Text Text,
783 termInclude :: Maybe TermInclusion,
784 termExclude :: Maybe TermInclusion,
785 termOrder :: Maybe TermOrder,
786 termMinDocCount :: Maybe Int,
787 termSize :: Maybe Int,
788 termShardSize :: Maybe Int,
789 termCollectMode :: Maybe CollectionMode,
790 termExecutionHint :: Maybe ExecutionHint,
791 termAggs :: Maybe Aggregations}
792```
794Term Aggregations have two factory functions, , and , and can be used as follows:
796``` {.haskell}
797let ta = TermsAgg $ mkTermsAggregation "user"
798```
800There are of course other options that can be added to a Terms Aggregation, such as the collection mode:
802``` {.haskell}
803let ta = mkTermsAggregation "user"
804let ta' = ta { termCollectMode = Just BreadthFirst }
805let ta'' = TermsAgg ta'
806```
808For more documentation on how the Terms Aggregation works, see <http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html>
810#### Date Histogram Aggregation
812``` {.haskell}
813data DateHistogramAggregation
814 = DateHistogramAggregation {dateField :: FieldName,
815 dateInterval :: Interval,
816 dateFormat :: Maybe Text,
817 datePreZone :: Maybe Text,
818 datePostZone :: Maybe Text,
819 datePreOffset :: Maybe Text,
820 datePostOffset :: Maybe Text,
821 dateAggs :: Maybe Aggregations}
822```
824haskell
826The Date Histogram Aggregation works much the same as the Terms Aggregation.
828Relevant functions include , and
830``` {.haskell}
831let dh = DateHistogramAgg (mkDateHistogram (FieldName "postDate") Minute)
832```
834Date histograms also accept a :
836``` {.haskell}
837FractionalInterval :: Float -> TimeInterval -> Interval
838-- TimeInterval is the following:
839data TimeInterval = Weeks | Days | Hours | Minutes | Seconds
840```
842It can be used as follows:
844``` {.haskell}
845let dh = DateHistogramAgg (mkDateHistogram (FieldName "postDate") (FractionalInterval 1.5 Minutes))
846```
848The is defined as:
850``` {.haskell}
851data DateHistogramResult
852 = DateHistogramResult {dateKey :: Int,
853 dateKeyStr :: Maybe Text,
854 dateDocCount :: Int,
855 dateHistogramAggs :: Maybe AggregationResults}
856```
858It is an instance of , and can have nested aggregations in each bucket.
860Buckets can be extracted from a using
862For more information on the Date Histogram Aggregation, see: <http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-aggregations-bucket-datehistogram-aggregation.html>
865Contributors
866============
868* [Chris Allen](https://github.com/bitemyapp)
869* [Liam Atkinson](https://github.com/latkins)
870* [Christopher Guiney](https://github.com/chrisguiney)
871* [Curtis Carter](https://github.com/ccarter)
872* [Michael Xavier](https://github.com/MichaelXavier)
873* [Bob Long](https://github.com/bobjflong)
874* [Maximilian Tagher](https://github.com/MaxGabriel)
875* [Anna Kopp](https://github.com/annakopp)
876* [Matvey B. Aksenov](https://github.com/supki)
878Possible future functionality
879=============================
881Span Queries
882------------
884Beginning here: <http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-span-first-query.html>
886Function Score Query
887--------------------
889<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html>
891Node discovery and failover
892---------------------------
894Might require TCP support.
896Support for TCP access to Elasticsearch
897---------------------------------------
899Pretend to be a transport client?
901Bulk cluster-join merge
902-----------------------
904Might require making a lucene index on disk with the appropriate format.
906GeoShapeQuery
907-------------
909<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-geo-shape-query.html>
911GeoShapeFilter
912--------------
914<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-geo-shape-filter.html>
916Geohash cell filter
917-------------------
919<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-geohash-cell-filter.html>
921HasChild Filter
922---------------
924<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-has-child-filter.html>
926HasParent Filter
927----------------
929<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-has-parent-filter.html>
931Indices Filter
932--------------
934<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-indices-filter.html>
936Query Filter
937------------
939<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-query-filter.html>
941Script based sorting
942--------------------
944<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-sort.html#_script_based_sorting>
946Collapsing redundantly nested and/or structures
947-----------------------------------------------
949The 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.
951Runtime checking for cycles in data structures
952----------------------------------------------
954check for n \> 1 occurrences in DFS:
956<http://hackage.haskell.org/package/stable-maps-0.0.5/docs/System-Mem-StableName-Dynamic.html>
958<http://hackage.haskell.org/package/stable-maps-0.0.5/docs/System-Mem-StableName-Dynamic-Map.html>
960Photo Origin
961============
963Photo from HA! Designs: <https://www.flickr.com/photos/hadesigns/>