mirror of
https://github.com/typeable/bloodhound.git
synced 2024-09-11 08:25:32 +03:00
Add support for 'Phrase Suggester' (V5)
Adds the ability to attach a 'suggester' to a query. A few notes: * This commit is only V5. I hope to port it to V1 as well. * Only one suggestion request / response is supported per-query. * Suggestions are only supported on the '_search' endpoint (not the '_suggest' endpoint.
This commit is contained in:
parent
a6d1114103
commit
f535e7e99d
@ -1044,7 +1044,7 @@ scanSearch indexName mappingName search = do
|
||||
-- >>> 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
|
||||
mkSearch query filter = Search query filter Nothing Nothing Nothing False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing Nothing
|
||||
|
||||
-- | 'mkAggregateSearch' is a helper function that defaults everything in a 'Search' except for
|
||||
-- the 'Query' and the 'Aggregation'.
|
||||
@ -1054,7 +1054,7 @@ mkSearch query filter = Search query filter Nothing Nothing Nothing False (From
|
||||
-- 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
|
||||
mkAggregateSearch query mkSearchAggs = Search query Nothing Nothing (Just mkSearchAggs) Nothing False (From 0) (Size 0) SearchTypeQueryThenFetch Nothing Nothing Nothing
|
||||
|
||||
-- | 'mkHighlightSearch' is a helper function that defaults everything in a 'Search' except for
|
||||
-- the 'Query' and the 'Aggregation'.
|
||||
@ -1063,7 +1063,7 @@ mkAggregateSearch query mkSearchAggs = Search query Nothing Nothing (Just mkSear
|
||||
-- >>> 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
|
||||
mkHighlightSearch query searchHighlights = Search query Nothing Nothing Nothing (Just searchHighlights) False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing Nothing
|
||||
|
||||
-- | 'pageSearch' is a helper function that takes a search and assigns the from
|
||||
-- and size fields for the search. The from parameter defines the offset
|
||||
|
@ -330,6 +330,10 @@ module Database.V5.Bloodhound.Types
|
||||
, PhraseSuggester(..)
|
||||
, PhraseSuggesterHighlighter(..)
|
||||
, PhraseSuggesterCollate(..)
|
||||
, mkPhraseSuggester
|
||||
, SuggestOptions(..)
|
||||
, SuggestResponse(..)
|
||||
, NamedSuggestionResponse(..)
|
||||
|
||||
, Aggregation(..)
|
||||
, Aggregations
|
||||
@ -1116,7 +1120,9 @@ data Search = Search { queryBody :: Maybe Query
|
||||
, size :: Size
|
||||
, searchType :: SearchType
|
||||
, fields :: Maybe [FieldName]
|
||||
, source :: Maybe Source } deriving (Eq, Read, Show, Generic, Typeable)
|
||||
, source :: Maybe Source
|
||||
, suggestBody :: Maybe Suggest -- ^ Only one Suggestion request / response per Search is supported.
|
||||
} deriving (Eq, Read, Show, Generic, Typeable)
|
||||
|
||||
data SearchType = SearchTypeQueryThenFetch
|
||||
| SearchTypeDfsQueryThenFetch
|
||||
@ -1663,13 +1669,17 @@ instance FromJSON TemplateQueryInline where
|
||||
<$> o .: "inline"
|
||||
<*> o .: "params"
|
||||
|
||||
|
||||
data SearchResult a =
|
||||
SearchResult { took :: Int
|
||||
, timedOut :: Bool
|
||||
, shards :: ShardResult
|
||||
, searchHits :: SearchHits a
|
||||
, aggregations :: Maybe AggregationResults
|
||||
, scrollId :: Maybe ScrollId } deriving (Eq, Read, Show, Generic, Typeable)
|
||||
, scrollId :: Maybe ScrollId
|
||||
, suggest :: Maybe NamedSuggestionResponse -- ^ Only one Suggestion request / response per Search is supported.
|
||||
}
|
||||
deriving (Eq, Read, Show, Generic, Typeable)
|
||||
|
||||
newtype ScrollId = ScrollId Text deriving (Eq, Read, Show, Generic, Ord, ToJSON, FromJSON)
|
||||
|
||||
@ -3258,7 +3268,7 @@ instance FromJSON SearchAliasRouting where
|
||||
where parse t = SearchAliasRouting <$> parseNEJSON (String <$> T.splitOn "," t)
|
||||
|
||||
instance ToJSON Search where
|
||||
toJSON (Search mquery sFilter sort searchAggs highlight sTrackSortScores sFrom sSize _ sFields sSource) =
|
||||
toJSON (Search mquery sFilter sort searchAggs highlight sTrackSortScores sFrom sSize _ sFields sSource sSuggest) =
|
||||
omitNulls [ "query" .= query'
|
||||
, "sort" .= sort
|
||||
, "aggregations" .= searchAggs
|
||||
@ -3267,7 +3277,8 @@ instance ToJSON Search where
|
||||
, "size" .= sSize
|
||||
, "track_scores" .= sTrackSortScores
|
||||
, "fields" .= sFields
|
||||
, "_source" .= sSource]
|
||||
, "_source" .= sSource
|
||||
, "suggest" .= sSuggest]
|
||||
|
||||
where query' = case sFilter of
|
||||
Nothing -> mquery
|
||||
@ -3620,7 +3631,8 @@ instance (FromJSON a) => FromJSON (SearchResult a) where
|
||||
v .: "_shards" <*>
|
||||
v .: "hits" <*>
|
||||
v .:? "aggregations" <*>
|
||||
v .:? "_scroll_id"
|
||||
v .:? "_scroll_id" <*>
|
||||
v .:? "suggest"
|
||||
parseJSON _ = empty
|
||||
|
||||
instance (FromJSON a) => FromJSON (SearchHits a) where
|
||||
@ -5106,7 +5118,7 @@ data Suggest = Suggest { suggestText :: Text
|
||||
, suggestName :: Text
|
||||
, suggestType :: SuggestType
|
||||
}
|
||||
deriving (Show, Generic)
|
||||
deriving (Show, Generic, Eq, Read)
|
||||
|
||||
instance ToJSON Suggest where
|
||||
toJSON Suggest{..} = object [ "text" .= suggestText
|
||||
@ -5125,7 +5137,7 @@ instance FromJSON Suggest where
|
||||
parseJSON x = typeMismatch "Suggest" x
|
||||
|
||||
data SuggestType = SuggestTypePhraseSuggester PhraseSuggester
|
||||
deriving (Show, Generic)
|
||||
deriving (Show, Generic, Eq, Read)
|
||||
|
||||
instance ToJSON SuggestType where
|
||||
toJSON (SuggestTypePhraseSuggester x) = object ["phrase" .= x]
|
||||
@ -5149,7 +5161,7 @@ data PhraseSuggester =
|
||||
, phraseSuggesterHighlight :: Maybe PhraseSuggesterHighlighter
|
||||
, phraseSuggesterCollate :: Maybe PhraseSuggesterCollate
|
||||
}
|
||||
deriving (Show, Generic)
|
||||
deriving (Show, Generic, Eq, Read)
|
||||
|
||||
instance ToJSON PhraseSuggester where
|
||||
toJSON PhraseSuggester{..} = omitNulls [ "field" .= phraseSuggesterField
|
||||
@ -5180,11 +5192,16 @@ instance FromJSON PhraseSuggester where
|
||||
<*> o .:? "highlight"
|
||||
<*> o .:? "collate"
|
||||
|
||||
mkPhraseSuggester :: FieldName -> PhraseSuggester
|
||||
mkPhraseSuggester fName =
|
||||
PhraseSuggester fName Nothing Nothing Nothing Nothing Nothing Nothing
|
||||
Nothing Nothing Nothing Nothing
|
||||
|
||||
data PhraseSuggesterHighlighter =
|
||||
PhraseSuggesterHighlighter { phraseSuggesterHighlighterPreTag :: Text
|
||||
, phraseSuggesterHighlighterPostTag :: Text
|
||||
}
|
||||
deriving (Show, Generic)
|
||||
deriving (Show, Generic, Eq, Read)
|
||||
|
||||
instance ToJSON PhraseSuggesterHighlighter where
|
||||
toJSON PhraseSuggesterHighlighter{..} =
|
||||
@ -5202,7 +5219,7 @@ data PhraseSuggesterCollate =
|
||||
PhraseSuggesterCollate { phraseSuggesterCollateTemplateQuery :: TemplateQueryInline
|
||||
, phraseSuggesterCollatePrune :: Bool
|
||||
}
|
||||
deriving (Show, Generic)
|
||||
deriving (Show, Generic, Eq, Read)
|
||||
|
||||
instance ToJSON PhraseSuggesterCollate where
|
||||
toJSON PhraseSuggesterCollate{..} = object [ "query" .= object
|
||||
@ -5220,3 +5237,51 @@ instance FromJSON PhraseSuggesterCollate where
|
||||
prune' <- o .:? "prune" .!= False
|
||||
return $ PhraseSuggesterCollate (TemplateQueryInline inline' params') prune'
|
||||
parseJSON x = typeMismatch "PhraseSuggesterCollate" x
|
||||
|
||||
data SuggestOptions =
|
||||
SuggestOptions { suggestOptionsText :: Text
|
||||
, suggestOptionsScore :: Double
|
||||
, suggestOptionsFreq :: Maybe Int
|
||||
, suggestOptionsHighlighted :: Maybe Text
|
||||
}
|
||||
deriving (Eq, Read, Show)
|
||||
|
||||
instance FromJSON SuggestOptions where
|
||||
parseJSON = withObject "SuggestOptions" parse
|
||||
where parse o = SuggestOptions
|
||||
<$> o .: "text"
|
||||
<*> o .: "score"
|
||||
<*> o .:? "freq"
|
||||
<*> o .:? "highlighted"
|
||||
|
||||
data SuggestResponse =
|
||||
SuggestResponse { suggestResponseText :: Text
|
||||
, suggestResponseOffset :: Int
|
||||
, suggestResponseLength :: Int
|
||||
, suggestResponseOptions :: [SuggestOptions]
|
||||
}
|
||||
deriving (Eq, Read, Show)
|
||||
|
||||
instance FromJSON SuggestResponse where
|
||||
parseJSON = withObject "SuggestResponse" parse
|
||||
where parse o = SuggestResponse
|
||||
<$> o .: "text"
|
||||
<*> o .: "offset"
|
||||
<*> o .: "length"
|
||||
<*> o .: "options"
|
||||
|
||||
data NamedSuggestionResponse =
|
||||
NamedSuggestionResponse { nsrName :: Text
|
||||
, nsrResponses :: [SuggestResponse]
|
||||
}
|
||||
deriving (Eq, Read, Show)
|
||||
|
||||
instance FromJSON NamedSuggestionResponse where
|
||||
parseJSON (Object o) = do
|
||||
suggestionName' <- case HM.toList o of
|
||||
[(x, _)] -> return x
|
||||
_ -> fail "error parsing NamedSuggestionResponse name"
|
||||
suggestionResponses' <- o .: suggestionName'
|
||||
return $ NamedSuggestionResponse suggestionName' suggestionResponses'
|
||||
|
||||
parseJSON x = typeMismatch "NamedSuggestionResponse" x
|
||||
|
@ -1144,6 +1144,7 @@ main = hspec $ do
|
||||
let search = Search Nothing
|
||||
Nothing (Just [sortSpec]) Nothing Nothing
|
||||
False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing
|
||||
Nothing
|
||||
result <- searchTweets search
|
||||
let myTweet = grabFirst result
|
||||
liftIO $
|
||||
@ -1582,6 +1583,21 @@ main = hspec $ do
|
||||
resp <- forceMergeIndex (IndexList (testIndex :| [])) defaultForceMergeIndexSettings
|
||||
liftIO $ validateStatus resp 200
|
||||
|
||||
describe "Suggest" $ do
|
||||
it "returns a search suggestion using the phrase suggester" $ withTestEnv $ do
|
||||
_ <- insertData
|
||||
let query = QueryMatchNoneQuery
|
||||
phraseSuggester = mkPhraseSuggester (FieldName "message")
|
||||
namedSuggester = Suggest "Use haskel" "suggest_name" (SuggestTypePhraseSuggester phraseSuggester)
|
||||
search' = mkSearch (Just query) Nothing
|
||||
search = search' { suggestBody = Just namedSuggester }
|
||||
expectedText = Just "use haskell"
|
||||
resp <- searchByIndex testIndex search
|
||||
parsed <- parseEsResponse resp :: BH IO (Either EsError (SearchResult Tweet))
|
||||
case parsed of
|
||||
Left e -> liftIO $ expectationFailure ("Expected an search suggestion but got " <> show e)
|
||||
Right sr -> liftIO $ (suggestOptionsText . head . suggestResponseOptions . head . nsrResponses <$> suggest sr) `shouldBe` expectedText
|
||||
|
||||
describe "JSON instances" $ do
|
||||
propJSON (Proxy :: Proxy Version)
|
||||
propJSON (Proxy :: Proxy IndexName)
|
||||
|
Loading…
Reference in New Issue
Block a user