Copyright | (C) 2014 Chris Allen |
---|---|
License | BSD-style (see the file LICENSE) |
Maintainer | Chris Allen <cma@bitemyapp.com |
Stability | provisional |
Portability | OverloadedStrings |
Safe Haskell | None |
Language | Haskell2010 |
Client side functions for talking to Elasticsearch servers.
- createIndex :: Server -> IndexSettings -> IndexName -> IO Reply
- deleteIndex :: Server -> IndexName -> IO Reply
- indexExists :: Server -> IndexName -> IO Bool
- openIndex :: Server -> IndexName -> IO Reply
- closeIndex :: Server -> IndexName -> IO Reply
- putMapping :: ToJSON a => Server -> IndexName -> MappingName -> a -> IO Reply
- deleteMapping :: Server -> IndexName -> MappingName -> IO Reply
- indexDocument :: ToJSON doc => Server -> IndexName -> MappingName -> doc -> DocId -> IO Reply
- getDocument :: Server -> IndexName -> MappingName -> DocId -> IO Reply
- documentExists :: Server -> IndexName -> MappingName -> DocId -> IO Bool
- deleteDocument :: Server -> IndexName -> MappingName -> DocId -> IO Reply
- searchAll :: Server -> Search -> IO Reply
- searchByIndex :: Server -> IndexName -> Search -> IO Reply
- searchByType :: Server -> IndexName -> MappingName -> Search -> IO Reply
- refreshIndex :: Server -> IndexName -> IO Reply
- mkSearch :: Maybe Query -> Maybe Filter -> Search
- mkAggregateSearch :: Maybe Query -> Aggregations -> Search
- mkHighlightSearch :: Maybe Query -> Highlights -> Search
- bulk :: Server -> Vector BulkOperation -> IO Reply
- pageSearch :: Int -> Int -> Search -> Search
- mkShardCount :: Int -> Maybe ShardCount
- mkReplicaCount :: Int -> Maybe ReplicaCount
- getStatus :: Server -> IO (Maybe Status)
- encodeBulkOperations :: Vector BulkOperation -> ByteString
- encodeBulkOperation :: BulkOperation -> ByteString
Bloodhound client functions
The examples in this module assume the following code has been run. The :{ and :} will only work in GHCi. You'll only need the data types and typeclass instances for the functions that make use of them.
>>>
:set -XOverloadedStrings
>>>
:set -XDeriveGeneric
>>>
import Database.Bloodhound
>>>
import Test.DocTest.Prop (assert)
>>>
let testServer = (Server "http://localhost:9200")
>>>
let testIndex = IndexName "twitter"
>>>
let testMapping = MappingName "tweet"
>>>
let defaultIndexSettings = IndexSettings (ShardCount 3) (ReplicaCount 2)
>>>
data TweetMapping = TweetMapping deriving (Eq, Show)
>>>
_ <- deleteIndex testServer testIndex
>>>
_ <- deleteMapping testServer testIndex testMapping
>>>
import GHC.Generics
>>>
import Data.Time.Calendar (Day (..))
>>>
import Data.Time.Clock (UTCTime (..), secondsToDiffTime)
>>>
:{
instance ToJSON TweetMapping where toJSON TweetMapping = object ["tweet" .= object ["properties" .= object ["location" .= object ["type" .= ("geo_point" :: Text)]]]] data Location = Location { lat :: Double , lon :: Double } deriving (Eq, Generic, Show) data Tweet = Tweet { user :: Text , postDate :: UTCTime , message :: Text , age :: Int , location :: Location } deriving (Eq, Generic, Show) exampleTweet = Tweet { user = "bitemyapp" , postDate = UTCTime (ModifiedJulianDay 55000) (secondsToDiffTime 10) , message = "Use haskell!" , age = 10000 , location = Location 40.12 (-71.34) } instance ToJSON Tweet instance FromJSON Tweet instance ToJSON Location instance FromJSON Location data BulkTest = BulkTest { name :: Text } deriving (Eq, Generic, Show) instance FromJSON BulkTest instance ToJSON BulkTest :}
createIndex :: Server -> IndexSettings -> IndexName -> IO Reply Source
createIndex
will create an index given a Server
, IndexSettings
, and an IndexName
.
>>>
response <- createIndex testServer defaultIndexSettings (IndexName "didimakeanindex")
>>>
respIsTwoHunna response
True>>>
indexExists testServer (IndexName "didimakeanindex")
True
deleteIndex :: Server -> IndexName -> IO Reply Source
deleteIndex
will delete an index given a Server
, and an IndexName
.
>>>
response <- createIndex testServer defaultIndexSettings (IndexName "didimakeanindex")
>>>
response <- deleteIndex testServer (IndexName "didimakeanindex")
>>>
respIsTwoHunna response
True>>>
indexExists testServer testIndex
False
indexExists :: Server -> IndexName -> IO Bool Source
indexExists
enables you to check if an index exists. Returns Bool
in IO
>>>
exists <- indexExists testServer testIndex
openIndex :: Server -> IndexName -> IO Reply Source
openIndex
opens an index given a Server
and an IndexName
. Explained in further detail at
http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-open-close.html
>>>
reply <- openIndex testServer testIndex
closeIndex :: Server -> IndexName -> IO Reply Source
closeIndex
closes an index given a Server
and an IndexName
. Explained in further detail at
http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-open-close.html
>>>
reply <- closeIndex testServer testIndex
putMapping :: ToJSON a => Server -> IndexName -> MappingName -> a -> IO Reply Source
putMapping
is an HTTP PUT and has upsert semantics. Mappings are schemas
for documents in indexes.
>>>
_ <- createIndex testServer defaultIndexSettings testIndex
>>>
resp <- putMapping testServer testIndex testMapping TweetMapping
>>>
print resp
Response {responseStatus = Status {statusCode = 200, statusMessage = "OK"}, responseVersion = HTTP/1.1, responseHeaders = [("Content-Type","application/json; charset=UTF-8"),("Content-Length","21")], responseBody = "{\"acknowledged\":true}", responseCookieJar = CJ {expose = []}, responseClose' = ResponseClose}
deleteMapping :: Server -> IndexName -> MappingName -> IO Reply Source
deleteMapping
is an HTTP DELETE and deletes a mapping for a given index.
Mappings are schemas for documents in indexes.
>>>
_ <- createIndex testServer defaultIndexSettings testIndex
>>>
_ <- putMapping testServer testIndex testMapping TweetMapping
>>>
resp <- deleteMapping testServer testIndex testMapping
>>>
print resp
Response {responseStatus = Status {statusCode = 200, statusMessage = "OK"}, responseVersion = HTTP/1.1, responseHeaders = [("Content-Type","application/json; charset=UTF-8"),("Content-Length","21")], responseBody = "{\"acknowledged\":true}", responseCookieJar = CJ {expose = []}, responseClose' = ResponseClose}
indexDocument :: ToJSON doc => Server -> IndexName -> MappingName -> doc -> DocId -> IO Reply Source
indexDocument
is the primary way to save a single document in
Elasticsearch. The document itself is simply something we can
convert into a JSON Value
. The DocId
will function as the
primary key for the document.
>>>
resp <- indexDocument testServer testIndex testMapping exampleTweet (DocId "1")
>>>
print resp
Response {responseStatus = Status {statusCode = 201, statusMessage = "Created"}, responseVersion = HTTP/1.1, responseHeaders = [("Content-Type","application/json; charset=UTF-8"),("Content-Length","74")], responseBody = "{\"_index\":\"twitter\",\"_type\":\"tweet\",\"_id\":\"1\",\"_version\":1,\"created\":true}", responseCookieJar = CJ {expose = []}, responseClose' = ResponseClose}
getDocument :: Server -> IndexName -> MappingName -> DocId -> IO Reply Source
getDocument
is a straight-forward way to fetch a single document from
Elasticsearch using a Server
, IndexName
, MappingName
, and a DocId
.
The DocId
is the primary key for your Elasticsearch document.
>>>
yourDoc <- getDocument testServer testIndex testMapping (DocId "1")
documentExists :: Server -> IndexName -> MappingName -> DocId -> IO Bool Source
documentExists
enables you to check if a document exists. Returns Bool
in IO
>>>
exists <- documentExists testServer testIndex testMapping (DocId "1")
deleteDocument :: Server -> IndexName -> MappingName -> DocId -> IO Reply Source
deleteDocument
is the primary way to delete a single document.
>>>
_ <- deleteDocument testServer testIndex testMapping (DocId "1")
searchByIndex :: Server -> IndexName -> Search -> IO Reply Source
searchByIndex
, given a Search
and an IndexName
, will perform that search
against all mappings within an index on an Elasticsearch server.
>>>
let query = TermQuery (Term "user" "bitemyapp") Nothing
>>>
let search = mkSearch (Just query) Nothing
>>>
reply <- searchByIndex testServer testIndex search
searchByType :: Server -> IndexName -> MappingName -> Search -> IO Reply Source
searchByType
, given a Search
, IndexName
, and MappingName
, will perform that
search against a specific mapping within an index on an Elasticsearch server.
>>>
let query = TermQuery (Term "user" "bitemyapp") Nothing
>>>
let search = mkSearch (Just query) Nothing
>>>
reply <- searchByType testServer testIndex testMapping search
refreshIndex :: Server -> IndexName -> IO Reply Source
refreshIndex
will force a refresh on an index. You must
do this if you want to read what you wrote.
>>>
_ <- createIndex testServer defaultIndexSettings testIndex
>>>
_ <- refreshIndex testServer testIndex
mkSearch :: Maybe Query -> Maybe Filter -> Search Source
mkSearch
is a helper function for defaulting additional fields of a Search
to Nothing in case you only care about your Query
and Filter
. Use record update
syntax if you want to add things like aggregations or highlights while still using
this helper function.
>>>
let query = TermQuery (Term "user" "bitemyapp") Nothing
>>>
mkSearch (Just query) Nothing
Search {queryBody = Just (TermQuery (Term {termField = "user", termValue = "bitemyapp"}) Nothing), filterBody = Nothing, sortBody = Nothing, aggBody = Nothing, highlight = Nothing, trackSortScores = False, from = 0, size = 10}
mkAggregateSearch :: Maybe Query -> Aggregations -> Search Source
mkAggregateSearch
is a helper function that defaults everything in a Search
except for
the Query
and the Aggregation
.
>>>
let terms = TermsAgg $ (mkTermsAggregation "user") { termCollectMode = Just BreadthFirst }
>>>
terms
TermsAgg (TermsAggregation {term = Left "user", termInclude = Nothing, termExclude = Nothing, termOrder = Nothing, termMinDocCount = Nothing, termSize = Nothing, termShardSize = Nothing, termCollectMode = Just BreadthFirst, termExecutionHint = Nothing, termAggs = Nothing})>>>
let myAggregation = mkAggregateSearch Nothing $ mkAggregations "users" terms
mkHighlightSearch :: Maybe Query -> Highlights -> Search Source
mkHighlightSearch
is a helper function that defaults everything in a Search
except for
the Query
and the Aggregation
.
>>>
let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
>>>
let testHighlight = Highlights Nothing [FieldHighlight (FieldName "message") Nothing]
>>>
let search = mkHighlightSearch (Just query) testHighlight
bulk :: Server -> Vector BulkOperation -> IO Reply Source
bulk
uses
Elasticsearch's bulk API
to perform bulk operations. The BulkOperation
data type encodes the
indexupdatedelete/create operations. You pass a Vector
of BulkOperation
s
and a Server
to bulk
in order to send those operations up to your Elasticsearch
server to be performed. I changed from [BulkOperation] to a Vector due to memory overhead.
>>>
let stream = V.fromList [BulkIndex testIndex testMapping (DocId "2") (toJSON (BulkTest "blah"))]
>>>
_ <- bulk testServer stream
>>>
_ <- refreshIndex testServer testIndex
pageSearch :: Int -> Int -> Search -> Search Source
pageSearch
is a helper function that takes a search and assigns the page from and to
fields for the search.
>>>
let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")
>>>
let search = mkSearch (Just query) Nothing
>>>
search
Search {queryBody = Just (QueryMatchQuery (MatchQuery {matchQueryField = FieldName "_all", matchQueryQueryString = QueryString "haskell", matchQueryOperator = Or, matchQueryZeroTerms = ZeroTermsNone, matchQueryCutoffFrequency = Nothing, matchQueryMatchType = Nothing, matchQueryAnalyzer = Nothing, matchQueryMaxExpansions = Nothing, matchQueryLenient = Nothing})), filterBody = Nothing, sortBody = Nothing, aggBody = Nothing, highlight = Nothing, trackSortScores = False, from = 0, size = 10}>>>
pageSearch 10 100 search
Search {queryBody = Just (QueryMatchQuery (MatchQuery {matchQueryField = FieldName "_all", matchQueryQueryString = QueryString "haskell", matchQueryOperator = Or, matchQueryZeroTerms = ZeroTermsNone, matchQueryCutoffFrequency = Nothing, matchQueryMatchType = Nothing, matchQueryAnalyzer = Nothing, matchQueryMaxExpansions = Nothing, matchQueryLenient = Nothing})), filterBody = Nothing, sortBody = Nothing, aggBody = Nothing, highlight = Nothing, trackSortScores = False, from = 10, size = 100}
mkShardCount :: Int -> Maybe ShardCount Source
mkShardCount
is a straight-forward smart constructor for ShardCount
which rejects Int
values below 1 and above 1000.
>>>
mkShardCount 10
Just (ShardCount 10)
mkReplicaCount :: Int -> Maybe ReplicaCount Source
mkReplicaCount
is a straight-forward smart constructor for ReplicaCount
which rejects Int
values below 1 and above 1000.
>>>
mkReplicaCount 10
Just (ReplicaCount 10)
getStatus :: Server -> IO (Maybe Status) Source
getStatus
fetches the Status
of a Server
>>>
getStatus testServer
Just (Status {ok = Nothing, status = 200, name = "Arena", version = Version {number = "1.4.1", build_hash = "89d3241d670db65f994242c8e8383b169779e2d4", build_timestamp = 2014-11-26 15:49:29 UTC, build_snapshot = False, lucene_version = "4.10.2"}, tagline = "You Know, for Search"})
encodeBulkOperations :: Vector BulkOperation -> ByteString Source
encodeBulkOperations
is a convenience function for dumping a vector of BulkOperation
into an ByteString
>>>
let bulkOps = V.fromList [BulkIndex testIndex testMapping (DocId "2") (toJSON (BulkTest "blah"))]
>>>
encodeBulkOperations bulkOps
"\n{\"index\":{\"_type\":\"tweet\",\"_id\":\"2\",\"_index\":\"twitter\"}}\n{\"name\":\"blah\"}\n"
encodeBulkOperation :: BulkOperation -> ByteString Source
encodeBulkOperation
is a convenience function for dumping a single BulkOperation
into an ByteString
>>>
let bulkOp = BulkIndex testIndex testMapping (DocId "2") (toJSON (BulkTest "blah"))
>>>
encodeBulkOperation bulkOp
"{\"index\":{\"_type\":\"tweet\",\"_id\":\"2\",\"_index\":\"twitter\"}}\n{\"name\":\"blah\"}"