git.iko.soy/typeable/bloodhound
on master help sign in
Merge pull request #182 from bermanjosh/suggest

Support for 'Suggesters' and 'Template Queries'

commit79b4f901639e33ec654477279b5782b6b366a429 authorChris Allen <cma@bitemyapp.com> dateTue 30 May 2017, 20:41 (9 years ago) parentb1df6e594e3670
7 files changed+620−22
src/Database/V1/Bloodhound/Client.hs+3−3 src/Database/V1/Bloodhound/Types.hs+240−7 src/Database/V5/Bloodhound/Client.hs+3−3 src/Database/V5/Bloodhound/Types.hs+242−9 stack-7.8.yaml+1 tests/V1/tests.hs+66 tests/V5/tests.hs+65
src/Database/V1/Bloodhound/Client.hs+3−3
@@ -861,17 +861,17 @@ scanSearch indexName mappingName search = do
861861-- syntax if you want to add things like aggregations or highlights while still using
862862-- this helper function.
863863mkSearch :: Maybe Query -> Maybe Filter -> Search
864−mkSearch query filter = Search query filter Nothing Nothing Nothing False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing
864+mkSearch query filter = Search query filter Nothing Nothing Nothing False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing Nothing
865865
866866-- | 'mkAggregateSearch' is a helper function that defaults everything in a 'Search' except for
867867-- the 'Query' and the 'Aggregation'.
868868mkAggregateSearch :: Maybe Query -> Aggregations -> Search
869−mkAggregateSearch query mkSearchAggs = Search query Nothing Nothing (Just mkSearchAggs) Nothing False (From 0) (Size 0) SearchTypeQueryThenFetch Nothing Nothing
869+mkAggregateSearch query mkSearchAggs = Search query Nothing Nothing (Just mkSearchAggs) Nothing False (From 0) (Size 0) SearchTypeQueryThenFetch Nothing Nothing Nothing
870870
871871-- | 'mkHighlightSearch' is a helper function that defaults everything in a 'Search' except for
872872-- the 'Query' and the 'Aggregation'.
873873mkHighlightSearch :: Maybe Query -> Highlights -> Search
874−mkHighlightSearch query searchHighlights = Search query Nothing Nothing Nothing (Just searchHighlights) False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing
874+mkHighlightSearch query searchHighlights = Search query Nothing Nothing Nothing (Just searchHighlights) False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing Nothing
875875
876876-- | 'pageSearch' is a helper function that takes a search and assigns the from
877877-- and size fields for the search. The from parameter defines the offset
src/Database/V1/Bloodhound/Types.hs+240−7
@@ -195,6 +195,8 @@ module Database.V1.Bloodhound.Types
195195 , RangeQuery(..)
196196 , RegexpQuery(..)
197197 , QueryString(..)
198+ , TemplateQueryInline(..)
199+ , TemplateQueryKeyValuePairs(..)
198200 , BooleanOperator(..)
199201 , ZeroTermsQuery(..)
200202 , CutoffFrequency(..)
@@ -322,6 +324,15 @@ module Database.V1.Bloodhound.Types
322324 , rrGroupRefNum
323325 , mkRRGroupRefNum
324326 , RestoreIndexSettings(..)
327+ , Suggest(..)
328+ , SuggestType(..)
329+ , PhraseSuggester(..)
330+ , PhraseSuggesterHighlighter(..)
331+ , PhraseSuggesterCollate(..)
332+ , mkPhraseSuggester
333+ , SuggestOptions(..)
334+ , SuggestResponse(..)
335+ , NamedSuggestionResponse(..)
325336
326337 , Aggregation(..)
327338 , Aggregations
@@ -376,7 +387,8 @@ import Control.Monad.Writer (MonadWriter)
376387import Data.Aeson
377388import Data.Aeson.Types (Pair, Parser,
378389 emptyObject,
379− parseEither, parseMaybe)
390+ parseEither, parseMaybe,
391+ typeMismatch)
380392import qualified Data.ByteString.Lazy.Char8 as L
381393import Data.Char
382394import Data.Hashable (Hashable)
@@ -1091,7 +1103,7 @@ unpackId (DocId docId) = docId
10911103
10921104type TrackSortScores = Bool
10931105newtype From = From Int deriving (Eq, Read, Show, Generic, ToJSON)
1094−newtype Size = Size Int deriving (Eq, Read, Show, Generic, ToJSON)
1106+newtype Size = Size Int deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
10951107
10961108data Search = Search { queryBody :: Maybe Query
10971109 , filterBody :: Maybe Filter
@@ -1104,7 +1116,9 @@ data Search = Search { queryBody :: Maybe Query
11041116 , size :: Size
11051117 , searchType :: SearchType
11061118 , fields :: Maybe [FieldName]
1107− , source :: Maybe Source } deriving (Eq, Read, Show, Generic, Typeable)
1119+ , source :: Maybe Source
1120+ , suggestBody :: Maybe Suggest -- ^ Only one Suggestion request / response per Search is supported.
1121+ } deriving (Eq, Read, Show, Generic, Typeable)
11081122
11091123data SearchType = SearchTypeQueryThenFetch
11101124 | SearchTypeDfsQueryThenFetch
@@ -1211,6 +1225,7 @@ data Query =
12111225 | QuerySimpleQueryStringQuery SimpleQueryStringQuery
12121226 | QueryRangeQuery RangeQuery
12131227 | QueryRegexpQuery RegexpQuery
1228+ | QueryTemplateQueryInline TemplateQueryInline
12141229 deriving (Eq, Read, Show, Generic, Typeable)
12151230
12161231data RegexpQuery =
@@ -1635,13 +1650,48 @@ data DistanceRange =
16351650 DistanceRange { distanceFrom :: Distance
16361651 , distanceTo :: Distance } deriving (Eq, Read, Show, Generic, Typeable)
16371652
1653+type TemplateQueryKey = Text
1654+type TemplateQueryValue = Text
1655+
1656+newtype TemplateQueryKeyValuePairs = TemplateQueryKeyValuePairs (HM.HashMap TemplateQueryKey TemplateQueryValue)
1657+ deriving (Eq, Read, Show, Generic, Typeable)
1658+
1659+instance ToJSON TemplateQueryKeyValuePairs where
1660+ toJSON (TemplateQueryKeyValuePairs x) = Object $ HM.map toJSON x
1661+
1662+instance FromJSON TemplateQueryKeyValuePairs where
1663+ parseJSON (Object o) = pure . TemplateQueryKeyValuePairs $ HM.mapMaybe getValue o
1664+ where getValue (String x) = Just x
1665+ getValue _ = Nothing
1666+ parseJSON _ = fail "error parsing TemplateQueryKeyValuePairs"
1667+
1668+data TemplateQueryInline =
1669+ TemplateQueryInline { inline :: Query
1670+ , params :: TemplateQueryKeyValuePairs
1671+ }
1672+ deriving (Eq, Read, Show, Generic, Typeable)
1673+
1674+instance ToJSON TemplateQueryInline where
1675+ toJSON TemplateQueryInline{..} = object [ "query" .= inline
1676+ , "params" .= params
1677+ ]
1678+
1679+instance FromJSON TemplateQueryInline where
1680+ parseJSON = withObject "TemplateQueryInline" parse
1681+ where parse o = TemplateQueryInline
1682+ <$> o .: "query"
1683+ <*> o .: "params"
1684+
16381685data SearchResult a =
16391686 SearchResult { took :: Int
16401687 , timedOut :: Bool
16411688 , shards :: ShardResult
16421689 , searchHits :: SearchHits a
16431690 , aggregations :: Maybe AggregationResults
1644− , scrollId :: Maybe ScrollId } deriving (Eq, Read, Show, Generic, Typeable)
1691+ , scrollId :: Maybe ScrollId
1692+ , suggest :: Maybe NamedSuggestionResponse -- ^ Only one Suggestion request / response per Search is supported.
1693+ }
1694+ deriving (Eq, Read, Show, Generic, Typeable)
16451695
16461696newtype ScrollId = ScrollId Text deriving (Eq, Read, Show, Generic, Ord, ToJSON, FromJSON)
16471697
@@ -2404,6 +2454,9 @@ instance ToJSON Query where
24042454 toJSON (QuerySimpleQueryStringQuery query) =
24052455 object [ "simple_query_string" .= query ]
24062456
2457+ toJSON (QueryTemplateQueryInline templateQuery) =
2458+ object [ "template" .= templateQuery ]
2459+
24072460instance FromJSON Query where
24082461 parseJSON v = withObject "Query" parse v
24092462 where parse o = termQuery `taggedWith` "term"
@@ -2433,6 +2486,7 @@ instance FromJSON Query where
24332486 <|> queryRangeQuery `taggedWith` "range"
24342487 <|> queryRegexpQuery `taggedWith` "regexp"
24352488 <|> querySimpleQueryStringQuery `taggedWith` "simple_query_string"
2489+ <|> queryTemplateQueryInline `taggedWith` "template"
24362490 where taggedWith parser k = parser =<< o .: k
24372491 termQuery = fieldTagged $ \(FieldName fn) o ->
24382492 TermQuery <$> (Term fn <$> o .: "value") <*> o .:? "boost"
@@ -2474,6 +2528,7 @@ instance FromJSON Query where
24742528 queryRangeQuery = pure . QueryRangeQuery
24752529 queryRegexpQuery = pure . QueryRegexpQuery
24762530 querySimpleQueryStringQuery = pure . QuerySimpleQueryStringQuery
2531+ queryTemplateQueryInline = pure . QueryTemplateQueryInline
24772532
24782533
24792534omitNulls :: [(Text, Value)] -> Value
@@ -3412,7 +3467,7 @@ instance FromJSON SearchAliasRouting where
34123467 where parse t = SearchAliasRouting <$> parseNEJSON (String <$> T.splitOn "," t)
34133468
34143469instance ToJSON Search where
3415− toJSON (Search query sFilter sort searchAggs highlight sTrackSortScores sFrom sSize _ sFields sSource) =
3470+ toJSON (Search query sFilter sort searchAggs highlight sTrackSortScores sFrom sSize _ sFields sSource sSuggest) =
34163471 omitNulls [ "query" .= query
34173472 , "filter" .= sFilter
34183473 , "sort" .= sort
@@ -3422,7 +3477,8 @@ instance ToJSON Search where
34223477 , "size" .= sSize
34233478 , "track_scores" .= sTrackSortScores
34243479 , "fields" .= sFields
3425− , "_source" .= sSource]
3480+ , "_source" .= sSource
3481+ , "suggest" .= sSuggest]
34263482
34273483
34283484instance ToJSON Source where
@@ -3772,7 +3828,8 @@ instance (FromJSON a) => FromJSON (SearchResult a) where
37723828 v .: "_shards" <*>
37733829 v .: "hits" <*>
37743830 v .:? "aggregations" <*>
3775− v .:? "_scroll_id"
3831+ v .:? "_scroll_id" <*>
3832+ v .:? "suggest"
37763833 parseJSON _ = empty
37773834
37783835instance (FromJSON a) => FromJSON (SearchHits a) where
@@ -5277,3 +5334,179 @@ newtype MaybeNA a = MaybeNA { unMaybeNA :: Maybe a }
52775334instance FromJSON a => FromJSON (MaybeNA a) where
52785335 parseJSON (String "NA") = pure $ MaybeNA Nothing
52795336 parseJSON o = MaybeNA . Just <$> parseJSON o
5337+
5338+
5339+data Suggest = Suggest { suggestText :: Text
5340+ , suggestName :: Text
5341+ , suggestType :: SuggestType
5342+ }
5343+ deriving (Show, Generic, Eq, Read, Typeable)
5344+
5345+instance ToJSON Suggest where
5346+ toJSON Suggest{..} = object [ "text" .= suggestText
5347+ , suggestName .= suggestType
5348+ ]
5349+
5350+instance FromJSON Suggest where
5351+ parseJSON (Object o) = do
5352+ suggestText' <- o .: "text"
5353+ let dropTextList = HM.toList $ HM.filterWithKey (\x _ -> x /= "text") o
5354+ suggestName' <- case dropTextList of
5355+ [(x, _)] -> return x
5356+ _ -> fail "error parsing Suggest field name"
5357+ suggestType' <- o .: suggestName'
5358+ return $ Suggest suggestText' suggestName' suggestType'
5359+ parseJSON x = typeMismatch "Suggest" x
5360+
5361+data SuggestType = SuggestTypePhraseSuggester PhraseSuggester
5362+ deriving (Show, Generic, Eq, Read, Typeable)
5363+
5364+instance ToJSON SuggestType where
5365+ toJSON (SuggestTypePhraseSuggester x) = object ["phrase" .= x]
5366+
5367+instance FromJSON SuggestType where
5368+ parseJSON = withObject "SuggestType" parse
5369+ where parse o = phraseSuggester `taggedWith` "phrase"
5370+ where taggedWith parser k = parser =<< o .: k
5371+ phraseSuggester = pure . SuggestTypePhraseSuggester
5372+
5373+data PhraseSuggester =
5374+ PhraseSuggester { phraseSuggesterField :: FieldName
5375+ , phraseSuggesterGramSize :: Maybe Int
5376+ , phraseSuggesterRealWordErrorLikelihood :: Maybe Int
5377+ , phraseSuggesterConfidence :: Maybe Int
5378+ , phraseSuggesterMaxErrors :: Maybe Int
5379+ , phraseSuggesterSeparator :: Maybe Text
5380+ , phraseSuggesterSize :: Maybe Size
5381+ , phraseSuggesterAnalyzer :: Maybe Analyzer
5382+ , phraseSuggesterShardSize :: Maybe Int
5383+ , phraseSuggesterHighlight :: Maybe PhraseSuggesterHighlighter
5384+ , phraseSuggesterCollate :: Maybe PhraseSuggesterCollate
5385+ }
5386+ deriving (Show, Generic, Eq, Read, Typeable)
5387+
5388+instance ToJSON PhraseSuggester where
5389+ toJSON PhraseSuggester{..} = omitNulls [ "field" .= phraseSuggesterField
5390+ , "gram_size" .= phraseSuggesterGramSize
5391+ , "real_word_error_likelihood" .= phraseSuggesterRealWordErrorLikelihood
5392+ , "confidence" .= phraseSuggesterConfidence
5393+ , "max_errors" .= phraseSuggesterMaxErrors
5394+ , "separator" .= phraseSuggesterSeparator
5395+ , "size" .= phraseSuggesterSize
5396+ , "analyzer" .= phraseSuggesterAnalyzer
5397+ , "shard_size" .= phraseSuggesterShardSize
5398+ , "highlight" .= phraseSuggesterHighlight
5399+ , "collate" .= phraseSuggesterCollate
5400+ ]
5401+
5402+instance FromJSON PhraseSuggester where
5403+ parseJSON = withObject "PhraseSuggester" parse
5404+ where parse o = PhraseSuggester
5405+ <$> o .: "field"
5406+ <*> o .:? "gram_size"
5407+ <*> o .:? "real_word_error_likelihood"
5408+ <*> o .:? "confidence"
5409+ <*> o .:? "max_errors"
5410+ <*> o .:? "separator"
5411+ <*> o .:? "size"
5412+ <*> o .:? "analyzer"
5413+ <*> o .:? "shard_size"
5414+ <*> o .:? "highlight"
5415+ <*> o .:? "collate"
5416+
5417+mkPhraseSuggester :: FieldName -> PhraseSuggester
5418+mkPhraseSuggester fName =
5419+ PhraseSuggester fName Nothing Nothing Nothing Nothing Nothing Nothing
5420+ Nothing Nothing Nothing Nothing
5421+
5422+data PhraseSuggesterHighlighter =
5423+ PhraseSuggesterHighlighter { phraseSuggesterHighlighterPreTag :: Text
5424+ , phraseSuggesterHighlighterPostTag :: Text
5425+ }
5426+ deriving (Show, Generic, Eq, Read, Typeable)
5427+
5428+instance ToJSON PhraseSuggesterHighlighter where
5429+ toJSON PhraseSuggesterHighlighter{..} =
5430+ object [ "pre_tag" .= phraseSuggesterHighlighterPreTag
5431+ , "post_tag" .= phraseSuggesterHighlighterPostTag
5432+ ]
5433+
5434+instance FromJSON PhraseSuggesterHighlighter where
5435+ parseJSON = withObject "PhraseSuggesterHighlighter" parse
5436+ where parse o = PhraseSuggesterHighlighter
5437+ <$> o .: "pre_tag"
5438+ <*> o .: "post_tag"
5439+
5440+data PhraseSuggesterCollate =
5441+ PhraseSuggesterCollate { phraseSuggesterCollateTemplateQuery :: TemplateQueryInline
5442+ , phraseSuggesterCollatePrune :: Bool
5443+ }
5444+ deriving (Show, Generic, Eq, Read, Typeable)
5445+
5446+instance ToJSON PhraseSuggesterCollate where
5447+ toJSON PhraseSuggesterCollate{..} = object [ "query" .= object
5448+ [ "inline" .= (inline phraseSuggesterCollateTemplateQuery)
5449+ ]
5450+ , "params" .= (params phraseSuggesterCollateTemplateQuery)
5451+ , "prune" .= phraseSuggesterCollatePrune
5452+ ]
5453+
5454+instance FromJSON PhraseSuggesterCollate where
5455+ parseJSON (Object o) = do
5456+ query' <- o .: "query"
5457+ inline' <- query' .: "inline"
5458+ params' <- o .: "params"
5459+ prune' <- o .:? "prune" .!= False
5460+ return $ PhraseSuggesterCollate (TemplateQueryInline inline' params') prune'
5461+ parseJSON x = typeMismatch "PhraseSuggesterCollate" x
5462+
5463+mkPhraseSuggesterCollate :: TemplateQueryInline -> PhraseSuggesterCollate
5464+mkPhraseSuggesterCollate tQuery = PhraseSuggesterCollate tQuery False
5465+
5466+data SuggestOptions =
5467+ SuggestOptions { suggestOptionsText :: Text
5468+ , suggestOptionsScore :: Double
5469+ , suggestOptionsFreq :: Maybe Int
5470+ , suggestOptionsHighlighted :: Maybe Text
5471+ }
5472+ deriving (Eq, Read, Show)
5473+
5474+instance FromJSON SuggestOptions where
5475+ parseJSON = withObject "SuggestOptions" parse
5476+ where parse o = SuggestOptions
5477+ <$> o .: "text"
5478+ <*> o .: "score"
5479+ <*> o .:? "freq"
5480+ <*> o .:? "highlighted"
5481+
5482+data SuggestResponse =
5483+ SuggestResponse { suggestResponseText :: Text
5484+ , suggestResponseOffset :: Int
5485+ , suggestResponseLength :: Int
5486+ , suggestResponseOptions :: [SuggestOptions]
5487+ }
5488+ deriving (Eq, Read, Show)
5489+
5490+instance FromJSON SuggestResponse where
5491+ parseJSON = withObject "SuggestResponse" parse
5492+ where parse o = SuggestResponse
5493+ <$> o .: "text"
5494+ <*> o .: "offset"
5495+ <*> o .: "length"
5496+ <*> o .: "options"
5497+
5498+data NamedSuggestionResponse =
5499+ NamedSuggestionResponse { nsrName :: Text
5500+ , nsrResponses :: [SuggestResponse]
5501+ }
5502+ deriving (Eq, Read, Show)
5503+
5504+instance FromJSON NamedSuggestionResponse where
5505+ parseJSON (Object o) = do
5506+ suggestionName' <- case HM.toList o of
5507+ [(x, _)] -> return x
5508+ _ -> fail "error parsing NamedSuggestionResponse name"
5509+ suggestionResponses' <- o .: suggestionName'
5510+ return $ NamedSuggestionResponse suggestionName' suggestionResponses'
5511+
5512+ parseJSON x = typeMismatch "NamedSuggestionResponse" x
src/Database/V5/Bloodhound/Client.hs+3−3
@@ -1044,7 +1044,7 @@ scanSearch indexName mappingName search = do
10441044-- >>> mkSearch (Just query) Nothing
10451045-- 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}
10461046mkSearch :: Maybe Query -> Maybe Filter -> Search
1047−mkSearch query filter = Search query filter Nothing Nothing Nothing False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing
1047+mkSearch query filter = Search query filter Nothing Nothing Nothing False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing Nothing
10481048
10491049-- | 'mkAggregateSearch' is a helper function that defaults everything in a 'Search' except for
10501050-- the 'Query' and the 'Aggregation'.
@@ -1054,7 +1054,7 @@ mkSearch query filter = Search query filter Nothing Nothing Nothing False (From
10541054-- TermsAgg (TermsAggregation {term = Left "user", termInclude = Nothing, termExclude = Nothing, termOrder = Nothing, termMinDocCount = Nothing, termSize = Nothing, termShardSize = Nothing, termCollectMode = Just BreadthFirst, termExecutionHint = Nothing, termAggs = Nothing})
10551055-- >>> let myAggregation = mkAggregateSearch Nothing $ mkAggregations "users" terms
10561056mkAggregateSearch :: Maybe Query -> Aggregations -> Search
1057−mkAggregateSearch query mkSearchAggs = Search query Nothing Nothing (Just mkSearchAggs) Nothing False (From 0) (Size 0) SearchTypeQueryThenFetch Nothing Nothing
1057+mkAggregateSearch query mkSearchAggs = Search query Nothing Nothing (Just mkSearchAggs) Nothing False (From 0) (Size 0) SearchTypeQueryThenFetch Nothing Nothing Nothing
10581058
10591059-- | 'mkHighlightSearch' is a helper function that defaults everything in a 'Search' except for
10601060-- the 'Query' and the 'Aggregation'.
@@ -1063,7 +1063,7 @@ mkAggregateSearch query mkSearchAggs = Search query Nothing Nothing (Just mkSear
10631063-- >>> let testHighlight = Highlights Nothing [FieldHighlight (FieldName "message") Nothing]
10641064-- >>> let search = mkHighlightSearch (Just query) testHighlight
10651065mkHighlightSearch :: Maybe Query -> Highlights -> Search
1066−mkHighlightSearch query searchHighlights = Search query Nothing Nothing Nothing (Just searchHighlights) False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing
1066+mkHighlightSearch query searchHighlights = Search query Nothing Nothing Nothing (Just searchHighlights) False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing Nothing
10671067
10681068-- | 'pageSearch' is a helper function that takes a search and assigns the from
10691069-- and size fields for the search. The from parameter defines the offset
src/Database/V5/Bloodhound/Types.hs+242−9
@@ -196,6 +196,8 @@ module Database.V5.Bloodhound.Types
196196 , RangeQuery(..)
197197 , RegexpQuery(..)
198198 , QueryString(..)
199+ , TemplateQueryInline(..)
200+ , TemplateQueryKeyValuePairs(..)
199201 , BooleanOperator(..)
200202 , ZeroTermsQuery(..)
201203 , CutoffFrequency(..)
@@ -323,6 +325,15 @@ module Database.V5.Bloodhound.Types
323325 , rrGroupRefNum
324326 , mkRRGroupRefNum
325327 , RestoreIndexSettings(..)
328+ , Suggest(..)
329+ , SuggestType(..)
330+ , PhraseSuggester(..)
331+ , PhraseSuggesterHighlighter(..)
332+ , PhraseSuggesterCollate(..)
333+ , mkPhraseSuggester
334+ , SuggestOptions(..)
335+ , SuggestResponse(..)
336+ , NamedSuggestionResponse(..)
326337
327338 , Aggregation(..)
328339 , Aggregations
@@ -378,7 +389,8 @@ import Control.Monad.Writer (MonadWriter)
378389import Data.Aeson
379390import Data.Aeson.Types (Pair, Parser,
380391 emptyObject,
381− parseEither, parseMaybe)
392+ parseEither, parseMaybe,
393+ typeMismatch)
382394import qualified Data.ByteString.Lazy.Char8 as L
383395import Data.Char
384396import Data.Hashable (Hashable)
@@ -1095,7 +1107,7 @@ unpackId (DocId docId) = docId
10951107
10961108type TrackSortScores = Bool
10971109newtype From = From Int deriving (Eq, Read, Show, Generic, ToJSON)
1098−newtype Size = Size Int deriving (Eq, Read, Show, Generic, ToJSON)
1110+newtype Size = Size Int deriving (Eq, Read, Show, Generic, ToJSON, FromJSON)
10991111
11001112data Search = Search { queryBody :: Maybe Query
11011113 , filterBody :: Maybe Filter
@@ -1108,7 +1120,9 @@ data Search = Search { queryBody :: Maybe Query
11081120 , size :: Size
11091121 , searchType :: SearchType
11101122 , fields :: Maybe [FieldName]
1111− , source :: Maybe Source } deriving (Eq, Read, Show, Generic, Typeable)
1123+ , source :: Maybe Source
1124+ , suggestBody :: Maybe Suggest -- ^ Only one Suggestion request / response per Search is supported.
1125+ } deriving (Eq, Read, Show, Generic, Typeable)
11121126
11131127data SearchType = SearchTypeQueryThenFetch
11141128 | SearchTypeDfsQueryThenFetch
@@ -1211,6 +1225,7 @@ data Query =
12111225 | QueryRegexpQuery RegexpQuery
12121226 | QueryExistsQuery FieldName
12131227 | QueryMatchNoneQuery
1228+ | QueryTemplateQueryInline TemplateQueryInline
12141229 deriving (Eq, Read, Show, Generic, Typeable)
12151230
12161231-- | As of Elastic 2.0, 'Filters' are just 'Queries' housed in a Bool Query, and
@@ -1622,13 +1637,49 @@ data DistanceRange =
16221637 DistanceRange { distanceFrom :: Distance
16231638 , distanceTo :: Distance } deriving (Eq, Read, Show, Generic, Typeable)
16241639
1640+type TemplateQueryKey = Text
1641+type TemplateQueryValue = Text
1642+
1643+newtype TemplateQueryKeyValuePairs = TemplateQueryKeyValuePairs (HM.HashMap TemplateQueryKey TemplateQueryValue)
1644+ deriving (Eq, Read, Show, Generic)
1645+
1646+instance ToJSON TemplateQueryKeyValuePairs where
1647+ toJSON (TemplateQueryKeyValuePairs x) = Object $ HM.map toJSON x
1648+
1649+instance FromJSON TemplateQueryKeyValuePairs where
1650+ parseJSON (Object o) = pure . TemplateQueryKeyValuePairs $ HM.mapMaybe getValue o
1651+ where getValue (String x) = Just x
1652+ getValue _ = Nothing
1653+ parseJSON _ = fail "error parsing TemplateQueryKeyValuePairs"
1654+
1655+data TemplateQueryInline =
1656+ TemplateQueryInline { inline :: Query
1657+ , params :: TemplateQueryKeyValuePairs
1658+ }
1659+ deriving (Eq, Read, Show, Generic, Typeable)
1660+
1661+instance ToJSON TemplateQueryInline where
1662+ toJSON TemplateQueryInline{..} = object [ "inline" .= inline
1663+ , "params" .= params
1664+ ]
1665+
1666+instance FromJSON TemplateQueryInline where
1667+ parseJSON = withObject "TemplateQueryInline" parse
1668+ where parse o = TemplateQueryInline
1669+ <$> o .: "inline"
1670+ <*> o .: "params"
1671+
1672+
16251673data SearchResult a =
16261674 SearchResult { took :: Int
16271675 , timedOut :: Bool
16281676 , shards :: ShardResult
16291677 , searchHits :: SearchHits a
16301678 , aggregations :: Maybe AggregationResults
1631− , scrollId :: Maybe ScrollId } deriving (Eq, Read, Show, Generic, Typeable)
1679+ , scrollId :: Maybe ScrollId
1680+ , suggest :: Maybe NamedSuggestionResponse -- ^ Only one Suggestion request / response per Search is supported.
1681+ }
1682+ deriving (Eq, Read, Show, Generic, Typeable)
16321683
16331684newtype ScrollId = ScrollId Text deriving (Eq, Read, Show, Generic, Ord, ToJSON, FromJSON)
16341685
@@ -2220,6 +2271,9 @@ instance ToJSON Query where
22202271 toJSON QueryMatchNoneQuery =
22212272 object ["match_none" .= object []]
22222273
2274+ toJSON (QueryTemplateQueryInline templateQuery) =
2275+ object [ "template" .= templateQuery ]
2276+
22232277instance FromJSON Query where
22242278 parseJSON v = withObject "Query" parse v
22252279 where parse o = termQuery `taggedWith` "term"
@@ -2247,6 +2301,7 @@ instance FromJSON Query where
22472301 <|> queryRangeQuery `taggedWith` "range"
22482302 <|> queryRegexpQuery `taggedWith` "regexp"
22492303 <|> querySimpleQueryStringQuery `taggedWith` "simple_query_string"
2304+ <|> queryTemplateQueryInline `taggedWith` "template"
22502305 where taggedWith parser k = parser =<< o .: k
22512306 termQuery = fieldTagged $ \(FieldName fn) o ->
22522307 TermQuery <$> (Term fn <$> o .: "value") <*> o .:? "boost"
@@ -2284,6 +2339,7 @@ instance FromJSON Query where
22842339 queryRegexpQuery = pure . QueryRegexpQuery
22852340 querySimpleQueryStringQuery = pure . QuerySimpleQueryStringQuery
22862341 -- queryExistsQuery o = QueryExistsQuery <$> o .: "field"
2342+ queryTemplateQueryInline = pure . QueryTemplateQueryInline
22872343
22882344
22892345omitNulls :: [(Text, Value)] -> Value
@@ -2810,7 +2866,7 @@ instance ToJSON MultiMatchQuery where
28102866 , "query" .= query
28112867 , "operator" .= boolOp
28122868 , "zero_terms_query" .= ztQ
2813− , "tiebreaker" .= tb
2869+ , "tie_breaker" .= tb
28142870 , "type" .= mmqt
28152871 , "cutoff_frequency" .= cf
28162872 , "analyzer" .= analyzer
@@ -2825,7 +2881,7 @@ instance FromJSON MultiMatchQuery where
28252881 <*> o .: "query"
28262882 <*> o .: "operator"
28272883 <*> o .: "zero_terms_query"
2828− <*> o .:? "tiebreaker"
2884+ <*> o .:? "tie_breaker"
28292885 <*> o .:? "type"
28302886 <*> o .:? "cutoff_frequency"
28312887 <*> o .:? "analyzer"
@@ -3212,7 +3268,7 @@ instance FromJSON SearchAliasRouting where
32123268 where parse t = SearchAliasRouting <$> parseNEJSON (String <$> T.splitOn "," t)
32133269
32143270instance ToJSON Search where
3215− toJSON (Search mquery sFilter sort searchAggs highlight sTrackSortScores sFrom sSize _ sFields sSource) =
3271+ toJSON (Search mquery sFilter sort searchAggs highlight sTrackSortScores sFrom sSize _ sFields sSource sSuggest) =
32163272 omitNulls [ "query" .= query'
32173273 , "sort" .= sort
32183274 , "aggregations" .= searchAggs
@@ -3221,7 +3277,8 @@ instance ToJSON Search where
32213277 , "size" .= sSize
32223278 , "track_scores" .= sTrackSortScores
32233279 , "fields" .= sFields
3224− , "_source" .= sSource]
3280+ , "_source" .= sSource
3281+ , "suggest" .= sSuggest]
32253282
32263283 where query' = case sFilter of
32273284 Nothing -> mquery
@@ -3574,7 +3631,8 @@ instance (FromJSON a) => FromJSON (SearchResult a) where
35743631 v .: "_shards" <*>
35753632 v .: "hits" <*>
35763633 v .:? "aggregations" <*>
3577− v .:? "_scroll_id"
3634+ v .:? "_scroll_id" <*>
3635+ v .:? "suggest"
35783636 parseJSON _ = empty
35793637
35803638instance (FromJSON a) => FromJSON (SearchHits a) where
@@ -5055,3 +5113,178 @@ newtype MaybeNA a = MaybeNA { unMaybeNA :: Maybe a }
50555113instance FromJSON a => FromJSON (MaybeNA a) where
50565114 parseJSON (String "NA") = pure $ MaybeNA Nothing
50575115 parseJSON o = MaybeNA . Just <$> parseJSON o
5116+
5117+data Suggest = Suggest { suggestText :: Text
5118+ , suggestName :: Text
5119+ , suggestType :: SuggestType
5120+ }
5121+ deriving (Show, Generic, Eq, Read)
5122+
5123+instance ToJSON Suggest where
5124+ toJSON Suggest{..} = object [ "text" .= suggestText
5125+ , suggestName .= suggestType
5126+ ]
5127+
5128+instance FromJSON Suggest where
5129+ parseJSON (Object o) = do
5130+ suggestText' <- o .: "text"
5131+ let dropTextList = HM.toList $ HM.filterWithKey (\x _ -> x /= "text") o
5132+ suggestName' <- case dropTextList of
5133+ [(x, _)] -> return x
5134+ _ -> fail "error parsing Suggest field name"
5135+ suggestType' <- o .: suggestName'
5136+ return $ Suggest suggestText' suggestName' suggestType'
5137+ parseJSON x = typeMismatch "Suggest" x
5138+
5139+data SuggestType = SuggestTypePhraseSuggester PhraseSuggester
5140+ deriving (Show, Generic, Eq, Read)
5141+
5142+instance ToJSON SuggestType where
5143+ toJSON (SuggestTypePhraseSuggester x) = object ["phrase" .= x]
5144+
5145+instance FromJSON SuggestType where
5146+ parseJSON = withObject "SuggestType" parse
5147+ where parse o = phraseSuggester `taggedWith` "phrase"
5148+ where taggedWith parser k = parser =<< o .: k
5149+ phraseSuggester = pure . SuggestTypePhraseSuggester
5150+
5151+data PhraseSuggester =
5152+ PhraseSuggester { phraseSuggesterField :: FieldName
5153+ , phraseSuggesterGramSize :: Maybe Int
5154+ , phraseSuggesterRealWordErrorLikelihood :: Maybe Int
5155+ , phraseSuggesterConfidence :: Maybe Int
5156+ , phraseSuggesterMaxErrors :: Maybe Int
5157+ , phraseSuggesterSeparator :: Maybe Text
5158+ , phraseSuggesterSize :: Maybe Size
5159+ , phraseSuggesterAnalyzer :: Maybe Analyzer
5160+ , phraseSuggesterShardSize :: Maybe Int
5161+ , phraseSuggesterHighlight :: Maybe PhraseSuggesterHighlighter
5162+ , phraseSuggesterCollate :: Maybe PhraseSuggesterCollate
5163+ }
5164+ deriving (Show, Generic, Eq, Read)
5165+
5166+instance ToJSON PhraseSuggester where
5167+ toJSON PhraseSuggester{..} = omitNulls [ "field" .= phraseSuggesterField
5168+ , "gram_size" .= phraseSuggesterGramSize
5169+ , "real_word_error_likelihood" .= phraseSuggesterRealWordErrorLikelihood
5170+ , "confidence" .= phraseSuggesterConfidence
5171+ , "max_errors" .= phraseSuggesterMaxErrors
5172+ , "separator" .= phraseSuggesterSeparator
5173+ , "size" .= phraseSuggesterSize
5174+ , "analyzer" .= phraseSuggesterAnalyzer
5175+ , "shard_size" .= phraseSuggesterShardSize
5176+ , "highlight" .= phraseSuggesterHighlight
5177+ , "collate" .= phraseSuggesterCollate
5178+ ]
5179+
5180+instance FromJSON PhraseSuggester where
5181+ parseJSON = withObject "PhraseSuggester" parse
5182+ where parse o = PhraseSuggester
5183+ <$> o .: "field"
5184+ <*> o .:? "gram_size"
5185+ <*> o .:? "real_word_error_likelihood"
5186+ <*> o .:? "confidence"
5187+ <*> o .:? "max_errors"
5188+ <*> o .:? "separator"
5189+ <*> o .:? "size"
5190+ <*> o .:? "analyzer"
5191+ <*> o .:? "shard_size"
5192+ <*> o .:? "highlight"
5193+ <*> o .:? "collate"
5194+
5195+mkPhraseSuggester :: FieldName -> PhraseSuggester
5196+mkPhraseSuggester fName =
5197+ PhraseSuggester fName Nothing Nothing Nothing Nothing Nothing Nothing
5198+ Nothing Nothing Nothing Nothing
5199+
5200+data PhraseSuggesterHighlighter =
5201+ PhraseSuggesterHighlighter { phraseSuggesterHighlighterPreTag :: Text
5202+ , phraseSuggesterHighlighterPostTag :: Text
5203+ }
5204+ deriving (Show, Generic, Eq, Read)
5205+
5206+instance ToJSON PhraseSuggesterHighlighter where
5207+ toJSON PhraseSuggesterHighlighter{..} =
5208+ object [ "pre_tag" .= phraseSuggesterHighlighterPreTag
5209+ , "post_tag" .= phraseSuggesterHighlighterPostTag
5210+ ]
5211+
5212+instance FromJSON PhraseSuggesterHighlighter where
5213+ parseJSON = withObject "PhraseSuggesterHighlighter" parse
5214+ where parse o = PhraseSuggesterHighlighter
5215+ <$> o .: "pre_tag"
5216+ <*> o .: "post_tag"
5217+
5218+data PhraseSuggesterCollate =
5219+ PhraseSuggesterCollate { phraseSuggesterCollateTemplateQuery :: TemplateQueryInline
5220+ , phraseSuggesterCollatePrune :: Bool
5221+ }
5222+ deriving (Show, Generic, Eq, Read)
5223+
5224+instance ToJSON PhraseSuggesterCollate where
5225+ toJSON PhraseSuggesterCollate{..} = object [ "query" .= object
5226+ [ "inline" .= (inline phraseSuggesterCollateTemplateQuery)
5227+ ]
5228+ , "params" .= (params phraseSuggesterCollateTemplateQuery)
5229+ , "prune" .= phraseSuggesterCollatePrune
5230+ ]
5231+
5232+instance FromJSON PhraseSuggesterCollate where
5233+ parseJSON (Object o) = do
5234+ query' <- o .: "query"
5235+ inline' <- query' .: "inline"
5236+ params' <- o .: "params"
5237+ prune' <- o .:? "prune" .!= False
5238+ return $ PhraseSuggesterCollate (TemplateQueryInline inline' params') prune'
5239+ parseJSON x = typeMismatch "PhraseSuggesterCollate" x
5240+
5241+mkPhraseSuggesterCollate :: TemplateQueryInline -> PhraseSuggesterCollate
5242+mkPhraseSuggesterCollate tQuery = PhraseSuggesterCollate tQuery False
5243+
5244+data SuggestOptions =
5245+ SuggestOptions { suggestOptionsText :: Text
5246+ , suggestOptionsScore :: Double
5247+ , suggestOptionsFreq :: Maybe Int
5248+ , suggestOptionsHighlighted :: Maybe Text
5249+ }
5250+ deriving (Eq, Read, Show)
5251+
5252+instance FromJSON SuggestOptions where
5253+ parseJSON = withObject "SuggestOptions" parse
5254+ where parse o = SuggestOptions
5255+ <$> o .: "text"
5256+ <*> o .: "score"
5257+ <*> o .:? "freq"
5258+ <*> o .:? "highlighted"
5259+
5260+data SuggestResponse =
5261+ SuggestResponse { suggestResponseText :: Text
5262+ , suggestResponseOffset :: Int
5263+ , suggestResponseLength :: Int
5264+ , suggestResponseOptions :: [SuggestOptions]
5265+ }
5266+ deriving (Eq, Read, Show)
5267+
5268+instance FromJSON SuggestResponse where
5269+ parseJSON = withObject "SuggestResponse" parse
5270+ where parse o = SuggestResponse
5271+ <$> o .: "text"
5272+ <*> o .: "offset"
5273+ <*> o .: "length"
5274+ <*> o .: "options"
5275+
5276+data NamedSuggestionResponse =
5277+ NamedSuggestionResponse { nsrName :: Text
5278+ , nsrResponses :: [SuggestResponse]
5279+ }
5280+ deriving (Eq, Read, Show)
5281+
5282+instance FromJSON NamedSuggestionResponse where
5283+ parseJSON (Object o) = do
5284+ suggestionName' <- case HM.toList o of
5285+ [(x, _)] -> return x
5286+ _ -> fail "error parsing NamedSuggestionResponse name"
5287+ suggestionResponses' <- o .: suggestionName'
5288+ return $ NamedSuggestionResponse suggestionName' suggestionResponses'
5289+
5290+ parseJSON x = typeMismatch "NamedSuggestionResponse" x
stack-7.8.yaml+1
@@ -18,4 +18,5 @@ extra-deps:
1818- uri-bytestring-0.1.9
1919- void-0.7.1
2020- generics-sop-0.2.2.0
21+- unordered-containers-0.2.6.0
2122resolver: lts-2.18
tests/V1/tests.hs+66
@@ -595,6 +595,15 @@ instance (ApproxEq l, ApproxEq r) => ApproxEq (Either l r) where
595595instance ApproxEq NodeAttrFilter
596596instance ApproxEq NodeAttrName
597597instance ApproxEq BuildHash
598+instance ApproxEq TemplateQueryKeyValuePairs where
599+ (=~) = (==)
600+instance ApproxEq TemplateQueryInline
601+instance ApproxEq Size
602+instance ApproxEq PhraseSuggesterHighlighter
603+instance ApproxEq PhraseSuggesterCollate
604+instance ApproxEq PhraseSuggester
605+instance ApproxEq SuggestType
606+instance ApproxEq Suggest
598607
599608-- | Due to the way nodeattrfilters get serialized here, they may come
600609-- out in a different order, but they are morally equivalent
@@ -746,6 +755,7 @@ instance Arbitrary Query where
746755 , QuerySimpleQueryStringQuery <$> arbitrary
747756 , QueryRangeQuery <$> arbitrary
748757 , QueryRegexpQuery <$> arbitrary
758+ , QueryTemplateQueryInline <$> arbitrary
749759 ]
750760 shrink = genericShrink
751761
@@ -798,6 +808,10 @@ instance Arbitrary VersionNumber where
798808 where
799809 mk versions = VersionNumber (Vers.Version versions [])
800810
811+instance Arbitrary TemplateQueryKeyValuePairs where
812+ arbitrary = TemplateQueryKeyValuePairs . HM.fromList <$> arbitrary
813+ shrink (TemplateQueryKeyValuePairs x) = map (TemplateQueryKeyValuePairs . HM.fromList) . shrink $ HM.toList x
814+
801815instance Arbitrary IndexName where arbitrary = sopArbitrary; shrink = genericShrink
802816instance Arbitrary MappingName where arbitrary = sopArbitrary; shrink = genericShrink
803817instance Arbitrary DocId where arbitrary = sopArbitrary; shrink = genericShrink
@@ -913,6 +927,13 @@ instance Arbitrary FSType where arbitrary = sopArbitrary; shrink = genericShrink
913927instance Arbitrary CompoundFormat where arbitrary = sopArbitrary; shrink = genericShrink
914928instance Arbitrary FsSnapshotRepo where arbitrary = sopArbitrary; shrink = genericShrink
915929instance Arbitrary SnapshotRepoName where arbitrary = sopArbitrary; shrink = genericShrink
930+instance Arbitrary TemplateQueryInline where arbitrary = sopArbitrary; shrink = genericShrink
931+instance Arbitrary PhraseSuggesterCollate where arbitrary = sopArbitrary; shrink = genericShrink
932+instance Arbitrary PhraseSuggesterHighlighter where arbitrary = sopArbitrary; shrink = genericShrink
933+instance Arbitrary Size where arbitrary = sopArbitrary; shrink = genericShrink
934+instance Arbitrary PhraseSuggester where arbitrary = sopArbitrary; shrink = genericShrink
935+instance Arbitrary SuggestType where arbitrary = sopArbitrary; shrink = genericShrink
936+instance Arbitrary Suggest where arbitrary = sopArbitrary; shrink = genericShrink
916937
917938newtype UpdatableIndexSetting' = UpdatableIndexSetting' UpdatableIndexSetting
918939 deriving (Show, Eq, ToJSON, FromJSON, ApproxEq, Typeable)
@@ -1091,6 +1112,17 @@ main = hspec $ do
10911112 liftIO $
10921113 myTweet `shouldBe` Right exampleTweet
10931114
1115+ it "returns document for multi-match query with a custom tiebreaker" $ withTestEnv $ do
1116+ _ <- insertData
1117+ let tiebreaker = Just $ Tiebreaker 0.3
1118+ flds = [FieldName "user", FieldName "message"]
1119+ multiQuery' = mkMultiMatchQuery flds (QueryString "bitemyapp")
1120+ query = QueryMultiMatchQuery $ multiQuery' { multiMatchQueryTiebreaker = tiebreaker }
1121+ search = mkSearch (Just query) Nothing
1122+ myTweet <- searchTweet search
1123+ liftIO $
1124+ myTweet `shouldBe` Right exampleTweet
1125+
10941126 it "returns document for bool query" $ withTestEnv $ do
10951127 _ <- insertData
10961128 let innerQuery = QueryMatchQuery $
@@ -1124,6 +1156,21 @@ main = hspec $ do
11241156 liftIO $
11251157 myTweet `shouldBe` Right exampleTweet
11261158
1159+ it "returns document for for inline template query" $ withTestEnv $ do
1160+ _ <- insertData
1161+ let innerQuery = QueryMatchQuery $
1162+ mkMatchQuery (FieldName "{{userKey}}")
1163+ (QueryString "{{bitemyappKey}}")
1164+ templateParams = TemplateQueryKeyValuePairs $ HM.fromList
1165+ [ ("userKey", "user")
1166+ , ("bitemyappKey", "bitemyapp")
1167+ ]
1168+ templateQuery = QueryTemplateQueryInline $
1169+ TemplateQueryInline innerQuery templateParams
1170+ search = mkSearch (Just templateQuery) Nothing
1171+ myTweet <- searchTweet search
1172+ liftIO $ myTweet `shouldBe` Right exampleTweet
1173+
11271174
11281175 describe "sorting" $ do
11291176 it "returns documents in the right order" $ withTestEnv $ do
@@ -1133,6 +1180,7 @@ main = hspec $ do
11331180 let search = Search Nothing
11341181 (Just IdentityFilter) (Just [sortSpec]) Nothing Nothing
11351182 False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing
1183+ Nothing
11361184 result <- searchTweets search
11371185 let myTweet = grabFirst result
11381186 liftIO $
@@ -1761,6 +1809,22 @@ main = hspec $ do
17611809 resp <- optimizeIndex (IndexList (testIndex :| [])) defaultIndexOptimizationSettings
17621810 liftIO $ validateStatus resp 200
17631811
1812+ describe "Suggest" $ do
1813+ it "returns a search suggestion using the phrase suggester" $ withTestEnv $ do
1814+ _ <- insertData
1815+ let {- query = QueryMatchNoneQuery
1816+ query = TermQuery (Term "user" "bitemyapp") Nothing -}
1817+ let phraseSuggester = mkPhraseSuggester (FieldName "message")
1818+ namedSuggester = Suggest "Use haskel" "suggest_name" (SuggestTypePhraseSuggester phraseSuggester)
1819+ search' = mkSearch Nothing Nothing
1820+ search = search' { suggestBody = Just namedSuggester }
1821+ expectedText = Just "use haskell"
1822+ resp <- searchByIndex testIndex search
1823+ parsed <- parseEsResponse resp :: BH IO (Either EsError (SearchResult Tweet))
1824+ case parsed of
1825+ Left e -> liftIO $ expectationFailure ("Expected an search suggestion but got " <> show e)
1826+ Right sr -> liftIO $ (suggestOptionsText . head . suggestResponseOptions . head . nsrResponses <$> suggest sr) `shouldBe` expectedText
1827+
17641828 describe "JSON instances" $ do
17651829 propJSON (Proxy :: Proxy Version)
17661830 propJSON (Proxy :: Proxy IndexName)
@@ -1864,6 +1928,8 @@ main = hspec $ do
18641928 propJSON (Proxy :: Proxy InitialShardCount)
18651929 propJSON (Proxy :: Proxy FSType)
18661930 propJSON (Proxy :: Proxy CompoundFormat)
1931+ propJSON (Proxy :: Proxy TemplateQueryInline)
1932+ propJSON (Proxy :: Proxy Suggest)
18671933
18681934-- Temporary solution for lacking of generic derivation of Arbitrary
18691935-- We use generics-sop, as it's much more concise than directly using GHC.Generics
tests/V5/tests.hs+65
@@ -607,6 +607,15 @@ instance (ApproxEq l, Show l, ApproxEq r, Show r) => ApproxEq (Either l r) where
607607instance ApproxEq NodeAttrFilter
608608instance ApproxEq NodeAttrName
609609instance ApproxEq BuildHash
610+instance ApproxEq TemplateQueryKeyValuePairs where
611+ (=~) = (==)
612+instance ApproxEq TemplateQueryInline
613+instance ApproxEq Size
614+instance ApproxEq PhraseSuggesterHighlighter
615+instance ApproxEq PhraseSuggesterCollate
616+instance ApproxEq PhraseSuggester
617+instance ApproxEq SuggestType
618+instance ApproxEq Suggest
610619
611620-- | Due to the way nodeattrfilters get serialized here, they may come
612621-- out in a different order, but they are morally equivalent
@@ -756,6 +765,7 @@ instance Arbitrary Query where
756765 , QuerySimpleQueryStringQuery <$> arbitrary
757766 , QueryRangeQuery <$> arbitrary
758767 , QueryRegexpQuery <$> arbitrary
768+ , QueryTemplateQueryInline <$> arbitrary
759769 ]
760770 shrink = genericShrink
761771
@@ -791,6 +801,10 @@ instance Arbitrary VersionNumber where
791801 where
792802 mk versions = VersionNumber (Vers.Version versions [])
793803
804+instance Arbitrary TemplateQueryKeyValuePairs where
805+ arbitrary = TemplateQueryKeyValuePairs . HM.fromList <$> arbitrary
806+ shrink (TemplateQueryKeyValuePairs x) = map (TemplateQueryKeyValuePairs . HM.fromList) . shrink $ HM.toList x
807+
794808instance Arbitrary IndexName where arbitrary = sopArbitrary; shrink = genericShrink
795809instance Arbitrary MappingName where arbitrary = sopArbitrary; shrink = genericShrink
796810instance Arbitrary DocId where arbitrary = sopArbitrary; shrink = genericShrink
@@ -898,6 +912,13 @@ instance Arbitrary FSType where arbitrary = sopArbitrary; shrink = genericShrink
898912instance Arbitrary CompoundFormat where arbitrary = sopArbitrary; shrink = genericShrink
899913instance Arbitrary FsSnapshotRepo where arbitrary = sopArbitrary; shrink = genericShrink
900914instance Arbitrary SnapshotRepoName where arbitrary = sopArbitrary; shrink = genericShrink
915+instance Arbitrary TemplateQueryInline where arbitrary = sopArbitrary; shrink = genericShrink
916+instance Arbitrary PhraseSuggesterCollate where arbitrary = sopArbitrary; shrink = genericShrink
917+instance Arbitrary PhraseSuggesterHighlighter where arbitrary = sopArbitrary; shrink = genericShrink
918+instance Arbitrary Size where arbitrary = sopArbitrary; shrink = genericShrink
919+instance Arbitrary PhraseSuggester where arbitrary = sopArbitrary; shrink = genericShrink
920+instance Arbitrary SuggestType where arbitrary = sopArbitrary; shrink = genericShrink
921+instance Arbitrary Suggest where arbitrary = sopArbitrary; shrink = genericShrink
901922
902923newtype UpdatableIndexSetting' = UpdatableIndexSetting' UpdatableIndexSetting
903924 deriving (Show, Eq, ToJSON, FromJSON, ApproxEq, Typeable)
@@ -1066,6 +1087,17 @@ main = hspec $ do
10661087 liftIO $
10671088 myTweet `shouldBe` Right exampleTweet
10681089
1090+ it "returns document for multi-match query with a custom tiebreaker" $ withTestEnv $ do
1091+ _ <- insertData
1092+ let tiebreaker = Just $ Tiebreaker 0.3
1093+ flds = [FieldName "user", FieldName "message"]
1094+ multiQuery' = mkMultiMatchQuery flds (QueryString "bitemyapp")
1095+ query = QueryMultiMatchQuery $ multiQuery' { multiMatchQueryTiebreaker = tiebreaker }
1096+ search = mkSearch (Just query) Nothing
1097+ myTweet <- searchTweet search
1098+ liftIO $
1099+ myTweet `shouldBe` Right exampleTweet
1100+
10691101 it "returns document for bool query" $ withTestEnv $ do
10701102 _ <- insertData
10711103 let innerQuery = QueryMatchQuery $
@@ -1099,6 +1131,21 @@ main = hspec $ do
10991131 liftIO $
11001132 myTweet `shouldBe` Right exampleTweet
11011133
1134+ it "returns document for for inline template query" $ withTestEnv $ do
1135+ _ <- insertData
1136+ let innerQuery = QueryMatchQuery $
1137+ mkMatchQuery (FieldName "{{userKey}}")
1138+ (QueryString "{{bitemyappKey}}")
1139+ templateParams = TemplateQueryKeyValuePairs $ HM.fromList
1140+ [ ("userKey", "user")
1141+ , ("bitemyappKey", "bitemyapp")
1142+ ]
1143+ templateQuery = QueryTemplateQueryInline $
1144+ TemplateQueryInline innerQuery templateParams
1145+ search = mkSearch (Just templateQuery) Nothing
1146+ myTweet <- searchTweet search
1147+ liftIO $ myTweet `shouldBe` Right exampleTweet
1148+
11021149
11031150 describe "sorting" $ do
11041151 it "returns documents in the right order" $ withTestEnv $ do
@@ -1108,6 +1155,7 @@ main = hspec $ do
11081155 let search = Search Nothing
11091156 Nothing (Just [sortSpec]) Nothing Nothing
11101157 False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing
1158+ Nothing
11111159 result <- searchTweets search
11121160 let myTweet = grabFirst result
11131161 liftIO $
@@ -1546,6 +1594,21 @@ main = hspec $ do
15461594 resp <- forceMergeIndex (IndexList (testIndex :| [])) defaultForceMergeIndexSettings
15471595 liftIO $ validateStatus resp 200
15481596
1597+ describe "Suggest" $ do
1598+ it "returns a search suggestion using the phrase suggester" $ withTestEnv $ do
1599+ _ <- insertData
1600+ let query = QueryMatchNoneQuery
1601+ phraseSuggester = mkPhraseSuggester (FieldName "message")
1602+ namedSuggester = Suggest "Use haskel" "suggest_name" (SuggestTypePhraseSuggester phraseSuggester)
1603+ search' = mkSearch (Just query) Nothing
1604+ search = search' { suggestBody = Just namedSuggester }
1605+ expectedText = Just "use haskell"
1606+ resp <- searchByIndex testIndex search
1607+ parsed <- parseEsResponse resp :: BH IO (Either EsError (SearchResult Tweet))
1608+ case parsed of
1609+ Left e -> liftIO $ expectationFailure ("Expected an search suggestion but got " <> show e)
1610+ Right sr -> liftIO $ (suggestOptionsText . head . suggestResponseOptions . head . nsrResponses <$> suggest sr) `shouldBe` expectedText
1611+
15491612 describe "JSON instances" $ do
15501613 propJSON (Proxy :: Proxy Version)
15511614 propJSON (Proxy :: Proxy IndexName)
@@ -1648,6 +1711,8 @@ main = hspec $ do
16481711 propJSON (Proxy :: Proxy InitialShardCount)
16491712 propJSON (Proxy :: Proxy FSType)
16501713 propJSON (Proxy :: Proxy CompoundFormat)
1714+ propJSON (Proxy :: Proxy TemplateQueryInline)
1715+ propJSON (Proxy :: Proxy Suggest)
16511716
16521717-- Temporary solution for lacking of generic derivation of Arbitrary
16531718-- We use generics-sop, as it's much more concise than directly using GHC.Generics