Awesome
Bloodhound
Elasticsearch client and query DSL for Haskell
Why?
Search doesn't have to be hard. Let the dog do it.
Endorsements
"Bloodhound makes Elasticsearch almost tolerable!" - Almost-gruntled user
"ES is a nightmare but Bloodhound at least makes it tolerable." - Same user, later opinion.
Version compatibility
See the Github compatibility workflow for a listing of Elasticsearch and OpenSearch versions we test against.
The workflow executions can be seen in the Github actions view.
Stability
Bloodhound 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.
Testing
The Bloodhound project uses Github workflows using Cabal to test for regressions and compatibility. A convenient development environment is provided by Nix and a Makefile, though the project can be built with only Cabal.
To run the tests:
- Get into the Nix environment by running
nix develop
(ornix-shell
for a non-flake setup) - Start Elasticsearch defined by
docker-compose.yml
:make compose
- Run the tests with Cabal:
cabal test
The second step can be left out if ElasticSearch (or OpenSearch) is started manually.
Contributions
Any contribution is welcomed, for consistency reason ormolu
is used.
Hackage page and Haddock documentation
http://hackage.haskell.org/package/bloodhound
Elasticsearch Tutorial
It'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.
Examples
See the examples directory for example code.
Index a document
indexDocument testIndex defaultIndexDocumentSettings exampleTweet (DocId "1")
{-
IndexedDocument
{ idxDocIndex = "twitter"
, idxDocType = "_doc"
, idxDocId = "1"
, idxDocVersion = 3
, idxDocResult = "updated"
, idxDocShards =
ShardResult
{ shardTotal = 1
, shardsSuccessful = 1
, shardsSkipped = 0
, shardsFailed = 0
}
, idxDocSeqNo = 2
, idxDocPrimaryTerm = 1
}
-}
Fetch documents
let query = TermQuery (Term "user" "bitemyapp") boost
let search = mkSearch (Just query) boost
searchByIndex @_ @Tweet testIndex search
{-
SearchResult
{ took = 1
, timedOut = False
, shards =
ShardResult
{ shardTotal = 1
, shardsSuccessful = 1
, shardsSkipped = 0
, shardsFailed = 0
}
, searchHits =
SearchHits
{ hitsTotal = HitsTotal { value = 2 , relation = HTR_EQ }
, maxScore = Just 0.18232156
, hits =
[ Hit
{ hitIndex = IndexName "twitter"
, hitDocId = DocId "1"
, hitScore = Just 0.18232156
, hitSource =
Just
Tweet
{ user = "bitemyapp"
, postDate = 2009-06-18 00:00:10 UTC
, message = "Use haskell!"
, age = 10000
, location = LatLon { lat = 40.12 , lon = -71.3 }
}
, hitSort = Nothing
, hitFields = Nothing
, hitHighlight = Nothing
, hitInnerHits = Nothing
}
, Hit
{ hitIndex = IndexName "twitter"
, hitDocId = DocId "2"
, hitScore = Just 0.18232156
, hitSource =
Just
Tweet
{ user = "bitemyapp"
, postDate = 2009-06-18 00:00:10 UTC
, message = "Use haskell!"
, age = 10000
, location = LatLon { lat = 40.12 , lon = -71.3 }
}
, hitSort = Nothing
, hitFields = Nothing
, hitHighlight = Nothing
, hitInnerHits = Nothing
}
]
}
, aggregations = Nothing
, scrollId = Nothing
, suggest = Nothing
, pitId = Nothing
}
-}
Contributors
- Chris Allen
- Liam Atkinson
- Christopher Guiney
- Curtis Carter
- Michael Xavier
- Bob Long
- Maximilian Tagher
- Anna Kopp
- Matvey B. Aksenov
- Jan-Philip Loos
- Gautier DI FOLCO
Possible future functionality
Span Queries
Beginning here: https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-span-first-query.html
Function Score Query
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html
Node discovery and failover
Might require TCP support.
Support for TCP access to Elasticsearch
Pretend to be a transport client?
Bulk cluster-join merge
Might require making a lucene index on disk with the appropriate format.
GeoShapeQuery
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-shape-query.html
GeoShapeFilter
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-shape-filter.html
Geohash cell filter
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geohash-cell-filter.html
HasChild Filter
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-has-child-filter.html
HasParent Filter
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-has-parent-filter.html
Indices Filter
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-indices-filter.html
Query Filter
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-filter.html
Script based sorting
Collapsing redundantly nested and/or structures
The 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.
Runtime checking for cycles in data structures
check for n > 1 occurrences in DFS:
http://hackage.haskell.org/package/stable-maps-0.0.5/docs/System-Mem-StableName-Dynamic.html
http://hackage.haskell.org/package/stable-maps-0.0.5/docs/System-Mem-StableName-Dynamic-Map.html
Photo Origin
Photo from HA! Designs: https://www.flickr.com/photos/hadesigns/