git.iko.soy/typeable/bloodhound
on master help sign in
cleanup, passing test for match queries
commit069704ac602b96cba6030dd13c6e3be90aad4524 authorChris Allen <cma@bitemyapp.com> dateSun 27 Apr 2014, 00:49 (12 years ago) parent8bd69f4
8 files changed+366−167
.gitignore+2 Database/Bloodhound.hs+2−77 Database/Bloodhound/Types.hs+246−58 Database/Bloodhound/Types/Instances.hs+73−21 Makefile+1−1 README.org+8 bloodhound.cabal+5 tests/tests.hs+29−10
.gitignore+2
@@ -1,3 +1,5 @@
11cabal.sandbox.config
22.cabal-sandbox/
33dist/
4+.project
5+.dist-buildwrapper/
Database/Bloodhound.hs+2−77
@@ -1,81 +1,6 @@
11module Database.Bloodhound
2− ( createIndex
3− , deleteIndex
4− , defaultIndexSettings
5− , createMapping
6− , deleteMapping
7− , indexDocument
8− , getDocument
9− , documentExists
10− , deleteDocument
11− , searchAll
12− , searchByIndex
13− , searchByType
14− , refreshIndex
15− , mkSearch
16− , bulk
17− , defaultCache
18− , halfRangeToKV
19− , maybeJson
20− , maybeJsonF
21− , rangeToKV
22− , showText
23− , unpackId
24− , mkSort
25− , Version(..)
26− , Status(..)
27− , Existence(..)
28− , NullValue(..)
29− , IndexSettings(..)
30− , Server(..)
31− , Reply(..)
32− , EsResult(..)
33− , Query(..)
34− , Search(..)
35− , SearchResult(..)
36− , SearchHits(..)
37− , ShardResult(..)
38− , Hit(..)
39− , Filter(..)
40− , Seminearring(..)
41− , BoolMatch(..)
42− , Term(..)
43− , GeoPoint(..)
44− , GeoBoundingBoxConstraint(..)
45− , GeoBoundingBox(..)
46− , GeoFilterType(..)
47− , Distance(..)
48− , DistanceUnit(..)
49− , DistanceType(..)
50− , DistanceRange(..)
51− , OptimizeBbox(..)
52− , LatLon(..)
53− , Range(..)
54− , HalfRange(..)
55− , RangeExecution(..)
56− , LessThan(..)
57− , LessThanEq(..)
58− , GreaterThan(..)
59− , GreaterThanEq(..)
60− , Regexp(..)
61− , RegexpFlags(..)
62− , FieldName(..)
63− , IndexName(..)
64− , MappingName(..)
65− , DocId(..)
66− , CacheName(..)
67− , CacheKey(..)
68− , BulkOperation(..)
69− , ReplicaCount(..)
70− , ShardCount(..)
71− , Sort(..)
72− , SortMode(..)
73− , SortOrder(..)
74− , SortSpec(..)
75− , DefaultSort(..)
76− , Missing(..)
77− , OpenCloseIndex(..)
78− , Method(..)
2+ ( module Database.Bloodhound.Client
3+ , module Database.Bloodhound.Types
794 ) where
805
816import Database.Bloodhound.Client
Database/Bloodhound/Types.hs+246−58
@@ -10,6 +10,7 @@ module Database.Bloodhound.Types
1010 , rangeToKV
1111 , showText
1212 , unpackId
13+ , mkMatchQuery
1314 , Version(..)
1415 , Status(..)
1516 , Existence(..)
@@ -64,6 +65,36 @@ module Database.Bloodhound.Types
6465 , Missing(..)
6566 , OpenCloseIndex(..)
6667 , Method(..)
68+ , Boost(..)
69+ , MatchQuery(..)
70+ , MultiMatchQuery(..)
71+ , BoolQuery(..)
72+ , BoostingQuery(..)
73+ , CommonTermsQuery(..)
74+ , DisMaxQuery(..)
75+ , FilteredQuery(..)
76+ , FuzzyLikeThisQuery(..)
77+ , FuzzyLikeFieldQuery(..)
78+ , FuzzyQuery(..)
79+ , HasChildQuery(..)
80+ , HasParentQuery(..)
81+ , AnIndicesQuery(..)
82+ , MoreLikeThisQuery(..)
83+ , MoreLikeThisFieldQuery(..)
84+ , NestedQuery(..)
85+ , PrefixQuery(..)
86+ , QueryStringQuery(..)
87+ , SimpleQueryStringQuery(..)
88+ , RangeQuery(..)
89+ , RegexpQuery(..)
90+ , QueryString(..)
91+ , BooleanOperator(..)
92+ , ZeroTermsQuery(..)
93+ , CutoffFrequency(..)
94+ , Analyzer(..)
95+ , MaxExpansions(..)
96+ , Lenient(..)
97+ , MatchQueryType(..)
6798 ) where
6899
69100import Data.Aeson
@@ -82,15 +113,11 @@ data Version = Version { number :: Text
82113 , build_snapshot :: Bool
83114 , lucene_version :: Text } deriving (Show, Generic)
84115
85−data (FromJSON a, ToJSON a) => Status a =
86− Status { ok :: Bool
87− , status :: Int
88− , name :: Text
89− , version :: a
90− , tagline :: Text } deriving (Eq, Show)
91−
92−newtype ShardCount = ShardCount Int deriving (Eq, Show, Generic)
93−newtype ReplicaCount = ReplicaCount Int deriving (Eq, Show, Generic)
116+data Status a = Status { ok :: Bool
117+ , status :: Int
118+ , name :: Text
119+ , version :: a
120+ , tagline :: Text } deriving (Eq, Show)
94121
95122data IndexSettings =
96123 IndexSettings { indexShards :: ShardCount
@@ -100,7 +127,6 @@ defaultIndexSettings = IndexSettings (ShardCount 3) (ReplicaCount 2)
100127
101128data Strategy = RoundRobinStrat | RandomStrat | HeadStrat deriving (Eq, Show)
102129
103−newtype Server = Server String deriving (Eq, Show)
104130
105131type Reply = Network.HTTP.Conduit.Response L.ByteString
106132type Method = NHTM.Method
@@ -120,9 +146,9 @@ data FieldDefinition =
120146
121147data MappingField =
122148 MappingField { mappingFieldName :: FieldName
123− , fieldDefinition :: FieldDefinition } deriving (Eq, Show)
149+ , fieldDefinition :: FieldDefinition } deriving (Eq, Show)
124150
125−data Mapping = Mapping { typeName :: Text
151+data Mapping = Mapping { typeName :: TypeName
126152 , fields :: [MappingField] } deriving (Eq, Show)
127153
128154data BulkOperation =
@@ -174,28 +200,48 @@ type PrefixValue = Text
174200
175201data BooleanOperator = And | Or deriving (Eq, Show)
176202
177−newtype IndexName = IndexName String deriving (Eq, Generic, Show)
178−newtype MappingName = MappingName String deriving (Eq, Generic, Show)
179−newtype DocId = DocId String deriving (Eq, Generic, Show)
180−newtype QueryString = QueryString Text deriving (Eq, Show)
181−newtype FieldName = FieldName Text deriving (Eq, Show)
182−newtype CacheName = CacheName Text deriving (Eq, Show)
183−newtype CacheKey = CacheKey Text deriving (Eq, Show)
184−newtype Existence = Existence Bool deriving (Eq, Show)
185−newtype NullValue = NullValue Bool deriving (Eq, Show)
186−newtype CutoffFrequency = CutoffFrequency Double deriving (Eq, Show)
187−newtype Analyzer = Analyzer Text deriving (Eq, Show)
188−newtype MaxExpansions = MaxExpansions Int deriving (Eq, Show)
189−newtype Lenient = Lenient Bool deriving (Eq, Show)
190−newtype Tiebreaker = Tiebreaker Double deriving (Eq, Show)
191−newtype Boost = Boost Double deriving (Eq, Show)
192−newtype MinimumMatch = MinimumMatch Int deriving (Eq, Show)
193−newtype MinimumMatchText = MinimumMatchText Text deriving (Eq, Show)
194−newtype DisableCoord = DisableCoord Bool deriving (Eq, Show)
195−newtype IgnoreTermFrequency = IgnoreTermFrequency Bool deriving (Eq, Show)
196−newtype MaxQueryTerms = MaxQueryTerms Int deriving (Eq, Show)
197−newtype Fuzziness = Fuzziness Double deriving (Eq, Show)
198−newtype PrefixLength = PrefixLength Int deriving (Eq, Show)
203+newtype ShardCount = ShardCount Int deriving (Eq, Show, Generic)
204+newtype ReplicaCount = ReplicaCount Int deriving (Eq, Show, Generic)
205+newtype Server = Server String deriving (Eq, Show)
206+newtype IndexName = IndexName String deriving (Eq, Generic, Show)
207+newtype MappingName = MappingName String deriving (Eq, Generic, Show)
208+newtype DocId = DocId String deriving (Eq, Generic, Show)
209+newtype QueryString = QueryString Text deriving (Eq, Show)
210+newtype FieldName = FieldName Text deriving (Eq, Show)
211+newtype CacheName = CacheName Text deriving (Eq, Show)
212+newtype CacheKey = CacheKey Text deriving (Eq, Show)
213+newtype Existence = Existence Bool deriving (Eq, Show)
214+newtype NullValue = NullValue Bool deriving (Eq, Show)
215+newtype CutoffFrequency = CutoffFrequency Double deriving (Eq, Show, Generic)
216+newtype Analyzer = Analyzer Text deriving (Eq, Show, Generic)
217+newtype MaxExpansions = MaxExpansions Int deriving (Eq, Show, Generic)
218+newtype Lenient = Lenient Bool deriving (Eq, Show, Generic)
219+newtype Tiebreaker = Tiebreaker Double deriving (Eq, Show)
220+newtype Boost = Boost Double deriving (Eq, Show, Generic)
221+newtype BoostTerms = BoostTerms Double deriving (Eq, Show)
222+newtype MinimumMatch = MinimumMatch Int deriving (Eq, Show)
223+newtype MinimumMatchText = MinimumMatchText Text deriving (Eq, Show)
224+newtype DisableCoord = DisableCoord Bool deriving (Eq, Show)
225+newtype IgnoreTermFrequency = IgnoreTermFrequency Bool deriving (Eq, Show)
226+newtype MinimumTermFrequency = MinimumTermFrequency Int deriving (Eq, Show)
227+newtype MaxQueryTerms = MaxQueryTerms Int deriving (Eq, Show)
228+newtype Fuzziness = Fuzziness Double deriving (Eq, Show)
229+newtype PrefixLength = PrefixLength Int deriving (Eq, Show)
230+newtype TypeName = TypeName Text deriving (Eq, Show)
231+newtype PercentMatch = PercentMatch Double deriving (Eq, Show)
232+newtype StopWord = StopWord Text deriving (Eq, Show)
233+newtype QueryPath = QueryPath Text deriving (Eq, Show)
234+newtype AllowLeadingWildcard = AllowLeadingWildcard Bool deriving (Eq, Show)
235+newtype LowercaseExpanded = LowercaseExpanded Bool deriving (Eq, Show)
236+newtype EnablePositionIncrements = EnablePositionIncrements Bool deriving (Eq, Show)
237+newtype AnalyzeWildcard = AnalyzeWildcard Bool deriving (Eq, Show)
238+newtype GeneratePhraseQueries = GeneratePhraseQueries Bool deriving (Eq, Show)
239+newtype Locale = Locale Text deriving (Eq, Show)
240+newtype MaxWordLength = MaxWordLength Int deriving (Eq, Show)
241+newtype MinWordLength = MinWordLength Int deriving (Eq, Show)
242+newtype PhraseSlop = PhraseSlop Int deriving (Eq, Show)
243+newtype MinDocFrequency = MinDocFrequency Int deriving (Eq, Show)
244+newtype MaxDocFrequency = MaxDocFrequency Int deriving (Eq, Show)
199245
200246unpackId :: DocId -> String
201247unpackId (DocId docId) = docId
@@ -212,21 +258,173 @@ data Search = Search { queryBody :: Maybe Query
212258 , from :: From
213259 , size :: Size } deriving (Eq, Show)
214260
215−data Query = TermQuery Term (Maybe Boost)
216− | QueryMatchQuery MatchQuery
217− | QueryMultiMatchQuery MultiMatchQuery
218− | QueryBoolQuery BoolQuery
219− | QueryBoostingQuery BoostingQuery
220− | QueryCommonTermsQuery CommonTermsQuery
221− | ConstantScoreFilter Filter Boost
222− | ConstantScoreQuery Query Boost
223− | QueryDisMaxQuery DisMaxQuery
224− | QueryFilteredQuery FilteredQuery
225− | QueryFuzzyLikeThisQuery FuzzyLikeThisQuery
226− | QueryFuzzyLikeFieldQuery FuzzyLikeFieldQuery
227− | QueryFuzzyQuery FuzzyQuery
261+data Query = TermQuery Term (Maybe Boost)
262+ | TermsQuery [Term] MinimumMatch
263+ | QueryMatchQuery MatchQuery
264+ | QueryMultiMatchQuery MultiMatchQuery
265+ | QueryBoolQuery BoolQuery
266+ | QueryBoostingQuery BoostingQuery
267+ | QueryCommonTermsQuery CommonTermsQuery
268+ | ConstantScoreFilter Filter Boost
269+ | ConstantScoreQuery Query Boost
270+ | QueryDisMaxQuery DisMaxQuery
271+ | QueryFilteredQuery FilteredQuery
272+ | QueryFuzzyLikeThisQuery FuzzyLikeThisQuery
273+ | QueryFuzzyLikeFieldQuery FuzzyLikeFieldQuery
274+ | QueryFuzzyQuery FuzzyQuery
275+ | QueryHasChildQuery HasChildQuery
276+ | QueryHasParentQuery HasParentQuery
277+ | IdsQuery MappingName [DocId]
278+ | QueryIndicesQuery AnIndicesQuery
279+ | MatchAllQuery (Maybe Boost)
280+ | QueryMoreLikeThisQuery MoreLikeThisQuery
281+ | QueryMoreLikeThisFieldQuery MoreLikeThisFieldQuery
282+ | QueryNestedQuery NestedQuery
283+ | QueryPrefixQuery PrefixQuery
284+ | QueryQueryStringQuery QueryStringQuery
285+ | QuerySimpleQueryStringQuery SimpleQueryStringQuery
286+ | QueryRangeQuery RangeQuery
287+ | QueryRegexpQuery RegexpQuery
228288 deriving (Eq, Show)
229289
290+-- Possible flags are ALL, ANYSTRING, AUTOMATON, COMPLEMENT,
291+-- EMPTY, INTERSECTION, INTERVAL, or NONE. huh?
292+
293+data RegexpQuery =
294+ RegexpQuery { regexpQueryField :: FieldName
295+ , regexpQuery :: Regexp
296+ , regexpQueryFlags :: RegexpFlags
297+ , regexpQueryCacheName :: CacheName
298+ , regexpQueryCache :: Cache
299+ , regexpQueryCacheKey :: CacheKey } deriving (Eq, Show)
300+
301+data RangeQuery =
302+ RangeQuery { rangeQueryField :: FieldName
303+ , rangeQueryRange :: Either HalfRange Range
304+ , rangeQueryBoost :: Boost } deriving (Eq, Show)
305+
306+data SimpleQueryStringQuery =
307+ SimpleQueryStringQuery { simpleQueryStringQuery :: QueryString
308+ , simpleQueryStringField :: Maybe FieldOrFields
309+ , simpleQueryStringOperator :: Maybe BooleanOperator
310+ , simpleQueryStringAnalyzer :: Maybe Analyzer
311+ , simpleQueryStringFlags :: Maybe [SimpleQueryFlag]
312+ , simpleQueryStringLowercaseExpanded :: Maybe LowercaseExpanded
313+ , simpleQueryStringLocale :: Maybe Locale
314+ } deriving (Eq, Show)
315+
316+data SimpleQueryFlag = SimpleQueryAll
317+ | SimpleQueryNone
318+ | SimpleQueryAnd
319+ | SimpleQueryOr
320+ | SimpleQueryPrefix
321+ | SimpleQueryPhrase
322+ | SimpleQueryPrecedence
323+ | SimpleQueryEscape
324+ | SimpleQueryWhitespace
325+ | SimpleQueryFuzzy
326+ | SimpleQueryNear
327+ | SimpleQuerySlop deriving (Eq, Show)
328+
329+-- use_dis_max and tie_breaker when fields are plural?
330+data QueryStringQuery =
331+ QueryStringQuery { queryStringQuery :: QueryString
332+ , queryStringDefaultField :: Maybe FieldOrFields
333+ , queryStringOperator :: Maybe BooleanOperator
334+ , queryStringAnalyzer :: Maybe Analyzer
335+ , queryStringAllowLeadingWildcard :: Maybe AllowLeadingWildcard
336+ , queryStringLowercaseExpanded :: Maybe LowercaseExpanded
337+ , queryStringEnablePositionIncrements :: Maybe EnablePositionIncrements
338+ , queryStringFuzzyMaxExpansions :: Maybe MaxExpansions
339+ , queryStringFuzziness :: Maybe Fuzziness
340+ , queryStringFuzzyPrefixLength :: Maybe PrefixLength
341+ , queryStringPhraseSlop :: Maybe PhraseSlop
342+ , queryStringBoost :: Maybe Boost
343+ , queryStringAnalyzeWildcard :: Maybe AnalyzeWildcard
344+ , queryStringGeneratePhraseQueries :: Maybe GeneratePhraseQueries
345+ , queryStringMinimumShouldMatch :: Maybe MinimumMatch
346+ , queryStringLenient :: Maybe Lenient
347+ , queryStringLocale :: Maybe Locale
348+ } deriving (Eq, Show)
349+
350+data FieldOrFields = FofField FieldName
351+ | FofFields [FieldName] deriving (Eq, Show)
352+
353+data PrefixQuery =
354+ PrefixQuery { prefixQueryField :: FieldName
355+ , prefixQueryPrefixValue :: Text
356+ , prefixQueryBoost :: Maybe Boost } deriving (Eq, Show)
357+
358+data NestedQuery =
359+ NestedQuery { nestedQueryPath :: QueryPath
360+ , nestedQueryScoreType :: ScoreType
361+ , nestedQuery :: Query } deriving (Eq, Show)
362+
363+data MoreLikeThisFieldQuery =
364+ MoreLikeThisFieldQuery { moreLikeThisFieldText :: Text
365+ , moreLikeThisFieldFields :: FieldName
366+ -- default 0.3 (30%)
367+ , moreLikeThisFieldPercentMatch :: Maybe PercentMatch
368+ , moreLikeThisFieldMinimumTermFreq :: Maybe MinimumTermFrequency
369+ , moreLikeThisFieldMaxQueryTerms :: Maybe MaxQueryTerms
370+ , moreLikeThisFieldStopWords :: Maybe [StopWord]
371+ , moreLikeThisFieldMinDocFrequency :: Maybe MinDocFrequency
372+ , moreLikeThisFieldMaxDocFrequency :: Maybe MaxDocFrequency
373+ , moreLikeThisFieldMinWordLength :: Maybe MinWordLength
374+ , moreLikeThisFieldMaxWordLength :: Maybe MaxWordLength
375+ , moreLikeThisFieldBoostTerms :: Maybe BoostTerms
376+ , moreLikeThisFieldBoost :: Maybe Boost
377+ , moreLikeThisFieldAnalyzer :: Maybe Analyzer
378+ } deriving (Eq, Show)
379+
380+data MoreLikeThisQuery =
381+ MoreLikeThisQuery { moreLikeThisText :: Text
382+ , moreLikeThisFields :: Maybe [FieldName]
383+ -- default 0.3 (30%)
384+ , moreLikeThisPercentMatch :: Maybe PercentMatch
385+ , moreLikeThisMinimumTermFreq :: Maybe MinimumTermFrequency
386+ , moreLikeThisMaxQueryTerms :: Maybe MaxQueryTerms
387+ , moreLikeThisStopWords :: Maybe [StopWord]
388+ , moreLikeThisMinDocFrequency :: Maybe MinDocFrequency
389+ , moreLikeThisMaxDocFrequency :: Maybe MaxDocFrequency
390+ , moreLikeThisMinWordLength :: Maybe MinWordLength
391+ , moreLikeThisMaxWordLength :: Maybe MaxWordLength
392+ , moreLikeThisBoostTerms :: Maybe BoostTerms
393+ , moreLikeThisBoost :: Maybe Boost
394+ , moreLikeThisAnalyzer :: Maybe Analyzer
395+ } deriving (Eq, Show)
396+
397+data AnIndicesQuery = AnIndexQuery IndexQuery
398+ | AnIndicesQuery IndicesQuery deriving (Eq, Show)
399+
400+
401+data IndicesQuery =
402+ IndicesQuery { indicesQueryIndexName :: IndexName
403+ , indicesQuery :: Query
404+ -- default "all"
405+ , indicesQueryNoMatch :: Maybe Query } deriving (Eq, Show)
406+
407+data IndexQuery =
408+ IndexQuery { indexQueryIndexName :: IndexName
409+ , indexQuery :: Query
410+ -- default "all"
411+ , indexQueryNoMatch :: Maybe Query } deriving (Eq, Show)
412+
413+data HasParentQuery =
414+ HasParentQuery { hasParentQueryType :: TypeName
415+ , hasParentQuery :: Query
416+ , hasParentQueryScoreType :: Maybe ScoreType } deriving (Eq, Show)
417+
418+data HasChildQuery =
419+ HasChildQuery { hasChildQueryType :: TypeName
420+ , hasChildQuery :: Query
421+ , hasChildQueryScoreType :: Maybe ScoreType } deriving (Eq, Show)
422+
423+data ScoreType = ScoreTypeMax
424+ | ScoreTypeSum
425+ | ScoreTypeAvg
426+ | ScoreTypeNone deriving (Eq, Show)
427+
230428data FuzzyQuery = FuzzyQuery { fuzzyQueryField :: FieldName
231429 , fuzzyQueryValue :: Text
232430 , fuzzyQueryPrefixLength :: PrefixLength
@@ -373,17 +571,7 @@ data RangeExecution = RangeExecutionIndex
373571 | RangeExecutionFielddata deriving (Eq, Show)
374572
375573newtype Regexp = Regexp Text deriving (Eq, Show)
376−data RegexpFlags = RegexpAll
377− | Complement
378− | Interval
379− | Intersection
380− | AnyString
381− | CompInterval
382− | CompIntersection
383− | CompAnyString
384− | IntervalIntersection
385− | IntervalAnyString
386− | IntersectionAnyString deriving (Eq, Show)
574+newtype RegexpFlags = RegexpFlags Text deriving (Eq, Show)
387575
388576halfRangeToKV :: HalfRange -> (Text, Double)
389577halfRangeToKV (HalfRangeLt (LessThan n)) = ("lt", n)
Database/Bloodhound/Types/Instances.hs+73−21
@@ -1,3 +1,5 @@
1+{-# LANGUAGE DeriveGeneric #-}
2+
13module Database.Bloodhound.Types.Instances
24 ( Monoid(..)
35 , Seminearring(..)
@@ -6,6 +8,7 @@ module Database.Bloodhound.Types.Instances
68
79import Control.Applicative
810import Data.Aeson
11+import Data.Maybe (catMaybes)
912import Data.Monoid
1013import qualified Data.Text as T
1114import Database.Bloodhound.Types
@@ -119,6 +122,7 @@ instance ToJSON GeoPoint where
119122 toJSON (GeoPoint (FieldName geoField) latLon) =
120123 object [geoField .= toJSON latLon]
121124
125+
122126instance ToJSON Query where
123127 toJSON (TermQuery (Term termField termValue) boost) =
124128 object ["term" .=
@@ -130,10 +134,55 @@ instance ToJSON Query where
130134 Nothing -> []
131135 merged = mappend base boosted
132136
137+ toJSON (QueryMatchQuery matchQuery) =
138+ object ["match" .= toJSON matchQuery]
139+
140+
141+instance ToJSON MatchQuery where
142+ toJSON (MatchQuery (FieldName fieldName)
143+ (QueryString queryString) booleanOperator
144+ zeroTermsQuery cutoffFrequency matchQueryType
145+ analyzer maxExpansions lenient) =
146+ object [ fieldName .= object conjoined ]
147+ where conjoined = [ "query" .= queryString
148+ , "operator" .= toJSON booleanOperator
149+ , "zero_terms_query" .= toJSON zeroTermsQuery]
150+ ++ maybeAdd
151+ f field = fmap ((field .=) . toJSON)
152+ maybeAdd = catMaybes [ f "cutoff_frequency" cutoffFrequency
153+ , f "type" matchQueryType
154+ , f "analyzer" analyzer
155+ , f "max_expansions" maxExpansions
156+ , f "lenient" lenient ]
157+
158+
159+instance ToJSON BooleanOperator where
160+ toJSON And = String "and"
161+ toJSON Or = String "or"
162+
163+instance ToJSON ZeroTermsQuery where
164+ toJSON ZeroTermsNone = String "none"
165+ toJSON ZeroTermsAll = String "all"
166+
167+instance ToJSON MatchQueryType where
168+ toJSON MatchPhrase = "phrase"
169+ toJSON MatchPhrasePrefix = "phrase_prefix"
170+
171+instance ToJSON ReplicaCount
172+instance ToJSON ShardCount
173+instance ToJSON CutoffFrequency
174+instance ToJSON Analyzer
175+instance ToJSON MaxExpansions
176+instance ToJSON Lenient
177+instance ToJSON Boost
133178instance ToJSON Version
134179instance FromJSON Version
180+instance FromJSON IndexName
181+instance FromJSON MappingName
182+instance FromJSON DocId
183+
135184
136−instance (FromJSON a, ToJSON a) => FromJSON (Status a) where
185+instance (FromJSON a) => FromJSON (Status a) where
137186 parseJSON (Object v) = Status <$>
138187 v .: "ok" <*>
139188 v .: "status" <*>
@@ -142,13 +191,12 @@ instance (FromJSON a, ToJSON a) => FromJSON (Status a) where
142191 v .: "tagline"
143192 parseJSON _ = empty
144193
194+
145195instance ToJSON IndexSettings where
146196 toJSON (IndexSettings s r) = object ["settings" .= object ["shards" .= s, "replicas" .= r]]
147197
148−instance ToJSON ReplicaCount
149−instance ToJSON ShardCount
150198
151−instance (FromJSON a, ToJSON a) => FromJSON (EsResult a) where
199+instance (FromJSON a) => FromJSON (EsResult a) where
152200 parseJSON (Object v) = EsResult <$>
153201 v .: "_index" <*>
154202 v .: "_type" <*>
@@ -158,6 +206,7 @@ instance (FromJSON a, ToJSON a) => FromJSON (EsResult a) where
158206 v .: "_source"
159207 parseJSON _ = empty
160208
209+
161210instance ToJSON Search where
162211 toJSON (Search query filter sort trackSortScores from size) =
163212 object merged where
@@ -171,6 +220,7 @@ instance ToJSON Search where
171220 , lFilter
172221 , lSort]
173222
223+
174224instance ToJSON SortSpec where
175225 toJSON (DefaultSortSpec
176226 (DefaultSort (FieldName sortFieldName) sortOrder ignoreUnmapped
@@ -183,24 +233,24 @@ instance ToJSON SortSpec where
183233 lNestedFilter = maybeJson "nested_filter" nestedFilter
184234 merged = mconcat [base, lSortMode, lMissingSort, lNestedFilter]
185235
236+
186237instance ToJSON SortOrder where
187238 toJSON Ascending = String "asc"
188239 toJSON Descending = String "desc"
189240
241+
190242instance ToJSON SortMode where
191243 toJSON SortMin = String "min"
192244 toJSON SortMax = String "max"
193245 toJSON SortSum = String "sum"
194246 toJSON SortAvg = String "avg"
195247
248+
196249instance ToJSON Missing where
197250 toJSON LastMissing = String "_last"
198251 toJSON FirstMissing = String "_first"
199252 toJSON (CustomMissing txt) = String txt
200253
201−instance FromJSON IndexName
202−instance FromJSON MappingName
203−instance FromJSON DocId
204254
205255instance ToJSON Distance where
206256 toJSON (Distance coefficient unit) =
@@ -209,6 +259,7 @@ instance ToJSON Distance where
209259 (String unitText) = (toJSON unit)
210260 boltedTogether = mappend coefText unitText
211261
262+
212263instance ToJSON DistanceUnit where
213264 toJSON Miles = String "mi"
214265 toJSON Yards = String "yd"
@@ -220,56 +271,56 @@ instance ToJSON DistanceUnit where
220271 toJSON Millimeters = String "mm"
221272 toJSON NauticalMiles = String "nmi"
222273
274+
223275instance ToJSON DistanceType where
224276 toJSON Arc = String "arc"
225277 toJSON SloppyArc = String "sloppy_arc"
226278 toJSON Plane = String "plane"
227279
280+
228281instance ToJSON OptimizeBbox where
229282 toJSON NoOptimizeBbox = String "none"
230283 toJSON (OptimizeGeoFilterType gft) = toJSON gft
231284
285+
232286instance ToJSON GeoBoundingBoxConstraint where
233287 toJSON (GeoBoundingBoxConstraint (FieldName geoBBField) constraintBox cache) =
234288 object [geoBBField .= toJSON constraintBox
235289 , "_cache" .= cache]
236290
291+
237292instance ToJSON GeoFilterType where
238293 toJSON GeoFilterMemory = String "memory"
239294 toJSON GeoFilterIndexed = String "indexed"
240295
296+
241297instance ToJSON GeoBoundingBox where
242298 toJSON (GeoBoundingBox topLeft bottomRight) =
243299 object ["top_left" .= toJSON topLeft
244300 , "bottom_right" .= toJSON bottomRight]
245301
302+
246303instance ToJSON LatLon where
247304 toJSON (LatLon lat lon) =
248305 object ["lat" .= lat
249306 , "lon" .= lon]
250307
308+
251309-- index for smaller ranges, fielddata for longer ranges
252310instance ToJSON RangeExecution where
253311 toJSON RangeExecutionIndex = "index"
254312 toJSON RangeExecutionFielddata = "fielddata"
255313
314+
256315instance ToJSON RegexpFlags where
257− toJSON RegexpAll = String "ALL"
258− toJSON Complement = String "COMPLEMENT"
259− toJSON Interval = String "INTERVAL"
260− toJSON Intersection = String "INTERSECTION"
261− toJSON AnyString = String "ANYSTRING"
262− toJSON CompInterval = String "COMPLEMENT|INTERVAL"
263− toJSON CompIntersection = String "COMPLEMENT|INTERSECTION"
264− toJSON CompAnyString = String "COMPLEMENT|ANYSTRING"
265− toJSON IntervalIntersection = String "INTERVAL|INTERSECTION"
266− toJSON IntervalAnyString = String "INTERVAL|ANYSTRING"
267− toJSON IntersectionAnyString = String "INTERSECTION|ANYSTRING"
316+ toJSON (RegexpFlags txt) = String txt
317+
268318
269319instance ToJSON Term where
270320 toJSON (Term field value) = object ["term" .= object
271321 [field .= value]]
272322
323+
273324instance ToJSON BoolMatch where
274325 toJSON (MustMatch term cache) = object ["must" .= toJSON term,
275326 "_cache" .= cache]
@@ -278,7 +329,8 @@ instance ToJSON BoolMatch where
278329 toJSON (ShouldMatch terms cache) = object ["should" .= fmap toJSON terms,
279330 "_cache" .= cache]
280331
281−instance (FromJSON a, ToJSON a) => FromJSON (SearchResult a) where
332+
333+instance (FromJSON a) => FromJSON (SearchResult a) where
282334 parseJSON (Object v) = SearchResult <$>
283335 v .: "took" <*>
284336 v .: "timed_out" <*>
@@ -286,14 +338,14 @@ instance (FromJSON a, ToJSON a) => FromJSON (SearchResult a) where
286338 v .: "hits"
287339 parseJSON _ = empty
288340
289−instance (FromJSON a, ToJSON a) => FromJSON (SearchHits a) where
341+instance (FromJSON a) => FromJSON (SearchHits a) where
290342 parseJSON (Object v) = SearchHits <$>
291343 v .: "total" <*>
292344 v .: "max_score" <*>
293345 v .: "hits"
294346 parseJSON _ = empty
295347
296−instance (FromJSON a, ToJSON a) => FromJSON (Hit a) where
348+instance (FromJSON a) => FromJSON (Hit a) where
297349 parseJSON (Object v) = Hit <$>
298350 v .: "_index" <*>
299351 v .: "_type" <*>
Makefile+1−1
@@ -1,6 +1,6 @@
11
22test:
3− cabal install --enable-tests
3+ cabal test
44
55build:
66 cabal build
README.org+8
@@ -509,6 +509,10 @@ let filter = RegexpFilter (FieldName "user") (Regexp "bite.*app")
509509
510510* Possible future functionality
511511
512+** Span Queries
513+
514+Beginning here: http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-span-first-query.html
515+
512516** Function Score Query
513517
514518http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html
@@ -525,6 +529,10 @@ Pretend to be a transport client?
525529
526530Might require making a lucene index on disk with the appropriate format.
527531
532+** GeoShapeQuery
533+
534+http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-geo-shape-query.html
535+
528536** GeoShapeFilter
529537
530538http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-geo-shape-filter.html
bloodhound.cabal+5
@@ -16,6 +16,7 @@ build-type: Simple
1616cabal-version: >=1.10
1717
1818library
19+ ghc-options: -Wall
1920 default-extensions: OverloadedStrings
2021 exposed-modules: Database.Bloodhound
2122 Database.Bloodhound.Client
@@ -28,6 +29,7 @@ library
2829 free >= 4.5,
2930 lens >= 4,
3031 aeson >= 0.7,
32+ aeson-pretty >= 0.7,
3133 conduit >= 1.0,
3234 http-conduit >= 2.0,
3335 io-streams >= 1.1,
@@ -42,6 +44,7 @@ library
4244 default-language: Haskell2010
4345
4446test-suite tests
47+ ghc-options: -Wall
4548 default-extensions: OverloadedStrings
4649 type: exitcode-stdio-1.0
4750 main-is: tests.hs
@@ -51,12 +54,14 @@ test-suite tests
5154 bloodhound,
5255 http-conduit,
5356 http-types,
57+ bytestring >= 0.10,
5458 hspec >= 1.8,
5559 QuickCheck >= 2.5,
5660 derive >= 2.5,
5761 text >= 0.11,
5862 time >= 1.4,
5963 aeson >= 0.7,
64+ aeson-pretty >= 0.7,
6065 random >= 1.0,
6166 quickcheck-instances >= 0.3
6267 default-language: Haskell2010
tests/tests.hs+29−10
@@ -4,25 +4,32 @@ module Main where
44
55import Database.Bloodhound
66import Data.Aeson
7−import Data.Either (Either(..))
8−import Data.Maybe (fromJust)
7+-- import Data.Aeson.Encode.Pretty
8+-- import Data.ByteString.Lazy.Char8 (putStrLn)
99import Data.Time.Calendar (Day(..))
1010import Data.Time.Clock (secondsToDiffTime, UTCTime(..))
1111import Data.Text (Text)
1212import GHC.Generics (Generic)
1313import Network.HTTP.Conduit
1414import qualified Network.HTTP.Types.Status as NHTS
15+import Prelude hiding (filter, putStrLn)
1516import Test.Hspec
1617
18+testServer :: Server
1719testServer = Server "http://localhost:9200"
20+testIndex :: IndexName
1821testIndex = IndexName "twitter"
22+testMapping :: MappingName
1923testMapping = MappingName "tweet"
2024
25+validateStatus :: Response body -> Int -> Expectation
2126validateStatus resp expected =
2227 (NHTS.statusCode $ responseStatus resp)
2328 `shouldBe` (expected :: Int)
2429
30+createExampleIndex :: IO Reply
2531createExampleIndex = createIndex testServer defaultIndexSettings testIndex
32+deleteExampleIndex :: IO Reply
2633deleteExampleIndex = deleteIndex testServer testIndex
2734
2835data Location = Location { lat :: Double
@@ -48,6 +55,7 @@ instance ToJSON TweetMapping where
4855 object ["properties" .=
4956 object ["location" .= object ["type" .= ("geo_point" :: Text)]]]]
5057
58+exampleTweet :: Tweet
5159exampleTweet = Tweet { user = "bitemyapp"
5260 , postDate = UTCTime
5361 (ModifiedJulianDay 55000)
@@ -56,6 +64,7 @@ exampleTweet = Tweet { user = "bitemyapp"
5664 , age = 10000
5765 , location = Location 40.12 (-71.34) }
5866
67+otherTweet :: Tweet
5968otherTweet = Tweet { user = "notmyapp"
6069 , postDate = UTCTime
6170 (ModifiedJulianDay 55000)
@@ -66,17 +75,16 @@ otherTweet = Tweet { user = "notmyapp"
6675
6776insertData :: IO ()
6877insertData = do
69− let encoded = encode exampleTweet
7078 _ <- deleteExampleIndex
71− created <- createExampleIndex
72− mappingCreated <- createMapping testServer testIndex testMapping TweetMapping
73− docCreated <- indexDocument testServer testIndex testMapping exampleTweet (DocId "1")
79+ _ <- createExampleIndex
80+ _ <- createMapping testServer testIndex testMapping TweetMapping
81+ _ <- indexDocument testServer testIndex testMapping exampleTweet (DocId "1")
7482 _ <- refreshIndex testServer testIndex
7583 return ()
7684
7785insertOther :: IO ()
7886insertOther = do
79− docCreated <- indexDocument testServer testIndex testMapping otherTweet (DocId "2")
87+ _ <- indexDocument testServer testIndex testMapping otherTweet (DocId "2")
8088 _ <- refreshIndex testServer testIndex
8189 return ()
8290
@@ -110,6 +118,7 @@ main = hspec $ do
110118 validateStatus resp 200
111119 validateStatus deleteResp 200
112120
121+
113122 describe "document API" $ do
114123 it "indexes, gets, and then deletes the generated document" $ do
115124 _ <- insertData
@@ -118,6 +127,7 @@ main = hspec $ do
118127 (responseBody docInserted) :: Either String (EsResult Tweet)
119128 fmap _source newTweet `shouldBe` Right exampleTweet
120129
130+
121131 describe "bulk API" $ do
122132 it "inserts all documents we request" $ do
123133 _ <- insertData
@@ -128,7 +138,7 @@ main = hspec $ do
128138 let secondDoc = BulkCreate (IndexName "twitter")
129139 (MappingName "tweet") (DocId "3") (object ["name" .= String "bloo"])
130140 let stream = [firstDoc, secondDoc]
131− response <- bulk testServer stream
141+ _ <- bulk testServer stream
132142 _ <- refreshIndex testServer testIndex
133143 fDoc <- getDocument testServer testIndex testMapping (DocId "2")
134144 sDoc <- getDocument testServer testIndex testMapping (DocId "3")
@@ -137,6 +147,7 @@ main = hspec $ do
137147 fmap _source maybeFirst `shouldBe` Right firstTest
138148 fmap _source maybeSecond `shouldBe` Right secondTest
139149
150+
140151 describe "query API" $ do
141152 it "returns document for term query and identity filter" $ do
142153 _ <- insertData
@@ -146,6 +157,13 @@ main = hspec $ do
146157 myTweet <- searchTweet search
147158 myTweet `shouldBe` Right exampleTweet
148159
160+ it "returns document for match query" $ do
161+ _ <- insertData
162+ let query = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
163+ let search = mkSearch (Just query) Nothing
164+ myTweet <- searchTweet search
165+ myTweet `shouldBe` Right exampleTweet
166+
149167 describe "sorting" $ do
150168 it "returns documents in the right order" $ do
151169 _ <- insertData
@@ -159,6 +177,7 @@ main = hspec $ do
159177 let myTweet = fmap (hitSource . head . hits . searchHits) result
160178 myTweet `shouldBe` Right otherTweet
161179
180+
162181 describe "filtering API" $ do
163182 it "returns document for composed boolmatch and identity" $ do
164183 _ <- insertData
@@ -258,7 +277,7 @@ main = hspec $ do
258277 it "returns document for regexp filter" $ do
259278 _ <- insertData
260279 let filter = RegexpFilter (FieldName "user") (Regexp "bite.*app")
261− RegexpAll (CacheName "test") False (CacheKey "key")
280+ (RegexpFlags "ALL") (CacheName "test") False (CacheKey "key")
262281 let search = mkSearch Nothing (Just filter)
263282 myTweet <- searchTweet search
264283 myTweet `shouldBe` Right exampleTweet
@@ -266,7 +285,7 @@ main = hspec $ do
266285 it "doesn't return document for non-matching regexp filter" $ do
267286 _ <- insertData
268287 let filter = RegexpFilter (FieldName "user")
269− (Regexp "boy") RegexpAll
288+ (Regexp "boy") (RegexpFlags "ALL")
270289 (CacheName "test") False (CacheKey "key")
271290 let search = mkSearch Nothing (Just filter)
272291 searchExpectNoResults search