@@ -5,14 +5,218 @@
6* Elasticsearch client and query DSL for Haskell
6* Elasticsearch client and query DSL for Haskell
10+Because you're tired of obnoxious errors like [this](http://i.imgur.com/FKtZYIP.png) and want types to guide your use of the API.
8** Stability
12** Stability
10−Bloodhound is alpha at the moment. The library works fine, but I don't want to mislead anyone into thinking the API is final or stable. I wouldn't call the library "complete" or representative of everything you can do in Elasticsearch but being personally familiar with multiple clients in other languages I think the story there is good.
14+Bloodhound is alpha at the moment. The library works fine, but I don't want to mislead anyone into thinking the API is final or stable. I wouldn't call the library "complete" or representative of everything you can do in Elasticsearch but being compared to clients in other languages the story here so far is good.
24+-- Formatted for use in ghci, so there are "let"s in front of the decls.
26+-- :set -XDeriveGeneric
27+import Database.Bloodhound.Client
29+import Data.Either (Either(..))
30+import Data.Maybe (fromJust)
31+import Data.Time.Calendar (Day(..))
32+import Data.Time.Clock (secondsToDiffTime, UTCTime(..))
33+import Data.Text (Text)
34+import GHC.Generics (Generic)
35+import Network.HTTP.Conduit
36+import qualified Network.HTTP.Types.Status as NHTS
38+-- no trailing slashes in servers, library handles building the path.
39+let testServer = (Server "http://localhost:9200")
40+let testIndex = IndexName "twitter"
41+let testMapping = MappingName "tweet"
43+-- defaultIndexSettings is exported by Database.Bloodhound.Client as well.
44+let defaultIndexSettings = IndexSettings (ShardCount 3) (ReplicaCount 2)
46+-- createIndex returns IO Reply
48+-- response :: Reply, Reply is a synonym for Network.HTTP.Conduit.Response
49+response <- createIndex testServer defaultIndexSettings testIndex
58+response <- deleteIndex testServer testIndex
60+-- print response if it was a success
61+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}
63+-- if the index to be deleted didn't exist anyway
64+Response {responseStatus = Status {statusCode = 404, statusMessage = "Not Found"}, responseVersion = HTTP/1.1, responseHeaders = [("Content-Type","application/json; charset=UTF-8"),("Content-Length","65")], responseBody = "{\"error\":\"IndexMissingException[[twitter] missing]\",\"status\":404}", responseCookieJar = CJ {expose = []}, responseClose' = ResponseClose}
70+**** Note, you *have* to do this if you expect to read what you just wrote
74+resp <- refreshIndex testServer testIndex
76+-- print resp on success
77+Response {responseStatus = Status {statusCode = 200, statusMessage = "OK"}, responseVersion = HTTP/1.1, responseHeaders = [("Content-Type","application/json; charset=UTF-8"),("Content-Length","50")], responseBody = "{\"_shards\":{\"total\":10,\"successful\":5,\"failed\":0}}", responseCookieJar = CJ {expose = []}, responseClose' = ResponseClose}
87+-- don't forget imports and the like at the top.
89+data TweetMapping = TweetMapping deriving (Eq, Show)
91+-- I know writing the JSON directly/manually sucks.
92+-- I don't have a real data type for Mappings yet.
93+-- Let me know if this is something you need.
96+instance ToJSON TweetMapping where
97+ toJSON TweetMapping =
99+ object ["properties" .=
100+ object ["location" .=
101+ object ["type" .= ("geo_point" :: Text)]]]]
104+resp <- createMapping testServer testIndex testMapping TweetMapping
112+resp <- deleteMapping testServer testIndex testMapping
116+** Document Operations
118+*** Indexing Documents
122+-- don't forget the imports and derive generic setting for ghci at the beginning of the examples.
125+data Location = Location { lat :: Double
126+ , lon :: Double } deriving (Eq, Generic, Show)
128+data Tweet = Tweet { user :: Text
129+ , postDate :: UTCTime
132+ , location :: Location } deriving (Eq, Generic, Show)
134+exampleTweet = Tweet { user = "bitemyapp"
135+ , postDate = UTCTime
136+ (ModifiedJulianDay 55000)
137+ (secondsToDiffTime 10)
138+ , message = "Use haskell!"
140+ , location = Location 40.12 (-71.34) }
142+-- automagic (generic) derivation of instances because we're lazy.
143+instance ToJSON Tweet
144+instance FromJSON Tweet
145+instance ToJSON Location
146+instance FromJSON Location
149+-- Should be able to toJSON and encode the data structures like this:
150+-- λ> toJSON $ Location 10.0 10.0
151+-- Object fromList [("lat",Number 10.0),("lon",Number 10.0)]
152+-- λ> encode $ Location 10.0 10.0
153+-- "{\"lat\":10,\"lon\":10}"
155+resp <- indexDocument testServer testIndex testMapping exampleTweet (DocId "1")
157+-- print resp on success
158+Response {responseStatus = Status {statusCode = 200, statusMessage = "OK"}, responseVersion = HTTP/1.1, responseHeaders = [("Content-Type","application/json; charset=UTF-8"),("Content-Length","75")], responseBody = "{\"_index\":\"twitter\",\"_type\":\"tweet\",\"_id\":\"1\",\"_version\":2,\"created\":false}", responseCookieJar = CJ {expose = []}, responseClose' = ResponseClose}
162+*** Deleting Documents
166+resp <- deleteDocument testServer testIndex testMapping (DocId "1")
170+*** Getting Documents
174+-- n.b., you'll need the earlier imports. responseBody is from http-conduit
176+resp <- getDocument testServer testIndex testMapping (DocId "1")
178+-- responseBody :: Response body -> body
179+let body = responseBody resp
181+-- you have two options, you use decode and just get Maybe (EsResult Tweet)
182+-- or you can use eitherDecode and get Either String (EsResult Tweet)
184+let maybeResult = decode body :: Maybe (EsResult Tweet)
185+-- the explicit typing is so Aeson knows how to parse the JSON.
187+-- use either if you want to know why something failed to parse.
188+-- (string errors, sadly)
189+let eitherResult = decode body :: Either String (EsResult Tweet)
191+-- print eitherResult should look like:
192+Right (EsResult {_index = "twitter", _type = "tweet", _id = "1", _version = 2, found = Just True, _source = Tweet {user = "bitemyapp", postDate = 2009-06-18 00:00:10 UTC, message = "Use haskell!", age = 10000, location = Location {lat = 40.12, lon = -71.34}}})
194+-- _source in EsResult is parametric, we dispatch the type by passing in what we expect (Tweet) as a parameter to EsResult.
14* Possible future functionality
206* Possible future functionality
208+** Node discovery and failover
210+Might require TCP support.
212+** Support for TCP access to Elasticsearch
214+Pretend to be a transport client?
216+** Bulk cluster-join merge
218+Might require making a lucene index on disk with the appropriate format.
16** GeoShapeFilter
220** GeoShapeFilter
18http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-geo-shape-filter.html
222http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-geo-shape-filter.html