mirror of
https://github.com/typeable/bloodhound.git
synced 2024-09-11 08:25:32 +03:00
First stab at removing doctests
This is for #173 Not sure how to handle all the exposition removed from the README. Maybe I should leave it? It doesn't really fit that neatly into examples because a lot of the coode is just introducing data types and linking off to ES docs.
This commit is contained in:
parent
1ea188e069
commit
9f75c4e7e5
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,3 +16,4 @@ bloodhound.iml
|
||||
.hg/00changelog.i
|
||||
.hg
|
||||
.hgignore
|
||||
examples/bloodhound-examples.cabal
|
||||
|
@ -36,8 +36,8 @@ script:
|
||||
- stack setup
|
||||
- stack update
|
||||
- stack build -j1
|
||||
- stack test bloodhound:doctests --flag bloodhound:$ESFLAG
|
||||
- stack test bloodhound:tests --test-arguments="--qc-max-success 500" --flag bloodhound:$ESFLAG
|
||||
- cd examples && stack build -j1
|
||||
|
||||
cache:
|
||||
directories:
|
||||
|
848
README.md
848
README.md
@ -21,9 +21,19 @@ Endorsements
|
||||
Version compatibility
|
||||
---------------------
|
||||
|
||||
Elasticsearch \>=1.0 && \<2.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 && \<2.0 versions of Elasticsearch work. Some (or even most?) features will work with versions \>=2.0, but it is not officially supported yet.
|
||||
As of version 0.13.0.0, Bloodhound has 2 separate module trees for
|
||||
ElasticSearch versions 1 and 5. Import the module that is appropriate
|
||||
for your use case. If you would like to add support for another major
|
||||
version, open a ticket expressing your intend and follow the pattern
|
||||
used for other versions. We weighed the idea of sharing code between
|
||||
versions but it just got too messy, especially considering the
|
||||
instability of the ElasticSearch API. We switched to a model which
|
||||
would allow the persons responsible for a particular protocol version
|
||||
to maintain that version while avoiding conflict with other versions.
|
||||
|
||||
See our [TravisCI](https://travis-ci.org/bitemyapp/bloodhound) for a
|
||||
listing of ElasticSearch version we test against.
|
||||
|
||||
Current versions we test against are 1.2.4, 1.3.6, 1.4.1, 1.5.2, 1.6.0, and 1.7.2. 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.
|
||||
|
||||
Stability
|
||||
---------
|
||||
@ -41,7 +51,7 @@ Steps to run the tests locally:
|
||||
3. In your local Bloodhound directory, run `stack setup && stack build`
|
||||
4. Start the desired version of ElasticSearch at `localhost:9200`, which should be the default.
|
||||
5. Run `stack test` in your local Bloodhound directory.
|
||||
6. The unit tests will pass if you re-execute `stack test`, but some of the doctests might fail due to existing data in ElasticSearch. If you want to start with a clean slate, stop your ElasticSearch instance, delete the `data/` folder in the ElasticSearch installation, restart ElasticSearch, and re-run `stack test`.
|
||||
6. The unit tests will pass if you re-execute `stack test`. If you want to start with a clean slate, stop your ElasticSearch instance, delete the `data/` folder in the ElasticSearch installation, restart ElasticSearch, and re-run `stack test`.
|
||||
|
||||
|
||||
Hackage page and Haddock documentation
|
||||
@ -57,837 +67,7 @@ It's not using Bloodhound, but if you need an introduction to or overview of Ela
|
||||
Examples
|
||||
========
|
||||
|
||||
Index Operations
|
||||
----------------
|
||||
|
||||
### Create Index
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
-- Formatted for use in ghci, so there are "let"s in front of the decls.
|
||||
|
||||
-- if you see :{ and :}, they're so you can copy-paste
|
||||
-- the multi-line examples into your ghci REPL.
|
||||
|
||||
:set -XDeriveGeneric
|
||||
:set -XOverloadedStrings
|
||||
:{
|
||||
import Control.Applicative
|
||||
import Database.Bloodhound
|
||||
import Data.Aeson
|
||||
import Data.Either (Either(..))
|
||||
import Data.Maybe (fromJust)
|
||||
import Data.Time.Calendar (Day(..))
|
||||
import Data.Time.Clock (secondsToDiffTime, UTCTime(..))
|
||||
import Data.Text (Text)
|
||||
import GHC.Generics (Generic)
|
||||
import Network.HTTP.Client
|
||||
import qualified Network.HTTP.Types.Status as NHTS
|
||||
|
||||
-- no trailing slashes in servers, library handles building the path.
|
||||
let testServer = (Server "http://localhost:9200")
|
||||
let testIndex = IndexName "twitter"
|
||||
let testMapping = MappingName "tweet"
|
||||
let withBH' = withBH defaultManagerSettings testServer
|
||||
|
||||
-- defaultIndexSettings is exported by Database.Bloodhound as well
|
||||
let defaultIndexSettings = IndexSettings (ShardCount 3) (ReplicaCount 2)
|
||||
|
||||
-- createIndex returns MonadBH m => m Reply. You can use withBH for
|
||||
-- one-off commands or you can use runBH to group together commands
|
||||
-- and to pass in your own HTTP manager for pipelining.
|
||||
|
||||
-- response :: Reply, Reply is a synonym for Network.HTTP.Conduit.Response
|
||||
response <- withBH' $ createIndex defaultIndexSettings testIndex
|
||||
:}
|
||||
|
||||
```
|
||||
|
||||
### Delete Index
|
||||
|
||||
#### Code
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
-- response :: Reply
|
||||
response <- withBH' $ deleteIndex testIndex
|
||||
|
||||
```
|
||||
|
||||
#### Example Response
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
-- print response if it was a success
|
||||
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}
|
||||
|
||||
-- if the index to be deleted didn't exist anyway
|
||||
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}
|
||||
|
||||
```
|
||||
|
||||
### Refresh Index
|
||||
|
||||
#### Note, you **have** to do this if you expect to read what you just wrote
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
resp <- withBH' $ refreshIndex testIndex
|
||||
|
||||
```
|
||||
|
||||
#### Example Response
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
-- print resp on success
|
||||
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}
|
||||
|
||||
```
|
||||
|
||||
Mapping Operations
|
||||
------------------
|
||||
|
||||
### Create Mapping
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
-- don't forget imports and the like at the top.
|
||||
|
||||
data TweetMapping = TweetMapping deriving (Eq, Show)
|
||||
|
||||
-- I know writing the JSON manually sucks.
|
||||
-- I don't have a proper data type for Mappings yet.
|
||||
-- Let me know if this is something you need.
|
||||
|
||||
:{
|
||||
instance ToJSON TweetMapping where
|
||||
toJSON TweetMapping =
|
||||
object ["properties" .=
|
||||
object ["location" .=
|
||||
object ["type" .= ("geo_point" :: Text)]]]
|
||||
:}
|
||||
|
||||
resp <- withBH' $ putMapping testIndex testMapping TweetMapping
|
||||
|
||||
```
|
||||
|
||||
### Delete Mapping
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
resp <- withBH' $ deleteMapping testIndex testMapping
|
||||
|
||||
```
|
||||
|
||||
Document Operations
|
||||
-------------------
|
||||
|
||||
### Indexing Documents
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
-- don't forget the imports and derive generic setting for ghci
|
||||
-- at the beginning of the examples.
|
||||
|
||||
:{
|
||||
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) }
|
||||
|
||||
-- automagic (generic) derivation of instances because we're lazy.
|
||||
instance ToJSON Tweet
|
||||
instance FromJSON Tweet
|
||||
instance ToJSON Location
|
||||
instance FromJSON Location
|
||||
:}
|
||||
|
||||
-- Should be able to toJSON and encode the data structures like this:
|
||||
-- λ> toJSON $ Location 10.0 10.0
|
||||
-- Object fromList [("lat",Number 10.0),("lon",Number 10.0)]
|
||||
-- λ> encode $ Location 10.0 10.0
|
||||
-- "{\"lat\":10,\"lon\":10}"
|
||||
|
||||
resp <- withBH' $ indexDocument testIndex testMapping defaultIndexDocumentSettings exampleTweet (DocId "1")
|
||||
|
||||
```
|
||||
|
||||
#### Example Response
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
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}
|
||||
|
||||
```
|
||||
|
||||
### Deleting Documents
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
resp <- withBH' $ deleteDocument testIndex testMapping (DocId "1")
|
||||
|
||||
```
|
||||
|
||||
### Getting Documents
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
-- n.b., you'll need the earlier imports. responseBody is from http-conduit
|
||||
|
||||
resp <- withBH' $ getDocument testIndex testMapping (DocId "1")
|
||||
|
||||
-- responseBody :: Response body -> body
|
||||
let body = responseBody resp
|
||||
|
||||
-- you have two options, you use decode and just get Maybe (EsResult Tweet)
|
||||
-- or you can use eitherDecode and get Either String (EsResult Tweet)
|
||||
|
||||
let maybeResult = decode body :: Maybe (EsResult Tweet)
|
||||
-- the explicit typing is so Aeson knows how to parse the JSON.
|
||||
|
||||
-- use either if you want to know why something failed to parse.
|
||||
-- (string errors, sadly)
|
||||
let eitherResult = eitherDecode body :: Either String (EsResult Tweet)
|
||||
|
||||
-- print eitherResult should look like:
|
||||
Right (EsResult {_index = "twitter"
|
||||
, _type = "tweet"
|
||||
, _id = "1"
|
||||
, foundResult = Just (EsResultFound { _version = 2
|
||||
, _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}}})})
|
||||
|
||||
-- _source in EsResultFound is parametric, we dispatch the type by passing in what we expect (Tweet) as a parameter to EsResult.
|
||||
|
||||
-- use the _source record accessor to get at your document
|
||||
fmap (fmap _source . foundResult) eitherResult
|
||||
Right (Just (Tweet {user = "bitemyapp"
|
||||
, postDate = 2009-06-18 00:00:10 UTC
|
||||
, message = "Use haskell!"
|
||||
, age = 10000
|
||||
, location = Location {lat = 40.12, lon = -71.34}}))
|
||||
|
||||
```
|
||||
|
||||
Bulk Operations
|
||||
---------------
|
||||
|
||||
### Bulk create, index
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
-- don't forget the imports and derive generic setting for ghci
|
||||
-- at the beginning of the examples.
|
||||
|
||||
:{
|
||||
-- Using the earlier Tweet datatype and exampleTweet data
|
||||
|
||||
-- just changing up the data a bit.
|
||||
let bulkTest = exampleTweet { user = "blah" }
|
||||
let bulkTestTwo = exampleTweet { message = "woohoo!" }
|
||||
|
||||
-- create only bulk operation
|
||||
-- BulkCreate :: IndexName -> MappingName -> DocId -> Value -> BulkOperation
|
||||
let firstOp = BulkCreate testIndex
|
||||
testMapping (DocId "3") (toJSON bulkTest)
|
||||
|
||||
-- index operation "create or update"
|
||||
let sndOp = BulkIndex testIndex
|
||||
testMapping (DocId "4") (toJSON bulkTestTwo)
|
||||
|
||||
-- Some explanation, the final "Value" type that BulkIndex,
|
||||
-- BulkCreate, and BulkUpdate accept is the actual document
|
||||
-- data that your operation applies to. BulkDelete doesn't
|
||||
-- take a value because it's just deleting whatever DocId
|
||||
-- you pass.
|
||||
|
||||
-- list of bulk operations
|
||||
let stream = [firstDoc, secondDoc]
|
||||
|
||||
-- Fire off the actual bulk request
|
||||
-- bulk :: Vector BulkOperation -> IO Reply
|
||||
resp <- withBH' $ bulk stream
|
||||
:}
|
||||
|
||||
```
|
||||
|
||||
### Encoding individual bulk API operations
|
||||
|
||||
``` {.haskell}
|
||||
-- the following functions are exported in Bloodhound so
|
||||
-- you can build up bulk operations yourself
|
||||
encodeBulkOperations :: V.Vector BulkOperation -> L.ByteString
|
||||
encodeBulkOperation :: BulkOperation -> L.ByteString
|
||||
|
||||
-- How to use the above:
|
||||
data BulkTest = BulkTest { name :: Text } deriving (Eq, Generic, Show)
|
||||
instance FromJSON BulkTest
|
||||
instance ToJSON BulkTest
|
||||
|
||||
_ <- insertData
|
||||
let firstTest = BulkTest "blah"
|
||||
let secondTest = BulkTest "bloo"
|
||||
let firstDoc = BulkIndex testIndex
|
||||
testMapping (DocId "2") (toJSON firstTest)
|
||||
let secondDoc = BulkCreate testIndex
|
||||
testMapping (DocId "3") (toJSON secondTest)
|
||||
let stream = V.fromList [firstDoc, secondDoc] :: V.Vector BulkOperation
|
||||
|
||||
-- to encode yourself
|
||||
let firstDocEncoded = encode firstDoc :: L.ByteString
|
||||
|
||||
-- to encode a vector of bulk operations
|
||||
let encodedOperations = encodeBulkOperations stream
|
||||
|
||||
-- to insert into a particular server
|
||||
-- bulk :: V.Vector BulkOperation -> IO Reply
|
||||
_ <- withBH' $ bulk streamp
|
||||
|
||||
```
|
||||
|
||||
Search
|
||||
------
|
||||
|
||||
### Querying
|
||||
|
||||
#### Term Query
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
-- exported by the Client module, just defaults some stuff.
|
||||
-- mkSearch :: Maybe Query -> Maybe Filter -> Search
|
||||
-- mkSearch query filter = Search query filter Nothing False (From 0) (Size 10) Nothing
|
||||
|
||||
let query = TermQuery (Term "user" "bitemyapp") Nothing
|
||||
|
||||
-- AND'ing identity filter with itself and then tacking it onto a query
|
||||
-- search should be a null-operation. I include it for the sake of example.
|
||||
-- <||> (or/plus) should make it into a search that returns everything.
|
||||
|
||||
let filter = IdentityFilter <&&> IdentityFilter
|
||||
|
||||
-- constructing the search object the searchByIndex function dispatches on.
|
||||
let search = mkSearch (Just query) (Just filter)
|
||||
|
||||
-- you can also searchByType and specify the mapping name.
|
||||
reply <- withBH' $ searchByIndex testIndex search
|
||||
|
||||
let result = eitherDecode (responseBody reply) :: Either String (SearchResult Tweet)
|
||||
|
||||
λ> fmap (hits . searchHits) result
|
||||
Right [Hit {hitIndex = IndexName "twitter"
|
||||
, hitType = MappingName "tweet"
|
||||
, hitDocId = DocId "1"
|
||||
, hitScore = 0.30685282
|
||||
, hitSource = Tweet {user = "bitemyapp"
|
||||
, postDate = 2009-06-18 00:00:10 UTC
|
||||
, message = "Use haskell!"
|
||||
, age = 10000
|
||||
, location = Location {lat = 40.12, lon = -71.34}}}]
|
||||
|
||||
```
|
||||
|
||||
#### Match Query
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
let query = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
|
||||
let search = mkSearch (Just query) Nothing
|
||||
|
||||
```
|
||||
|
||||
#### Multi-Match Query
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
let fields = [FieldName "user", FieldName "message"]
|
||||
let query = QueryMultiMatchQuery $ mkMultiMatchQuery fields (QueryString "bitemyapp")
|
||||
let search = mkSearch (Just query) Nothing
|
||||
|
||||
```
|
||||
|
||||
#### Bool Query
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
let innerQuery = QueryMatchQuery $
|
||||
mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
|
||||
let query = QueryBoolQuery $
|
||||
mkBoolQuery [innerQuery] [] []
|
||||
let search = mkSearch (Just query) Nothing
|
||||
|
||||
```
|
||||
|
||||
#### Boosting Query
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
let posQuery = QueryMatchQuery $
|
||||
mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
|
||||
let negQuery = QueryMatchQuery $
|
||||
mkMatchQuery (FieldName "user") (QueryString "notmyapp")
|
||||
let query = QueryBoostingQuery $
|
||||
BoostingQuery posQuery negQuery (Boost 0.2)
|
||||
|
||||
```
|
||||
|
||||
#### Rest of the query/filter types
|
||||
|
||||
Just follow the pattern you've seen here and check the Hackage API documentation.
|
||||
|
||||
### Sorting
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
let sortSpec = DefaultSortSpec $ mkSort (FieldName "age") Ascending
|
||||
|
||||
-- mkSort is a shortcut function that takes a FieldName and a SortOrder
|
||||
-- to generate a vanilla DefaultSort.
|
||||
-- checkt the DefaultSort type for the full list of customizable options.
|
||||
|
||||
-- From and size are integers for pagination.
|
||||
|
||||
-- When sorting on a field, scores are not computed. By setting TrackSortScores to true, scores will still be computed and tracked.
|
||||
|
||||
-- type Sort = [SortSpec]
|
||||
-- type TrackSortScores = Bool
|
||||
-- type From = Int
|
||||
-- type Size = Int
|
||||
|
||||
-- Search takes Maybe Query
|
||||
-- -> Maybe Filter
|
||||
-- -> Maybe Sort
|
||||
-- -> TrackSortScores
|
||||
-- -> From -> Size
|
||||
-- -> Maybe [FieldName]
|
||||
|
||||
-- just add more sortspecs to the list if you want tie-breakers.
|
||||
let search = Search Nothing (Just IdentityFilter) (Just [sortSpec]) False (From 0) (Size 10) Nothing
|
||||
|
||||
```
|
||||
|
||||
### Field selection
|
||||
|
||||
If you only want certain fields from the source document returned, you can
|
||||
set the "fields" field of the Search record.
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
let search' = mkSearch (Just (MatchAllQuery Nothing)) Nothing
|
||||
search = search' { fields = Just [FieldName "updated"] }
|
||||
|
||||
```
|
||||
|
||||
### Filtering
|
||||
|
||||
#### And, Not, and Or filters
|
||||
|
||||
Filters form a monoid and seminearring.
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
instance Monoid Filter where
|
||||
mempty = IdentityFilter
|
||||
mappend a b = AndFilter [a, b] defaultCache
|
||||
|
||||
instance Seminearring Filter where
|
||||
a <||> b = OrFilter [a, b] defaultCache
|
||||
|
||||
-- AndFilter and OrFilter take [Filter] as an argument.
|
||||
|
||||
-- This will return anything, because IdentityFilter returns everything
|
||||
OrFilter [IdentityFilter, someOtherFilter] False
|
||||
|
||||
-- This will return exactly what someOtherFilter returns
|
||||
AndFilter [IdentityFilter, someOtherFilter] False
|
||||
|
||||
-- Thanks to the seminearring and monoid, the above can be expressed as:
|
||||
|
||||
-- "and"
|
||||
IdentityFilter <&&> someOtherFilter
|
||||
|
||||
-- "or"
|
||||
IdentityFilter <||> someOtherFilter
|
||||
|
||||
-- Also there is a NotFilter, it only accepts a single filter, not a list.
|
||||
|
||||
NotFilter someOtherFilter False
|
||||
|
||||
```
|
||||
|
||||
#### Identity Filter
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
-- And'ing two Identity
|
||||
let queryFilter = IdentityFilter <&&> IdentityFilter
|
||||
|
||||
let search = mkSearch Nothing (Just queryFilter)
|
||||
|
||||
reply <- withBH' $ searchByType testIndex testMapping search
|
||||
|
||||
```
|
||||
|
||||
#### Boolean Filter
|
||||
|
||||
Similar to boolean queries.
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
-- Will return only items whose "user" field contains the term "bitemyapp"
|
||||
let queryFilter = BoolFilter (MustMatch (Term "user" "bitemyapp") False)
|
||||
|
||||
-- Will return only items whose "user" field does not contain the term "bitemyapp"
|
||||
let queryFilter = BoolFilter (MustNotMatch (Term "user" "bitemyapp") False)
|
||||
|
||||
-- The clause (query) should appear in the matching document.
|
||||
-- In a boolean query with no must clauses, one or more should
|
||||
-- clauses must match a document. The minimum number of should
|
||||
-- clauses to match can be set using the minimum_should_match parameter.
|
||||
let queryFilter = BoolFilter (ShouldMatch [(Term "user" "bitemyapp")] False)
|
||||
|
||||
```
|
||||
|
||||
#### Exists Filter
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
-- Will filter for documents that have the field "user"
|
||||
let existsFilter = ExistsFilter (FieldName "user")
|
||||
|
||||
```
|
||||
|
||||
#### Geo BoundingBox Filter
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
-- topLeft and bottomRight
|
||||
let box = GeoBoundingBox (LatLon 40.73 (-74.1)) (LatLon 40.10 (-71.12))
|
||||
|
||||
let constraint = GeoBoundingBoxConstraint (FieldName "tweet.location") box False GeoFilterMemory
|
||||
|
||||
```
|
||||
|
||||
#### Geo Distance Filter
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
|
||||
|
||||
-- coefficient and units
|
||||
let distance = Distance 10.0 Miles
|
||||
|
||||
-- GeoFilterType or NoOptimizeBbox
|
||||
let optimizeBbox = OptimizeGeoFilterType GeoFilterMemory
|
||||
|
||||
-- SloppyArc is the usual/default optimization in Elasticsearch today
|
||||
-- but pre-1.0 versions will need to pick Arc or Plane.
|
||||
|
||||
let geoFilter = GeoDistanceFilter geoPoint distance SloppyArc optimizeBbox False
|
||||
|
||||
```
|
||||
|
||||
#### Geo Distance Range Filter
|
||||
|
||||
Think of a donut and you won't be far off.
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
let geoPoint = GeoPoint (FieldName "tweet.location") (LatLon 40.12 (-71.34))
|
||||
|
||||
let distanceRange = DistanceRange (Distance 0.0 Miles) (Distance 10.0 Miles)
|
||||
|
||||
let geoFilter = GeoDistanceRangeFilter geoPoint distanceRange
|
||||
|
||||
```
|
||||
|
||||
#### Geo Polygon Filter
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
-- I think I drew a square here.
|
||||
let points = [LatLon 40.0 (-70.00),
|
||||
LatLon 40.0 (-72.00),
|
||||
LatLon 41.0 (-70.00),
|
||||
LatLon 41.0 (-72.00)]
|
||||
|
||||
let geoFilter = GeoPolygonFilter (FieldName "tweet.location") points
|
||||
|
||||
```
|
||||
|
||||
#### Document IDs filter
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
-- takes a mapping name and a list of DocIds
|
||||
IdsFilter (MappingName "tweet") [DocId "1"]
|
||||
|
||||
```
|
||||
|
||||
#### Range Filter
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
-- RangeFilter :: FieldName
|
||||
-- -> RangeValue
|
||||
-- -> RangeExecution
|
||||
-- -> Cache -> Filter
|
||||
|
||||
let filter = RangeFilter (FieldName "age")
|
||||
(RangeGtLt (GreaterThan 1000.0) (LessThan 100000.0))
|
||||
RangeExecutionIndex False
|
||||
|
||||
```
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
let filter = RangeFilter (FieldName "age")
|
||||
(RangeLte (LessThanEq 100000.0))
|
||||
RangeExecutionIndex False
|
||||
|
||||
```
|
||||
|
||||
##### Date Ranges
|
||||
|
||||
Date ranges are expressed in UTCTime. Date ranges use the same range bound constructors as numerics, except that they end in "D".
|
||||
|
||||
Note that compatibility with ES is tested only down to seconds.
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
let filter = RangeFilter (FieldName "postDate")
|
||||
(RangeDateGtLte
|
||||
(GreaterThanD (UTCTime
|
||||
(ModifiedJulianDay 55000)
|
||||
(secondsToDiffTime 9)))
|
||||
(LessThanEqD (UTCTime
|
||||
(ModifiedJulianDay 55000)
|
||||
(secondsToDiffTime 11))))
|
||||
RangeExecutionIndex False
|
||||
```
|
||||
|
||||
#### Regexp Filter
|
||||
|
||||
``` {.haskell}
|
||||
|
||||
-- RegexpFilter
|
||||
-- :: FieldName
|
||||
-- -> Regexp
|
||||
-- -> RegexpFlags
|
||||
-- -> CacheName
|
||||
-- -> Cache
|
||||
-- -> CacheKey
|
||||
-- -> Filter
|
||||
let filter = RegexpFilter (FieldName "user") (Regexp "bite.*app")
|
||||
AllRegexpFlags (CacheName "test") False (CacheKey "key")
|
||||
|
||||
-- n.b.
|
||||
-- data RegexpFlags = AllRegexpFlags
|
||||
-- | NoRegexpFlags
|
||||
-- | SomeRegexpFlags (NonEmpty RegexpFlag) deriving (Eq, Show)
|
||||
|
||||
-- data RegexpFlag = AnyString
|
||||
-- | Automaton
|
||||
-- | Complement
|
||||
-- | Empty
|
||||
-- | Intersection
|
||||
-- | Interval deriving (Eq, Show)
|
||||
|
||||
```
|
||||
|
||||
### Aggregations
|
||||
|
||||
#### Adding aggregations to search
|
||||
|
||||
Aggregations can now be added to search queries, or made on their own.
|
||||
|
||||
``` {.haskell}
|
||||
type Aggregations = M.Map Text Aggregation
|
||||
data Aggregation
|
||||
= TermsAgg TermsAggregation
|
||||
| DateHistogramAgg DateHistogramAggregation
|
||||
```
|
||||
|
||||
For convenience, \`\`\`mkAggregations\`\`\` exists, that will create an \`\`\`Aggregations\`\`\` with the aggregation provided.
|
||||
|
||||
For example:
|
||||
|
||||
``` {.haskell}
|
||||
let a = mkAggregations "users" $ TermsAgg $ mkTermsAggregation "user"
|
||||
let search = mkAggregateSearch Nothing a
|
||||
```
|
||||
|
||||
Aggregations can be added to an existing search, using the \`\`\`aggBody\`\`\` field
|
||||
|
||||
``` {.haskell}
|
||||
let search = mkSearch (Just (MatchAllQuery Nothing)) Nothing
|
||||
let search' = search {aggBody = Just a}
|
||||
```
|
||||
|
||||
Since the \`\`\`Aggregations\`\`\` structure is just a Map Text Aggregation, M.insert can be used to add additional aggregations.
|
||||
|
||||
``` {.haskell}
|
||||
let a' = M.insert "age" (TermsAgg $ mkTermsAggregation "age") a
|
||||
```
|
||||
|
||||
#### Extracting aggregations from results
|
||||
|
||||
Aggregations are part of the reply structure of every search, in the
|
||||
form of `Maybe AggregationResults`
|
||||
|
||||
``` {.haskell}
|
||||
-- Lift decode and response body to be in the IO monad.
|
||||
let decode' = liftM decode
|
||||
let responseBody' = liftM responseBody
|
||||
let reply = withBH' $ searchByIndex testIndex search
|
||||
let response = decode' $ responseBody' reply :: IO (Maybe (SearchResult Tweet))
|
||||
|
||||
-- Now that we have our response, we can extract our terms aggregation result -- which is a list of buckets.
|
||||
|
||||
let terms = do { response' <- response; return $ response' >>= aggregations >>= toTerms "users" }
|
||||
terms
|
||||
Just (Bucket {buckets = [TermsResult {termKey = "bitemyapp", termsDocCount = 1, termsAggs = Nothing}]})
|
||||
```
|
||||
|
||||
Note that bucket aggregation results, such as the TermsResult is a member of the type class `BucketAggregation`:
|
||||
|
||||
``` {.haskell}
|
||||
class BucketAggregation a where
|
||||
key :: a -> Text
|
||||
docCount :: a -> Int
|
||||
aggs :: a -> Maybe AggregationResults
|
||||
```
|
||||
|
||||
You can use the `aggs` 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 `termresult` , you would call `aggs
|
||||
termresult >>= toTerms "age"`
|
||||
|
||||
#### Terms Aggregation
|
||||
|
||||
``` {.haskell}
|
||||
data TermsAggregation
|
||||
= TermsAggregation {term :: Either Text Text,
|
||||
termInclude :: Maybe TermInclusion,
|
||||
termExclude :: Maybe TermInclusion,
|
||||
termOrder :: Maybe TermOrder,
|
||||
termMinDocCount :: Maybe Int,
|
||||
termSize :: Maybe Int,
|
||||
termShardSize :: Maybe Int,
|
||||
termCollectMode :: Maybe CollectionMode,
|
||||
termExecutionHint :: Maybe ExecutionHint,
|
||||
termAggs :: Maybe Aggregations}
|
||||
```
|
||||
|
||||
Term Aggregations have two factory functions, `mkTermsAggregation`, and
|
||||
`mkTermsScriptAggregation`, and can be used as follows:
|
||||
|
||||
``` {.haskell}
|
||||
let ta = TermsAgg $ mkTermsAggregation "user"
|
||||
```
|
||||
|
||||
There are of course other options that can be added to a Terms Aggregation, such as the collection mode:
|
||||
|
||||
``` {.haskell}
|
||||
let ta = mkTermsAggregation "user"
|
||||
let ta' = ta { termCollectMode = Just BreadthFirst }
|
||||
let ta'' = TermsAgg ta'
|
||||
```
|
||||
|
||||
For more documentation on how the Terms Aggregation works, see <https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html>
|
||||
|
||||
#### Date Histogram Aggregation
|
||||
|
||||
``` {.haskell}
|
||||
data DateHistogramAggregation
|
||||
= DateHistogramAggregation {dateField :: FieldName,
|
||||
dateInterval :: Interval,
|
||||
dateFormat :: Maybe Text,
|
||||
datePreZone :: Maybe Text,
|
||||
datePostZone :: Maybe Text,
|
||||
datePreOffset :: Maybe Text,
|
||||
datePostOffset :: Maybe Text,
|
||||
dateAggs :: Maybe Aggregations}
|
||||
```
|
||||
|
||||
The Date Histogram Aggregation works much the same as the Terms Aggregation.
|
||||
|
||||
Relevant functions include `mkDateHistogram`, and `toDateHistogram`
|
||||
|
||||
``` {.haskell}
|
||||
let dh = DateHistogramAgg (mkDateHistogram (FieldName "postDate") Minute)
|
||||
```
|
||||
|
||||
Date histograms also accept a `FractionalInterval`:
|
||||
|
||||
``` {.haskell}
|
||||
FractionalInterval :: Float -> TimeInterval -> Interval
|
||||
-- TimeInterval is the following:
|
||||
data TimeInterval = Weeks | Days | Hours | Minutes | Seconds
|
||||
```
|
||||
|
||||
It can be used as follows:
|
||||
|
||||
``` {.haskell}
|
||||
let dh = DateHistogramAgg (mkDateHistogram (FieldName "postDate") (FractionalInterval 1.5 Minutes))
|
||||
```
|
||||
|
||||
The `DateHistogramResult` is defined as:
|
||||
|
||||
``` {.haskell}
|
||||
data DateHistogramResult
|
||||
= DateHistogramResult {dateKey :: Int,
|
||||
dateKeyStr :: Maybe Text,
|
||||
dateDocCount :: Int,
|
||||
dateHistogramAggs :: Maybe AggregationResults}
|
||||
```
|
||||
|
||||
It is an instance of `BucketAggregation`, and can have nested aggregations in each bucket.
|
||||
|
||||
Buckets can be extracted from an `AggregationResult` using
|
||||
`toDateHistogram name`
|
||||
|
||||
For more information on the Date Histogram Aggregation, see: <https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-datehistogram-aggregation.html>
|
||||
See the [examples](htts://github.com/bitemyapp/bloodhound/tree/master/examples) directory for example code.
|
||||
|
||||
|
||||
Contributors
|
||||
|
@ -95,23 +95,3 @@ test-suite tests
|
||||
unix-compat,
|
||||
network-uri
|
||||
default-language: Haskell2010
|
||||
|
||||
test-suite doctests
|
||||
ghc-options: -threaded -Wall
|
||||
default-language: Haskell2010
|
||||
type: exitcode-stdio-1.0
|
||||
main-is: doctests.hs
|
||||
hs-source-dirs: src
|
||||
if flag(ES5)
|
||||
hs-source-dirs: tests/V5
|
||||
else
|
||||
hs-source-dirs: tests/V1
|
||||
if impl(ghc >= 7.8)
|
||||
build-depends: base,
|
||||
aeson,
|
||||
bloodhound,
|
||||
directory,
|
||||
doctest >= 0.10.1,
|
||||
filepath
|
||||
else
|
||||
buildable: False
|
||||
|
12
examples/LICENSE
Normal file
12
examples/LICENSE
Normal file
@ -0,0 +1,12 @@
|
||||
Copyright (c) 2014, Chris Allen
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
3
examples/README.md
Normal file
3
examples/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Bloodhound Examples
|
||||
|
||||
These examples can be build via `stack build`.
|
2
examples/Setup.hs
Normal file
2
examples/Setup.hs
Normal file
@ -0,0 +1,2 @@
|
||||
import Distribution.Simple
|
||||
main = defaultMain
|
125
examples/Tweet.hs
Normal file
125
examples/Tweet.hs
Normal file
@ -0,0 +1,125 @@
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
module Main
|
||||
( main
|
||||
) where
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import Data.Aeson (FromJSON (..), defaultOptions,
|
||||
genericParseJSON, genericToJSON,
|
||||
object, (.=))
|
||||
import Data.List.NonEmpty (NonEmpty (..))
|
||||
import Data.Text (Text)
|
||||
import Data.Time.Calendar (Day (..))
|
||||
import Data.Time.Clock (UTCTime (..), secondsToDiffTime)
|
||||
import qualified Data.Vector as V
|
||||
import Database.V1.Bloodhound
|
||||
import GHC.Generics (Generic)
|
||||
import Network.HTTP.Client (defaultManagerSettings)
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
data TweetMapping = TweetMapping deriving (Eq, Show)
|
||||
|
||||
instance ToJSON TweetMapping where
|
||||
toJSON TweetMapping =
|
||||
object
|
||||
[ "properties" .=
|
||||
object ["location" .= object ["type" .= ("geo_point" :: Text)]]
|
||||
]
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
data Location = Location
|
||||
{ locLat :: Double
|
||||
, locLon :: Double
|
||||
} deriving (Eq, Generic, Show)
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
data Tweet = Tweet
|
||||
{ user :: Text
|
||||
, postDate :: UTCTime
|
||||
, message :: Text
|
||||
, age :: Int
|
||||
, location :: Location
|
||||
} deriving (Eq, Generic, Show)
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
exampleTweet :: Tweet
|
||||
exampleTweet =
|
||||
Tweet
|
||||
{ user = "bitemyapp"
|
||||
, postDate = UTCTime (ModifiedJulianDay 55000) (secondsToDiffTime 10)
|
||||
, message = "Use haskell!"
|
||||
, age = 10000
|
||||
, location = loc
|
||||
}
|
||||
where
|
||||
loc = Location {locLat = 40.12, locLon = (-71.34)}
|
||||
|
||||
instance ToJSON Tweet where
|
||||
toJSON = genericToJSON defaultOptions
|
||||
instance FromJSON Tweet where
|
||||
parseJSON = genericParseJSON defaultOptions
|
||||
instance ToJSON Location where
|
||||
toJSON = genericToJSON defaultOptions
|
||||
instance FromJSON Location where
|
||||
parseJSON = genericParseJSON defaultOptions
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
main :: IO ()
|
||||
main = runBH' $ do
|
||||
-- set up index
|
||||
_ <- createIndex indexSettings testIndex
|
||||
True <- indexExists testIndex
|
||||
_ <- putMapping testIndex testMapping TweetMapping
|
||||
|
||||
-- create a tweet
|
||||
resp <- indexDocument testIndex testMapping defaultIndexDocumentSettings exampleTweet (DocId "1")
|
||||
liftIO (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}
|
||||
|
||||
-- bulk load
|
||||
let stream = V.fromList [BulkIndex testIndex testMapping (DocId "2") (toJSON exampleTweet)]
|
||||
_ <- bulk stream
|
||||
-- Bulk loads require an index refresh before new data is loaded.
|
||||
_ <- refreshIndex testIndex
|
||||
|
||||
-- set up some aliases
|
||||
let aliasName = IndexName "twitter-alias"
|
||||
let iAlias = IndexAlias testIndex (IndexAliasName aliasName)
|
||||
let aliasRouting = Nothing
|
||||
let aliasFiltering = Nothing
|
||||
let aliasCreate = IndexAliasCreate aliasRouting aliasFiltering
|
||||
_ <- updateIndexAliases (AddAlias iAlias aliasCreate :| [])
|
||||
True <- indexExists aliasName
|
||||
|
||||
-- create a template so that if we just write into an index named tweet-2017-01-02, for instance, the index will be automatically created with the given mapping. This is a great idea for any ongoing indices because it makes them much easier to manage and rotate.
|
||||
let idxTpl = IndexTemplate (TemplatePattern "tweet-*") (Just (IndexSettings (ShardCount 1) (ReplicaCount 1))) [toJSON TweetMapping]
|
||||
let templateName = TemplateName "tweet-tpl"
|
||||
_ <- putTemplate idxTpl templateName
|
||||
True <- templateExists templateName
|
||||
|
||||
-- do a search
|
||||
let boost = Nothing
|
||||
let query = TermQuery (Term "user" "bitemyapp") boost
|
||||
let search = mkSearch (Just query) boost
|
||||
_ <- searchByType testIndex testMapping search
|
||||
|
||||
-- clean up
|
||||
_ <- deleteTemplate templateName
|
||||
_ <- deleteIndex testIndex
|
||||
False <- indexExists testIndex
|
||||
|
||||
return ()
|
||||
where
|
||||
testServer = (Server "http://localhost:9200")
|
||||
runBH' = withBH defaultManagerSettings testServer
|
||||
testIndex = IndexName "twitter"
|
||||
testMapping = MappingName "tweet"
|
||||
indexSettings = IndexSettings (ShardCount 1) (ReplicaCount 0)
|
36
examples/package.yaml
Normal file
36
examples/package.yaml
Normal file
@ -0,0 +1,36 @@
|
||||
name: bloodhound-examples
|
||||
version: '0.0.0.0'
|
||||
category: Web
|
||||
author: Chris Allen
|
||||
maintainer: cma@bitemyapp.com
|
||||
copyright: 2017, Chris Allen
|
||||
license: BSD3
|
||||
github: bitemyapp/bloodhound
|
||||
extra-source-files:
|
||||
- README.md
|
||||
dependencies:
|
||||
- base
|
||||
- bloodhound
|
||||
- time
|
||||
- aeson
|
||||
- text
|
||||
- http-client
|
||||
- vector
|
||||
ghc-options:
|
||||
- -Wall
|
||||
- -threaded
|
||||
- -rtsopts
|
||||
- -with-rtsopts=-N
|
||||
when:
|
||||
- condition: flag(werror)
|
||||
ghc-options: -Werror
|
||||
|
||||
executables:
|
||||
tweet-example:
|
||||
main: Tweet.hs
|
||||
|
||||
flags:
|
||||
werror:
|
||||
description: "Treat warnings as errors"
|
||||
manual: true
|
||||
default: false
|
6
examples/src/Lib.hs
Normal file
6
examples/src/Lib.hs
Normal file
@ -0,0 +1,6 @@
|
||||
module Lib
|
||||
( someFunc
|
||||
) where
|
||||
|
||||
someFunc :: IO ()
|
||||
someFunc = putStrLn "someFunc"
|
9
examples/stack.yaml
Normal file
9
examples/stack.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
resolver: lts-7.19
|
||||
packages:
|
||||
- '.'
|
||||
- '../'
|
||||
extra-deps: []
|
||||
flags:
|
||||
bloodhound-examples:
|
||||
werror: true
|
||||
extra-package-dbs: []
|
@ -123,60 +123,8 @@ import Prelude hiding (filter, head)
|
||||
|
||||
import Database.V1.Bloodhound.Types
|
||||
|
||||
-- $setup
|
||||
-- >>> :set -XOverloadedStrings
|
||||
-- >>> :set -XDeriveGeneric
|
||||
-- >>> import Database.V1.Bloodhound
|
||||
-- >>> let testServer = (Server "http://localhost:9200")
|
||||
-- >>> let runBH' = withBH defaultManagerSettings testServer
|
||||
-- >>> let testIndex = IndexName "twitter"
|
||||
-- >>> let testMapping = MappingName "tweet"
|
||||
-- >>> let defaultIndexSettings = IndexSettings (ShardCount 1) (ReplicaCount 0)
|
||||
-- >>> data TweetMapping = TweetMapping deriving (Eq, Show)
|
||||
-- >>> _ <- runBH' $ deleteIndex testIndex >> deleteMapping testIndex testMapping
|
||||
-- >>> import GHC.Generics
|
||||
-- >>> import Data.Time.Calendar (Day (..))
|
||||
-- >>> import Data.Time.Clock (UTCTime (..), secondsToDiffTime)
|
||||
-- >>> :{
|
||||
--instance ToJSON TweetMapping where
|
||||
-- toJSON TweetMapping =
|
||||
-- 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 where
|
||||
-- toJSON = genericToJSON defaultOptions
|
||||
--instance FromJSON Tweet where
|
||||
-- parseJSON = genericParseJSON defaultOptions
|
||||
--instance ToJSON Location where
|
||||
-- toJSON = genericToJSON defaultOptions
|
||||
--instance FromJSON Location where
|
||||
-- parseJSON = genericParseJSON defaultOptions
|
||||
--data BulkTest = BulkTest { name :: Text } deriving (Eq, Generic, Show)
|
||||
--instance FromJSON BulkTest where
|
||||
-- parseJSON = genericParseJSON defaultOptions
|
||||
--instance ToJSON BulkTest where
|
||||
-- toJSON = genericToJSON defaultOptions
|
||||
-- :}
|
||||
|
||||
-- | 'mkShardCount' is a straight-forward smart constructor for 'ShardCount'
|
||||
-- which rejects 'Int' values below 1 and above 1000.
|
||||
--
|
||||
-- >>> mkShardCount 10
|
||||
-- Just (ShardCount 10)
|
||||
mkShardCount :: Int -> Maybe ShardCount
|
||||
mkShardCount n
|
||||
| n < 1 = Nothing
|
||||
@ -185,9 +133,6 @@ mkShardCount n
|
||||
|
||||
-- | 'mkReplicaCount' is a straight-forward smart constructor for 'ReplicaCount'
|
||||
-- which rejects 'Int' values below 0 and above 1000.
|
||||
--
|
||||
-- >>> mkReplicaCount 10
|
||||
-- Just (ReplicaCount 10)
|
||||
mkReplicaCount :: Int -> Maybe ReplicaCount
|
||||
mkReplicaCount n
|
||||
| n < 0 = Nothing
|
||||
@ -271,10 +216,6 @@ post = dispatch NHTM.methodPost
|
||||
-- https://github.com/supki/libjenkins/blob/master/src/Jenkins/Rest/Internal.hs
|
||||
|
||||
-- | 'getStatus' fetches the 'Status' of a 'Server'
|
||||
--
|
||||
-- >>> serverStatus <- runBH' getStatus
|
||||
-- >>> fmap status (serverStatus)
|
||||
-- Just 200
|
||||
getStatus :: MonadBH m => m (Maybe Status)
|
||||
getStatus = do
|
||||
response <- get =<< url
|
||||
@ -494,12 +435,6 @@ getNodesStats sel = parseEsResponse =<< get =<< url
|
||||
selToSeg (NodeByAttribute (NodeAttrName a) v) = a <> ":" <> v
|
||||
|
||||
-- | 'createIndex' will create an index given a 'Server', 'IndexSettings', and an 'IndexName'.
|
||||
--
|
||||
-- >>> response <- runBH' $ createIndex defaultIndexSettings (IndexName "didimakeanindex")
|
||||
-- >>> respIsTwoHunna response
|
||||
-- True
|
||||
-- >>> runBH' $ indexExists (IndexName "didimakeanindex")
|
||||
-- True
|
||||
createIndex :: MonadBH m => IndexSettings -> IndexName -> m Reply
|
||||
createIndex indexSettings (IndexName indexName) =
|
||||
bindM2 put url (return body)
|
||||
@ -508,23 +443,11 @@ createIndex indexSettings (IndexName indexName) =
|
||||
|
||||
|
||||
-- | 'deleteIndex' will delete an index given a 'Server', and an 'IndexName'.
|
||||
--
|
||||
-- >>> _ <- runBH' $ createIndex defaultIndexSettings (IndexName "didimakeanindex")
|
||||
-- >>> response <- runBH' $ deleteIndex (IndexName "didimakeanindex")
|
||||
-- >>> respIsTwoHunna response
|
||||
-- True
|
||||
-- >>> runBH' $ indexExists testIndex
|
||||
-- False
|
||||
deleteIndex :: MonadBH m => IndexName -> m Reply
|
||||
deleteIndex (IndexName indexName) =
|
||||
delete =<< joinPath [indexName]
|
||||
|
||||
-- | 'updateIndexSettings' will apply a non-empty list of setting updates to an index
|
||||
--
|
||||
-- >>> _ <- runBH' $ createIndex defaultIndexSettings (IndexName "unconfiguredindex")
|
||||
-- >>> response <- runBH' $ updateIndexSettings (BlocksWrite False :| []) (IndexName "unconfiguredindex")
|
||||
-- >>> respIsTwoHunna response
|
||||
-- True
|
||||
updateIndexSettings :: MonadBH m => NonEmpty UpdatableIndexSetting -> IndexName -> m Reply
|
||||
updateIndexSettings updates (IndexName indexName) =
|
||||
bindM2 put url (return body)
|
||||
@ -557,12 +480,6 @@ getIndexSettings (IndexName indexName) = do
|
||||
-- almost completely identical forcemerge API. Adding support to that
|
||||
-- API would be trivial but due to the significant breaking changes,
|
||||
-- this library cannot currently be used with >= 2.0, so that feature was omitted.
|
||||
--
|
||||
-- >>> let ixn = IndexName "unoptimizedindex"
|
||||
-- >>> _ <- runBH' $ deleteIndex ixn >> createIndex defaultIndexSettings ixn
|
||||
-- >>> response <- runBH' $ optimizeIndex (IndexList (ixn :| [])) (defaultIndexOptimizationSettings { maxNumSegments = Just 1, onlyExpungeDeletes = True })
|
||||
-- >>> respIsTwoHunna response
|
||||
-- True
|
||||
optimizeIndex :: MonadBH m => IndexSelection -> IndexOptimizationSettings -> m Reply
|
||||
optimizeIndex ixs IndexOptimizationSettings {..} =
|
||||
bindM2 post url (return body)
|
||||
@ -617,8 +534,6 @@ parseEsResponse reply
|
||||
|
||||
-- | 'indexExists' enables you to check if an index exists. Returns 'Bool'
|
||||
-- in IO
|
||||
--
|
||||
-- >>> exists <- runBH' $ indexExists testIndex
|
||||
indexExists :: MonadBH m => IndexName -> m Bool
|
||||
indexExists (IndexName indexName) = do
|
||||
(_, exists) <- existentialQuery =<< joinPath [indexName]
|
||||
@ -626,9 +541,6 @@ indexExists (IndexName indexName) = do
|
||||
|
||||
-- | 'refreshIndex' will force a refresh on an index. You must
|
||||
-- do this if you want to read what you wrote.
|
||||
--
|
||||
-- >>> _ <- runBH' $ createIndex defaultIndexSettings testIndex
|
||||
-- >>> _ <- runBH' $ refreshIndex testIndex
|
||||
refreshIndex :: MonadBH m => IndexName -> m Reply
|
||||
refreshIndex (IndexName indexName) =
|
||||
bindM2 post url (return Nothing)
|
||||
@ -655,15 +567,11 @@ openOrCloseIndexes oci (IndexName indexName) =
|
||||
|
||||
-- | '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 <- runBH' $ openIndex testIndex
|
||||
openIndex :: MonadBH m => IndexName -> m Reply
|
||||
openIndex = openOrCloseIndexes OpenIndex
|
||||
|
||||
-- | '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 <- runBH' $ closeIndex testIndex
|
||||
closeIndex :: MonadBH m => IndexName -> m Reply
|
||||
closeIndex = openOrCloseIndexes CloseIndex
|
||||
|
||||
@ -684,20 +592,6 @@ listIndices =
|
||||
-- | 'updateIndexAliases' updates the server's index alias
|
||||
-- table. Operations are atomic. Explained in further detail at
|
||||
-- <https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html>
|
||||
--
|
||||
-- >>> let src = IndexName "a-real-index"
|
||||
-- >>> let aliasName = IndexName "an-alias"
|
||||
-- >>> let iAlias = IndexAlias src (IndexAliasName aliasName)
|
||||
-- >>> let aliasCreate = IndexAliasCreate Nothing Nothing
|
||||
-- >>> _ <- runBH' $ deleteIndex src
|
||||
-- >>> respIsTwoHunna <$> runBH' (createIndex defaultIndexSettings src)
|
||||
-- True
|
||||
-- >>> runBH' $ indexExists src
|
||||
-- True
|
||||
-- >>> respIsTwoHunna <$> runBH' (updateIndexAliases (AddAlias iAlias aliasCreate :| []))
|
||||
-- True
|
||||
-- >>> runBH' $ indexExists aliasName
|
||||
-- True
|
||||
updateIndexAliases :: MonadBH m => NonEmpty IndexAliasAction -> m Reply
|
||||
updateIndexAliases actions = bindM2 post url (return body)
|
||||
where url = joinPath ["_aliases"]
|
||||
@ -713,9 +607,6 @@ getIndexAliases = parseEsResponse =<< get =<< url
|
||||
-- | 'putTemplate' creates a template given an 'IndexTemplate' and a 'TemplateName'.
|
||||
-- Explained in further detail at
|
||||
-- <https://www.elastic.co/guide/en/elasticsearch/reference/1.7/indices-templates.html>
|
||||
--
|
||||
-- >>> let idxTpl = IndexTemplate (TemplatePattern "tweet-*") (Just (IndexSettings (ShardCount 1) (ReplicaCount 1))) [toJSON TweetMapping]
|
||||
-- >>> resp <- runBH' $ putTemplate idxTpl (TemplateName "tweet-tpl")
|
||||
putTemplate :: MonadBH m => IndexTemplate -> TemplateName -> m Reply
|
||||
putTemplate indexTemplate (TemplateName templateName) =
|
||||
bindM2 put url (return body)
|
||||
@ -723,29 +614,18 @@ putTemplate indexTemplate (TemplateName templateName) =
|
||||
body = Just $ encode indexTemplate
|
||||
|
||||
-- | 'templateExists' checks to see if a template exists.
|
||||
--
|
||||
-- >>> exists <- runBH' $ templateExists (TemplateName "tweet-tpl")
|
||||
templateExists :: MonadBH m => TemplateName -> m Bool
|
||||
templateExists (TemplateName templateName) = do
|
||||
(_, exists) <- existentialQuery =<< joinPath ["_template", templateName]
|
||||
return exists
|
||||
|
||||
-- | 'deleteTemplate' is an HTTP DELETE and deletes a template.
|
||||
--
|
||||
-- >>> let idxTpl = IndexTemplate (TemplatePattern "tweet-*") (Just (IndexSettings (ShardCount 1) (ReplicaCount 1))) [toJSON TweetMapping]
|
||||
-- >>> _ <- runBH' $ putTemplate idxTpl (TemplateName "tweet-tpl")
|
||||
-- >>> resp <- runBH' $ deleteTemplate (TemplateName "tweet-tpl")
|
||||
deleteTemplate :: MonadBH m => TemplateName -> m Reply
|
||||
deleteTemplate (TemplateName templateName) =
|
||||
delete =<< joinPath ["_template", templateName]
|
||||
|
||||
-- | 'putMapping' is an HTTP PUT and has upsert semantics. Mappings are schemas
|
||||
-- for documents in indexes.
|
||||
--
|
||||
-- >>> _ <- runBH' $ createIndex defaultIndexSettings testIndex
|
||||
-- >>> resp <- runBH' $ putMapping 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}
|
||||
putMapping :: (MonadBH m, ToJSON a) => IndexName
|
||||
-> MappingName -> a -> m Reply
|
||||
putMapping (IndexName indexName) (MappingName mappingName) mapping =
|
||||
@ -757,12 +637,6 @@ putMapping (IndexName indexName) (MappingName mappingName) mapping =
|
||||
|
||||
-- | 'deleteMapping' is an HTTP DELETE and deletes a mapping for a given index.
|
||||
-- Mappings are schemas for documents in indexes.
|
||||
--
|
||||
-- >>> _ <- runBH' $ createIndex defaultIndexSettings testIndex
|
||||
-- >>> _ <- runBH' $ putMapping testIndex testMapping TweetMapping
|
||||
-- >>> resp <- runBH' $ deleteMapping 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}
|
||||
deleteMapping :: MonadBH m => IndexName -> MappingName -> m Reply
|
||||
deleteMapping (IndexName indexName)
|
||||
(MappingName mappingName) =
|
||||
@ -788,10 +662,6 @@ versionCtlParams cfg =
|
||||
-- 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 <- runBH' $ indexDocument testIndex testMapping defaultIndexDocumentSettings 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}
|
||||
indexDocument :: (ToJSON doc, MonadBH m) => IndexName -> MappingName
|
||||
-> IndexDocumentSettings -> doc -> DocId -> m Reply
|
||||
indexDocument (IndexName indexName)
|
||||
@ -816,8 +686,6 @@ updateDocument (IndexName indexName)
|
||||
body = Just (encode $ object ["doc" .= toJSON patch])
|
||||
|
||||
-- | 'deleteDocument' is the primary way to delete a single document.
|
||||
--
|
||||
-- >>> _ <- runBH' $ deleteDocument testIndex testMapping (DocId "1")
|
||||
deleteDocument :: MonadBH m => IndexName -> MappingName
|
||||
-> DocId -> m Reply
|
||||
deleteDocument (IndexName indexName)
|
||||
@ -830,10 +698,6 @@ deleteDocument (IndexName indexName)
|
||||
-- index\/update\/delete\/create operations. You pass a 'V.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"))]
|
||||
-- >>> _ <- runBH' $ bulk stream
|
||||
-- >>> _ <- runBH' $ refreshIndex testIndex
|
||||
bulk :: MonadBH m => V.Vector BulkOperation -> m Reply
|
||||
bulk bulkOps = bindM2 post url (return body)
|
||||
where url = joinPath ["_bulk"]
|
||||
@ -841,10 +705,6 @@ bulk bulkOps = bindM2 post url (return body)
|
||||
|
||||
-- | 'encodeBulkOperations' is a convenience function for dumping a vector of 'BulkOperation'
|
||||
-- into an 'L.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"
|
||||
encodeBulkOperations :: V.Vector BulkOperation -> L.ByteString
|
||||
encodeBulkOperations stream = collapsed where
|
||||
blobs = fmap encodeBulkOperation stream
|
||||
@ -863,10 +723,6 @@ mkBulkStreamValue operation indexName mappingName docId =
|
||||
|
||||
-- | 'encodeBulkOperation' is a convenience function for dumping a single 'BulkOperation'
|
||||
-- into an 'L.ByteString'
|
||||
--
|
||||
-- >>> let bulkOp = BulkIndex testIndex testMapping (DocId "2") (toJSON (BulkTest "blah"))
|
||||
-- >>> encodeBulkOperation bulkOp
|
||||
-- "{\"index\":{\"_type\":\"tweet\",\"_id\":\"2\",\"_index\":\"twitter\"}}\n{\"name\":\"blah\"}"
|
||||
encodeBulkOperation :: BulkOperation -> L.ByteString
|
||||
encodeBulkOperation (BulkIndex (IndexName indexName)
|
||||
(MappingName mappingName)
|
||||
@ -896,8 +752,6 @@ encodeBulkOperation (BulkUpdate (IndexName indexName)
|
||||
-- | '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 <- runBH' $ getDocument testIndex testMapping (DocId "1")
|
||||
getDocument :: MonadBH m => IndexName -> MappingName
|
||||
-> DocId -> m Reply
|
||||
getDocument (IndexName indexName)
|
||||
@ -906,8 +760,6 @@ getDocument (IndexName indexName)
|
||||
|
||||
-- | 'documentExists' enables you to check if a document exists. Returns 'Bool'
|
||||
-- in IO
|
||||
--
|
||||
-- >>> exists <- runBH' $ documentExists testIndex testMapping Nothing (DocId "1")
|
||||
documentExists :: MonadBH m => IndexName -> MappingName
|
||||
-> Maybe DocumentParent -> DocId -> m Bool
|
||||
documentExists (IndexName indexName) (MappingName mappingName)
|
||||
@ -924,30 +776,18 @@ dispatchSearch url search = post url' (Just (encode search))
|
||||
|
||||
-- | 'searchAll', given a 'Search', will perform that search against all indexes
|
||||
-- on an Elasticsearch server. Try to avoid doing this if it can be helped.
|
||||
--
|
||||
-- >>> let query = TermQuery (Term "user" "bitemyapp") Nothing
|
||||
-- >>> let search = mkSearch (Just query) Nothing
|
||||
-- >>> reply <- runBH' $ searchAll search
|
||||
searchAll :: MonadBH m => Search -> m Reply
|
||||
searchAll = bindM2 dispatchSearch url . return
|
||||
where url = joinPath ["_search"]
|
||||
|
||||
-- | '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 <- runBH' $ searchByIndex testIndex search
|
||||
searchByIndex :: MonadBH m => IndexName -> Search -> m Reply
|
||||
searchByIndex (IndexName indexName) = bindM2 dispatchSearch url . return
|
||||
where url = joinPath [indexName, "_search"]
|
||||
|
||||
-- | '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 <- runBH' $ searchByType testIndex testMapping search
|
||||
searchByType :: MonadBH m => IndexName -> MappingName -> Search
|
||||
-> m Reply
|
||||
searchByType (IndexName indexName)
|
||||
@ -1020,29 +860,16 @@ scanSearch indexName mappingName search = do
|
||||
-- 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 = From 0, size = Size 10, searchType = SearchTypeQueryThenFetch, fields = Nothing, source = Nothing}
|
||||
mkSearch :: Maybe Query -> Maybe Filter -> Search
|
||||
mkSearch query filter = Search query filter Nothing Nothing Nothing False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing
|
||||
|
||||
-- | '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
|
||||
mkAggregateSearch :: Maybe Query -> Aggregations -> Search
|
||||
mkAggregateSearch query mkSearchAggs = Search query Nothing Nothing (Just mkSearchAggs) Nothing False (From 0) (Size 0) SearchTypeQueryThenFetch Nothing Nothing
|
||||
|
||||
-- | '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
|
||||
mkHighlightSearch :: Maybe Query -> Highlights -> Search
|
||||
mkHighlightSearch query searchHighlights = Search query Nothing Nothing Nothing (Just searchHighlights) False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing
|
||||
|
||||
@ -1050,13 +877,6 @@ mkHighlightSearch query searchHighlights = Search query Nothing Nothing Nothing
|
||||
-- and size fields for the search. The from parameter defines the offset
|
||||
-- from the first result you want to fetch. The size parameter allows you to
|
||||
-- configure the maximum amount of hits to be returned.
|
||||
--
|
||||
-- >>> 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, matchQueryBoost = Nothing})), filterBody = Nothing, sortBody = Nothing, aggBody = Nothing, highlight = Nothing, trackSortScores = False, from = From 0, size = Size 10, searchType = SearchTypeQueryThenFetch, fields = Nothing, source = Nothing}
|
||||
-- >>> pageSearch (From 10) (Size 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, matchQueryBoost = Nothing})), filterBody = Nothing, sortBody = Nothing, aggBody = Nothing, highlight = Nothing, trackSortScores = False, from = From 10, size = Size 100, searchType = SearchTypeQueryThenFetch, fields = Nothing, source = Nothing}
|
||||
pageSearch :: From -- ^ The result offset
|
||||
-> Size -- ^ The number of results to return
|
||||
-> Search -- ^ The current seach
|
||||
|
@ -7,7 +7,6 @@ extra-deps:
|
||||
- fail-4.9.0.0
|
||||
- http-types-0.9
|
||||
- attoparsec-0.13.0.1
|
||||
- doctest-0.10.1
|
||||
- quickcheck-properties-0.1
|
||||
- semigroups-0.18.0.1
|
||||
- uri-bytestring-0.1.9
|
||||
|
@ -9,7 +9,6 @@ extra-deps:
|
||||
- http-types-0.9
|
||||
- http-client-0.5.0
|
||||
- attoparsec-0.13.0.1
|
||||
- doctest-0.10.1
|
||||
- quickcheck-properties-0.1
|
||||
- semigroups-0.18.0.1
|
||||
- tagged-0.8.3
|
||||
|
@ -1,4 +0,0 @@
|
||||
import Test.DocTest
|
||||
|
||||
main :: IO ()
|
||||
main = doctest ["-i src", "Database.V1.Bloodhound"]
|
@ -1,4 +0,0 @@
|
||||
import Test.DocTest
|
||||
|
||||
main :: IO ()
|
||||
main = doctest ["-i src", "Database.V5.Bloodhound"]
|
Loading…
Reference in New Issue
Block a user