git.iko.soy/typeable/bloodhound
on 58ec8ad help sign in
945 lines 27 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 Database.Bloodhound
61import Data.Aeson
62import Data.Either (Either(..))
63import Data.Maybe (fromJust)
64import Data.Time.Calendar (Day(..))
65import Data.Time.Clock (secondsToDiffTime, UTCTime(..))
66import Data.Text (Text)
67import GHC.Generics (Generic)
68import Network.HTTP.Client
69import qualified Network.HTTP.Types.Status as NHTS
71-- no trailing slashes in servers, library handles building the path.
72let testServer = (Server "http://localhost:9200")
73let testIndex = IndexName "twitter"
74let testMapping = MappingName "tweet"
76-- defaultIndexSettings is exported by Database.Bloodhound as well
77let defaultIndexSettings = IndexSettings (ShardCount 3) (ReplicaCount 2)
79-- createIndex returns IO Reply
81-- response :: Reply, Reply is a synonym for Network.HTTP.Conduit.Response
82response <- createIndex testServer defaultIndexSettings testIndex
83:}
85```
87### Delete Index
89#### Code
91``` {.haskell}
93-- response :: Reply
94response <- deleteIndex testServer testIndex
96```
98#### Example Response
100``` {.haskell}
102-- print response if it was a success
103Response {responseStatus = Status {statusCode = 200, statusMessage = "OK"}
104 , responseVersion = HTTP/1.1
105 , responseHeaders = [("Content-Type", "application/json; charset=UTF-8")
106 , ("Content-Length", "21")]
107 , responseBody = "{\"acknowledged\":true}"
108 , responseCookieJar = CJ {expose = []}
109 , responseClose' = ResponseClose}
111-- if the index to be deleted didn't exist anyway
112Response {responseStatus = Status {statusCode = 404, statusMessage = "Not Found"}
113 , responseVersion = HTTP/1.1
114 , responseHeaders = [("Content-Type", "application/json; charset=UTF-8")
115 , ("Content-Length","65")]
116 , responseBody = "{\"error\":\"IndexMissingException[[twitter] missing]\",\"status\":404}"
117 , responseCookieJar = CJ {expose = []}
118 , responseClose' = ResponseClose}
120```
122### Refresh Index
124#### Note, you **have** to do this if you expect to read what you just wrote
126``` {.haskell}
128resp <- refreshIndex testServer testIndex
130```
132#### Example Response
134``` {.haskell}
136-- print resp on success
137Response {responseStatus = Status {statusCode = 200, statusMessage = "OK"}
138 , responseVersion = HTTP/1.1
139 , responseHeaders = [("Content-Type", "application/json; charset=UTF-8")
140 , ("Content-Length","50")]
141 , responseBody = "{\"_shards\":{\"total\":10,\"successful\":5,\"failed\":0}}"
142 , responseCookieJar = CJ {expose = []}
143 , responseClose' = ResponseClose}
145```
147Mapping Operations
148------------------
150### Create Mapping
152``` {.haskell}
154-- don't forget imports and the like at the top.
156data TweetMapping = TweetMapping deriving (Eq, Show)
158-- I know writing the JSON manually sucks.
159-- I don't have a proper data type for Mappings yet.
160-- Let me know if this is something you need.
162:{
163instance ToJSON TweetMapping where
164 toJSON TweetMapping =
165 object ["tweet" .=
166 object ["properties" .=
167 object ["location" .=
168 object ["type" .= ("geo_point" :: Text)]]]]
169:}
171resp <- putMapping testServer testIndex testMapping TweetMapping
173```
175### Delete Mapping
177``` {.haskell}
179resp <- deleteMapping testServer testIndex testMapping
181```
183Document Operations
184-------------------
186### Indexing Documents
188``` {.haskell}
190-- don't forget the imports and derive generic setting for ghci
191-- at the beginning of the examples.
193:{
194data Location = Location { lat :: Double
195 , lon :: Double } deriving (Eq, Generic, Show)
197data Tweet = Tweet { user :: Text
198 , postDate :: UTCTime
199 , message :: Text
200 , age :: Int
201 , location :: Location } deriving (Eq, Generic, Show)
203exampleTweet = Tweet { user = "bitemyapp"
204 , postDate = UTCTime
205 (ModifiedJulianDay 55000)
206 (secondsToDiffTime 10)
207 , message = "Use haskell!"
208 , age = 10000
209 , location = Location 40.12 (-71.34) }
211-- automagic (generic) derivation of instances because we're lazy.
212instance ToJSON Tweet
213instance FromJSON Tweet
214instance ToJSON Location
215instance FromJSON Location
216:}
218-- Should be able to toJSON and encode the data structures like this:
219-- λ> toJSON $ Location 10.0 10.0
220-- Object fromList [("lat",Number 10.0),("lon",Number 10.0)]
221-- λ> encode $ Location 10.0 10.0
222-- "{\"lat\":10,\"lon\":10}"
224resp <- indexDocument testServer testIndex testMapping exampleTweet (DocId "1")
226```
228#### Example Response
230``` {.haskell}
232Response {responseStatus =
233 Status {statusCode = 200, statusMessage = "OK"}
234 , responseVersion = HTTP/1.1, responseHeaders =
235 [("Content-Type","application/json; charset=UTF-8"),
236 ("Content-Length","75")]
237 , responseBody = "{\"_index\":\"twitter\",\"_type\":\"tweet\",\"_id\":\"1\",\"_version\":2,\"created\":false}"
238 , responseCookieJar = CJ {expose = []}, responseClose' = ResponseClose}
240```
242### Deleting Documents
244``` {.haskell}
246resp <- deleteDocument testServer testIndex testMapping (DocId "1")
248```
250### Getting Documents
252``` {.haskell}
254-- n.b., you'll need the earlier imports. responseBody is from http-conduit
256resp <- getDocument testServer testIndex testMapping (DocId "1")
258-- responseBody :: Response body -> body
259let body = responseBody resp
261-- you have two options, you use decode and just get Maybe (EsResult Tweet)
262-- or you can use eitherDecode and get Either String (EsResult Tweet)
264let maybeResult = decode body :: Maybe (EsResult Tweet)
265-- the explicit typing is so Aeson knows how to parse the JSON.
267-- use either if you want to know why something failed to parse.
268-- (string errors, sadly)
269let eitherResult = eitherDecode body :: Either String (EsResult Tweet)
271-- print eitherResult should look like:
272Right (EsResult {_index = "twitter"
273 , _type = "tweet"
274 , _id = "1"
275 , _version = 2
276 , found = Just True
277 , _source = Tweet {user = "bitemyapp"
278 , postDate = 2009-06-18 00:00:10 UTC
279 , message = "Use haskell!"
280 , age = 10000
281 , location = Location {lat = 40.12, lon = -71.34}}})
283-- _source in EsResult is parametric, we dispatch the type by passing in what we expect (Tweet) as a parameter to EsResult.
285-- use the _source record accessor to get at your document
286fmap _source eitherResult
287Right (Tweet {user = "bitemyapp"
288 , postDate = 2009-06-18 00:00:10 UTC
289 , message = "Use haskell!"
290 , age = 10000
291 , location = Location {lat = 40.12, lon = -71.34}})
293```
295Bulk Operations
296---------------
298### Bulk create, index
300``` {.haskell}
302-- don't forget the imports and derive generic setting for ghci
303-- at the beginning of the examples.
305:{
306-- Using the earlier Tweet datatype and exampleTweet data
308-- just changing up the data a bit.
309let bulkTest = exampleTweet { user = "blah" }
310let bulkTestTwo = exampleTweet { message = "woohoo!" }
312-- create only bulk operation
313-- BulkCreate :: IndexName -> MappingName -> DocId -> Value -> BulkOperation
314let firstOp = BulkCreate testIndex
315 testMapping (DocId "3") (toJSON bulkTest)
317-- index operation "create or update"
318let sndOp = BulkIndex testIndex
319 testMapping (DocId "4") (toJSON bulkTestTwo)
321-- Some explanation, the final "Value" type that BulkIndex,
322-- BulkCreate, and BulkUpdate accept is the actual document
323-- data that your operation applies to. BulkDelete doesn't
324-- take a value because it's just deleting whatever DocId
325-- you pass.
327-- list of bulk operations
328let stream = [firstDoc, secondDoc]
330-- Fire off the actual bulk request
331-- bulk :: Server -> [BulkOperation] -> IO Reply
332resp <- bulk testServer stream
333:}
335```
337### Encoding individual bulk API operations
339``` {.haskell}
340-- the following functions are exported in Bloodhound so
341-- you can build up bulk operations yourself
342encodeBulkOperations :: V.Vector BulkOperation -> L.ByteString
343encodeBulkOperation :: BulkOperation -> L.ByteString
345-- How to use the above:
346data BulkTest = BulkTest { name :: Text } deriving (Eq, Generic, Show)
347instance FromJSON BulkTest
348instance ToJSON BulkTest
350_ <- insertData
351let firstTest = BulkTest "blah"
352let secondTest = BulkTest "bloo"
353let firstDoc = BulkIndex testIndex
354 testMapping (DocId "2") (toJSON firstTest)
355let secondDoc = BulkCreate testIndex
356 testMapping (DocId "3") (toJSON secondTest)
357let stream = V.fromList [firstDoc, secondDoc] :: V.Vector BulkOperation
359-- to encode yourself
360let firstDocEncoded = encode firstDoc :: L.ByteString
362-- to encode a vector of bulk operations
363let encodedOperations = encodeBulkOperations stream
365-- to insert into a particular server
366-- bulk :: Server -> V.Vector BulkOperation -> IO Reply
367_ <- bulk testServer stream
369```
371Search
372------
374### Querying
376#### Term Query
378``` {.haskell}
380-- exported by the Client module, just defaults some stuff.
381-- mkSearch :: Maybe Query -> Maybe Filter -> Search
382-- mkSearch query filter = Search query filter Nothing False 0 10
384let query = TermQuery (Term "user" "bitemyapp") Nothing
386-- AND'ing identity filter with itself and then tacking it onto a query
387-- search should be a null-operation. I include it for the sake of example.
388-- <||> (or/plus) should make it into a search that returns everything.
390let filter = IdentityFilter <&&> IdentityFilter
392-- constructing the search object the searchByIndex function dispatches on.
393let search = mkSearch (Just query) (Just filter)
395-- you can also searchByType and specify the mapping name.
396reply <- searchByIndex testServer testIndex search
398let result = eitherDecode (responseBody reply) :: Either String (SearchResult Tweet)
400λ> fmap (hits . searchHits) result
401Right [Hit {hitIndex = IndexName "twitter"
402 , hitType = MappingName "tweet"
403 , hitDocId = DocId "1"
404 , hitScore = 0.30685282
405 , hitSource = Tweet {user = "bitemyapp"
406 , postDate = 2009-06-18 00:00:10 UTC
407 , message = "Use haskell!"
408 , age = 10000
409 , location = Location {lat = 40.12, lon = -71.34}}}]
411```
413#### Match Query
415``` {.haskell}
417let query = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
418let search = mkSearch (Just query) Nothing
420```
422#### Multi-Match Query
424``` {.haskell}
426let fields = [FieldName "user", FieldName "message"]
427let query = QueryMultiMatchQuery $ mkMultiMatchQuery fields (QueryString "bitemyapp")
428let search = mkSearch (Just query) Nothing
430```
432#### Bool Query
434``` {.haskell}
436let innerQuery = QueryMatchQuery $
437 mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
438let query = QueryBoolQuery $
439 mkBoolQuery [innerQuery] [] []
440let search = mkSearch (Just query) Nothing
442```
444#### Boosting Query
446``` {.haskell}
448let posQuery = QueryMatchQuery $
449 mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
450let negQuery = QueryMatchQuery $
451 mkMatchQuery (FieldName "user") (QueryString "notmyapp")
452let query = QueryBoostingQuery $
453 BoostingQuery posQuery negQuery (Boost 0.2)
455```
457#### Rest of the query/filter types
459Just follow the pattern you've seen here and check the Hackage API documentation.
461### Sorting
463``` {.haskell}
465let sortSpec = DefaultSortSpec $ mkSort (FieldName "age") Ascending
467-- mkSort is a shortcut function that takes a FieldName and a SortOrder
468-- to generate a vanilla DefaultSort.
469-- checkt the DefaultSort type for the full list of customizable options.
471-- From and size are integers for pagination.
473-- When sorting on a field, scores are not computed. By setting TrackSortScores to true, scores will still be computed and tracked.
475-- type Sort = [SortSpec]
476-- type TrackSortScores = Bool
477-- type From = Int
478-- type Size = Int
480-- Search takes Maybe Query
481-- -> Maybe Filter
482-- -> Maybe Sort
483-- -> TrackSortScores
484-- -> From -> Size
486-- just add more sortspecs to the list if you want tie-breakers.
487let search = Search Nothing (Just IdentityFilter) (Just [sortSpec]) False 0 10
489```
491### Filtering
493#### And, Not, and Or filters
495Filters form a monoid and seminearring.
497``` {.haskell}
499instance Monoid Filter where
500 mempty = IdentityFilter
501 mappend a b = AndFilter [a, b] defaultCache
503instance Seminearring Filter where
504 a <||> b = OrFilter [a, b] defaultCache
506-- AndFilter and OrFilter take [Filter] as an argument.
508-- This will return anything, because IdentityFilter returns everything
509OrFilter [IdentityFilter, someOtherFilter] False
511-- This will return exactly what someOtherFilter returns
512AndFilter [IdentityFilter, someOtherFilter] False
514-- Thanks to the seminearring and monoid, the above can be expressed as:
516-- "and"
517IdentityFilter <&&> someOtherFilter
519-- "or"
520IdentityFilter <||> someOtherFilter
522-- Also there is a NotFilter, it only accepts a single filter, not a list.
524NotFilter someOtherFilter False
526```
528#### Identity Filter
530``` {.haskell}
532-- And'ing two Identity
533let queryFilter = IdentityFilter <&&> IdentityFilter
535let search = mkSearch Nothing (Just queryFilter)
537reply <- searchByType testServer testIndex testMapping search
539```
541#### Boolean Filter
543Similar to boolean queries.
545``` {.haskell}
547-- Will return only items whose "user" field contains the term "bitemyapp"
548let queryFilter = BoolFilter (MustMatch (Term "user" "bitemyapp") False)
550-- Will return only items whose "user" field does not contain the term "bitemyapp"
551let queryFilter = BoolFilter (MustNotMatch (Term "user" "bitemyapp") False)
553-- The clause (query) should appear in the matching document.
554-- In a boolean query with no must clauses, one or more should
555-- clauses must match a document. The minimum number of should
556-- clauses to match can be set using the minimum_should_match parameter.
557let queryFilter = BoolFilter (ShouldMatch [(Term "user" "bitemyapp")] False)
559```
561#### Exists Filter
563``` {.haskell}
565-- Will filter for documents that have the field "user"
566let existsFilter = ExistsFilter (FieldName "user")
568```
570#### Geo BoundingBox Filter
572``` {.haskell}
574-- topLeft and bottomRight
575let box = GeoBoundingBox (LatLon 40.73 (-74.1)) (LatLon 40.10 (-71.12))
577let constraint = GeoBoundingBoxConstraint (FieldName "tweet.location") box False GeoFilterMemory
579```
581#### Geo Distance Filter
583``` {.haskell}
585let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
587-- coefficient and units
588let distance = Distance 10.0 Miles
590-- GeoFilterType or NoOptimizeBbox
591let optimizeBbox = OptimizeGeoFilterType GeoFilterMemory
593-- SloppyArc is the usual/default optimization in Elasticsearch today
594-- but pre-1.0 versions will need to pick Arc or Plane.
596let geoFilter = GeoDistanceFilter geoPoint distance SloppyArc optimizeBbox False
598```
600#### Geo Distance Range Filter
602Think of a donut and you won't be far off.
604``` {.haskell}
606let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
608let distanceRange = DistanceRange (Distance 0.0 Miles) (Distance 10.0 Miles)
610let geoFilter = GeoDistanceRangeFilter geoPoint distanceRange
612```
614#### Geo Polygon Filter
616``` {.haskell}
618-- I think I drew a square here.
619let points = [LatLon 40.0 (-70.00),
620 LatLon 40.0 (-72.00),
621 LatLon 41.0 (-70.00),
622 LatLon 41.0 (-72.00)]
624let geoFilter = GeoPolygonFilter (FieldName "tweet.location") points
626```
628#### Document IDs filter
630``` {.haskell}
632-- takes a mapping name and a list of DocIds
633IdsFilter (MappingName "tweet") [DocId "1"]
635```
637#### Range Filter
639``` {.haskell}
641-- RangeFilter :: FieldName
642-- -> RangeValue
643-- -> RangeExecution
644-- -> Cache -> Filter
646let filter = RangeFilter (FieldName "age")
647 (RangeGtLt (GreaterThan 1000.0) (LessThan 100000.0))
648 RangeExecutionIndex False
650```
652``` {.haskell}
654let filter = RangeFilter (FieldName "age")
655 (RangeLte (LessThanEq 100000.0))
656 RangeExecutionIndex False
658```
660##### Date Ranges
662Date ranges are expressed in UTCTime. Date ranges use the same range bound constructors as numerics, except that they end in "D".
664Note that compatibility with ES is tested only down to seconds.
666``` {.haskell}
668let filter = RangeFilter (FieldName "postDate")
669 (RangeDateGtLte
670 (GreaterThanD (UTCTime
671 (ModifiedJulianDay 55000)
672 (secondsToDiffTime 9)))
673 (LessThanEqD (UTCTime
674 (ModifiedJulianDay 55000)
675 (secondsToDiffTime 11))))
676 RangeExecutionIndex False
677```
679#### Regexp Filter
681``` {.haskell}
683-- RegexpFilter
684-- :: FieldName
685-- -> Regexp
686-- -> RegexpFlags
687-- -> CacheName
688-- -> Cache
689-- -> CacheKey
690-- -> Filter
691let filter = RegexpFilter (FieldName "user") (Regexp "bite.*app")
692 AllRegexpFlags (CacheName "test") False (CacheKey "key")
694-- n.b.
695-- data RegexpFlags = AllRegexpFlags
696-- | NoRegexpFlags
697-- | SomeRegexpFlags (NonEmpty RegexpFlag) deriving (Eq, Show)
699-- data RegexpFlag = AnyString
700-- | Automaton
701-- | Complement
702-- | Empty
703-- | Intersection
704-- | Interval deriving (Eq, Show)
706```
708### Aggregations
710#### Adding aggregations to search
712Aggregations can now be added to search queries, or made on their own.
714``` {.haskell}
715type Aggregations = M.Map Text Aggregation
716data Aggregation
717 = TermsAgg TermsAggregation
718 | DateHistogramAgg DateHistogramAggregation
719```
721For convenience, \`\`\`mkAggregations\`\`\` exists, that will create an \`\`\`Aggregations\`\`\` with the aggregation provided.
723For example:
725``` {.haskell}
726 let a = mkAggregations "users" $ TermsAgg $ mkTermsAggregation "user"
727 let search = mkAggregateSearch Nothing a
728```
730Aggregations can be added to an existing search, using the \`\`\`aggBody\`\`\` field
732``` {.haskell}
733 let search = mkSearch (Just (MatchAllQuery Nothing)) Nothing
734 let search' = search {aggBody = Just a}
735```
737Since the \`\`\`Aggregations\`\`\` structure is just a Map Text Aggregation, M.insert can be used to add additional aggregations.
739``` {.haskell}
740 let a' = M.insert "age" (TermsAgg $ mkTermsAggregation "age") a
741```
743#### Extracting aggregations from results
745Aggregations are part of the reply structure of every search, in the form of
747``` {.haskell}
748-- Lift decode and response body to be in the IO monad.
749let decode' = liftM decode
750let responseBody' = liftM responseBody
751let reply = searchByIndex testServer testIndex search
752let response = decode' $ responseBody' reply :: IO (Maybe (SearchResult Tweet))
754-- Now that we have our response, we can extract our terms aggregation result -- which is a list of buckets.
756let terms = do { response' <- response; return $ response' >>= aggregations >>= toTerms "users" }
757terms
758Just (Bucket {buckets = [TermsResult {termKey = "bitemyapp", termsDocCount = 1, termsAggs = Nothing}]})
759```
761Note that bucket aggregation results, such as the TermsResult is a member of the type class :
763``` {.haskell}
764class BucketAggregation a where
765 key :: a -> Text
766 docCount :: a -> Int
767 aggs :: a -> Maybe AggregationResults
768```
770haskell
772You 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
774#### Terms Aggregation
776``` {.haskell}
777data TermsAggregation
778 = TermsAggregation {term :: Either Text Text,
779 termInclude :: Maybe TermInclusion,
780 termExclude :: Maybe TermInclusion,
781 termOrder :: Maybe TermOrder,
782 termMinDocCount :: Maybe Int,
783 termSize :: Maybe Int,
784 termShardSize :: Maybe Int,
785 termCollectMode :: Maybe CollectionMode,
786 termExecutionHint :: Maybe ExecutionHint,
787 termAggs :: Maybe Aggregations}
788```
790Term Aggregations have two factory functions, , and , and can be used as follows:
792``` {.haskell}
793let ta = TermsAgg $ mkTermsAggregation "user"
794```
796There are of course other options that can be added to a Terms Aggregation, such as the collection mode:
798``` {.haskell}
799let ta = mkTermsAggregation "user"
800let ta' = ta { termCollectMode = Just BreadthFirst }
801let ta'' = TermsAgg ta'
802```
804For more documentation on how the Terms Aggregation works, see <http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html>
806#### Date Histogram Aggregation
808``` {.haskell}
809data DateHistogramAggregation
810 = DateHistogramAggregation {dateField :: FieldName,
811 dateInterval :: Interval,
812 dateFormat :: Maybe Text,
813 datePreZone :: Maybe Text,
814 datePostZone :: Maybe Text,
815 datePreOffset :: Maybe Text,
816 datePostOffset :: Maybe Text,
817 dateAggs :: Maybe Aggregations}
818```
820haskell
822The Date Histogram Aggregation works much the same as the Terms Aggregation.
824Relevant functions include , and
826``` {.haskell}
827let dh = DateHistogramAgg (mkDateHistogram (FieldName "postDate") Minute)
828```
830Date histograms also accept a :
832``` {.haskell}
833FractionalInterval :: Float -> TimeInterval -> Interval
834-- TimeInterval is the following:
835data TimeInterval = Weeks | Days | Hours | Minutes | Seconds
836```
838It can be used as follows:
840``` {.haskell}
841let dh = DateHistogramAgg (mkDateHistogram (FieldName "postDate") (FractionalInterval 1.5 Minutes))
842```
844The is defined as:
846``` {.haskell}
847data DateHistogramResult
848 = DateHistogramResult {dateKey :: Int,
849 dateKeyStr :: Maybe Text,
850 dateDocCount :: Int,
851 dateHistogramAggs :: Maybe AggregationResults}
852```
854It is an instance of , and can have nested aggregations in each bucket.
856Buckets can be extracted from a using
858For more information on the Date Histogram Aggregation, see: <http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-aggregations-bucket-datehistogram-aggregation.html>
860Possible future functionality
861=============================
863Span Queries
864------------
866Beginning here: <http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-span-first-query.html>
868Function Score Query
869--------------------
871<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html>
873Node discovery and failover
874---------------------------
876Might require TCP support.
878Support for TCP access to Elasticsearch
879---------------------------------------
881Pretend to be a transport client?
883Bulk cluster-join merge
884-----------------------
886Might require making a lucene index on disk with the appropriate format.
888GeoShapeQuery
889-------------
891<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-geo-shape-query.html>
893GeoShapeFilter
894--------------
896<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-geo-shape-filter.html>
898Geohash cell filter
899-------------------
901<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-geohash-cell-filter.html>
903HasChild Filter
904---------------
906<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-has-child-filter.html>
908HasParent Filter
909----------------
911<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-has-parent-filter.html>
913Indices Filter
914--------------
916<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-indices-filter.html>
918Query Filter
919------------
921<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-query-filter.html>
923Script based sorting
924--------------------
926<http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-sort.html#_script_based_sorting>
928Collapsing redundantly nested and/or structures
929-----------------------------------------------
931The 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.
933Runtime checking for cycles in data structures
934----------------------------------------------
936check for n \> 1 occurrences in DFS:
938<http://hackage.haskell.org/package/stable-maps-0.0.5/docs/System-Mem-StableName-Dynamic.html>
940<http://hackage.haskell.org/package/stable-maps-0.0.5/docs/System-Mem-StableName-Dynamic-Map.html>
942Photo Origin
943============
945Photo from HA! Designs: <https://www.flickr.com/photos/hadesigns/>