git.iko.soy/typeable/bloodhound
on master help sign in
Merge pull request #219 from andrewthad/basic_token_filter

Add support for token filters

commitb161cadd2e7802ea4e629faf3fe76bf9c40deab1 authorChris Allen <cma@bitemyapp.com> dateThu 8 Feb 2018, 17:56 (9 years ago) parentbe83a9151e59fd
2 files changed+251−4
src/Database/V5/Bloodhound/Types.hs+226−3 tests/V5/tests.hs+25−1
src/Database/V5/Bloodhound/Types.hs+226−3
@@ -204,6 +204,7 @@ module Database.V5.Bloodhound.Types
204204 , CutoffFrequency(..)
205205 , Analyzer(..)
206206 , Tokenizer(..)
207+ , TokenFilter(..)
207208 , MaxExpansions(..)
208209 , Lenient(..)
209210 , MatchQueryType(..)
@@ -385,8 +386,11 @@ module Database.V5.Bloodhound.Types
385386 , Analysis(..)
386387 , AnalyzerDefinition(..)
387388 , TokenizerDefinition(..)
389+ , TokenFilterDefinition(..)
388390 , Ngram(..)
389391 , TokenChar(..)
392+ , Shingle(..)
393+ , Language(..)
390394 ) where
391395
392396import Control.Applicative as A
@@ -602,32 +606,244 @@ data UpdatableIndexSetting = NumberOfReplicas ReplicaCount
602606data Analysis = Analysis
603607 { analysisAnalyzer :: M.Map Text AnalyzerDefinition
604608 , analysisTokenizer :: M.Map Text TokenizerDefinition
609+ , analysisTokenFilter :: M.Map Text TokenFilterDefinition
605610 } deriving (Eq,Show,Generic,Typeable)
606611
607612instance ToJSON Analysis where
608− toJSON (Analysis analyzer tokenizer) = object
613+ toJSON (Analysis analyzer tokenizer tokenFilter) = object
609614 [ "analyzer" .= analyzer
610615 , "tokenizer" .= tokenizer
616+ , "filter" .= tokenFilter
611617 ]
612618
613619instance FromJSON Analysis where
614620 parseJSON = withObject "Analysis" $ \m -> Analysis
615621 <$> m .: "analyzer"
616− <*> m .: "tokenizer"
622+ <*> m .:? "tokenizer" .!= M.empty
623+ <*> m .:? "filter" .!= M.empty
617624
618625data AnalyzerDefinition = AnalyzerDefinition
619626 { analyzerDefinitionTokenizer :: Maybe Tokenizer
627+ , analyzerDefinitionFilter :: [TokenFilter]
620628 } deriving (Eq,Show,Generic,Typeable)
621629
622630instance ToJSON AnalyzerDefinition where
623− toJSON (AnalyzerDefinition tokenizer) = object $ catMaybes
631+ toJSON (AnalyzerDefinition tokenizer tokenFilter) = object $ catMaybes
624632 [ fmap ("tokenizer" .=) tokenizer
633+ , Just $ "filter" .= tokenFilter
625634 ]
626635
627636instance FromJSON AnalyzerDefinition where
628637 parseJSON = withObject "AnalyzerDefinition" $ \m -> AnalyzerDefinition
629638 <$> m .:? "tokenizer"
639+ <*> m .:? "filter" .!= []
630640
641+-- | Token filters are used to create custom analyzers.
642+data TokenFilterDefinition
643+ = TokenFilterDefinitionLowercase (Maybe Language)
644+ | TokenFilterDefinitionUppercase (Maybe Language)
645+ | TokenFilterDefinitionApostrophe
646+ | TokenFilterDefinitionReverse
647+ | TokenFilterDefinitionSnowball Language
648+ | TokenFilterDefinitionShingle Shingle
649+ deriving (Eq,Show,Generic)
650+
651+instance ToJSON TokenFilterDefinition where
652+ toJSON x = case x of
653+ TokenFilterDefinitionLowercase mlang -> object $ catMaybes
654+ [ Just $ "type" .= ("lowercase" :: Text)
655+ , fmap (\lang -> "language" .= languageToText lang) mlang
656+ ]
657+ TokenFilterDefinitionUppercase mlang -> object $ catMaybes
658+ [ Just $ "type" .= ("uppercase" :: Text)
659+ , fmap (\lang -> "language" .= languageToText lang) mlang
660+ ]
661+ TokenFilterDefinitionApostrophe -> object
662+ [ "type" .= ("apostrophe" :: Text)
663+ ]
664+ TokenFilterDefinitionReverse -> object
665+ [ "type" .= ("reverse" :: Text)
666+ ]
667+ TokenFilterDefinitionSnowball lang -> object
668+ [ "type" .= ("snowball" :: Text)
669+ , "language" .= languageToText lang
670+ ]
671+ TokenFilterDefinitionShingle s -> object
672+ [ "type" .= ("shingle" :: Text)
673+ , "max_shingle_size" .= shingleMaxSize s
674+ , "min_shingle_size" .= shingleMinSize s
675+ , "output_unigrams" .= shingleOutputUnigrams s
676+ , "output_unigrams_if_no_shingles" .= shingleOutputUnigramsIfNoShingles s
677+ , "token_separator" .= shingleTokenSeparator s
678+ , "filler_token" .= shingleFillerToken s
679+ ]
680+
681+instance FromJSON TokenFilterDefinition where
682+ parseJSON = withObject "TokenFilterDefinition" $ \m -> do
683+ t <- m .: "type"
684+ case (t :: Text) of
685+ "reverse" -> return TokenFilterDefinitionReverse
686+ "apostrophe" -> return TokenFilterDefinitionApostrophe
687+ "lowercase" -> TokenFilterDefinitionLowercase
688+ <$> m .:? "language"
689+ "uppercase" -> TokenFilterDefinitionUppercase
690+ <$> m .:? "language"
691+ "snowball" -> TokenFilterDefinitionSnowball
692+ <$> m .: "language"
693+ "shingle" -> fmap TokenFilterDefinitionShingle $ Shingle
694+ <$> (fmap.fmap) unStringlyTypedInt (m .:? "max_shingle_size") .!= 2
695+ <*> (fmap.fmap) unStringlyTypedInt (m .:? "min_shingle_size") .!= 2
696+ <*> (fmap.fmap) unStringlyTypedBool (m .:? "output_unigrams") .!= True
697+ <*> (fmap.fmap) unStringlyTypedBool (m .:? "output_unigrams_if_no_shingles") .!= False
698+ <*> m .:? "token_separator" .!= " "
699+ <*> m .:? "filler_token" .!= "_"
700+ _ -> fail ("unrecognized token filter type: " ++ T.unpack t)
701+
702+-- | The set of languages that can be passed to various analyzers,
703+-- filters, etc. in ElasticSearch. Most data types in this module
704+-- that have a 'Language' field are actually only actually to
705+-- handle a subset of these languages. Consult the official
706+-- ElasticSearch documentation to see what is actually supported.
707+data Language
708+ = Arabic
709+ | Armenian
710+ | Basque
711+ | Bengali
712+ | Brazilian
713+ | Bulgarian
714+ | Catalan
715+ | Cjk
716+ | Czech
717+ | Danish
718+ | Dutch
719+ | English
720+ | Finnish
721+ | French
722+ | Galician
723+ | German
724+ | German2
725+ | Greek
726+ | Hindi
727+ | Hungarian
728+ | Indonesian
729+ | Irish
730+ | Italian
731+ | Kp
732+ | Latvian
733+ | Lithuanian
734+ | Lovins
735+ | Norwegian
736+ | Persian
737+ | Porter
738+ | Portuguese
739+ | Romanian
740+ | Russian
741+ | Sorani
742+ | Spanish
743+ | Swedish
744+ | Thai
745+ | Turkish
746+ deriving (Show,Eq,Generic)
747+
748+instance ToJSON Language where
749+ toJSON = Data.Aeson.String . languageToText
750+
751+instance FromJSON Language where
752+ parseJSON = withText "Language" $ \t -> case languageFromText t of
753+ Nothing -> fail "not a supported ElasticSearch language"
754+ Just lang -> return lang
755+
756+languageToText :: Language -> Text
757+languageToText x = case x of
758+ Arabic -> "arabic"
759+ Armenian -> "armenian"
760+ Basque -> "basque"
761+ Bengali -> "bengali"
762+ Brazilian -> "brazilian"
763+ Bulgarian -> "bulgarian"
764+ Catalan -> "catalan"
765+ Cjk -> "cjk"
766+ Czech -> "czech"
767+ Danish -> "danish"
768+ Dutch -> "dutch"
769+ English -> "english"
770+ Finnish -> "finnish"
771+ French -> "french"
772+ Galician -> "galician"
773+ German -> "german"
774+ German2 -> "german2"
775+ Greek -> "greek"
776+ Hindi -> "hindi"
777+ Hungarian -> "hungarian"
778+ Indonesian -> "indonesian"
779+ Irish -> "irish"
780+ Italian -> "italian"
781+ Kp -> "kp"
782+ Latvian -> "latvian"
783+ Lithuanian -> "lithuanian"
784+ Lovins -> "lovins"
785+ Norwegian -> "norwegian"
786+ Persian -> "persian"
787+ Porter -> "porter"
788+ Portuguese -> "portuguese"
789+ Romanian -> "romanian"
790+ Russian -> "russian"
791+ Sorani -> "sorani"
792+ Spanish -> "spanish"
793+ Swedish -> "swedish"
794+ Thai -> "thai"
795+ Turkish -> "turkish"
796+
797+languageFromText :: Text -> Maybe Language
798+languageFromText x = case x of
799+ "arabic" -> Just Arabic
800+ "armenian" -> Just Armenian
801+ "basque" -> Just Basque
802+ "bengali" -> Just Bengali
803+ "brazilian" -> Just Brazilian
804+ "bulgarian" -> Just Bulgarian
805+ "catalan" -> Just Catalan
806+ "cjk" -> Just Cjk
807+ "czech" -> Just Czech
808+ "danish" -> Just Danish
809+ "dutch" -> Just Dutch
810+ "english" -> Just English
811+ "finnish" -> Just Finnish
812+ "french" -> Just French
813+ "galician" -> Just Galician
814+ "german" -> Just German
815+ "german2" -> Just German2
816+ "greek" -> Just Greek
817+ "hindi" -> Just Hindi
818+ "hungarian" -> Just Hungarian
819+ "indonesian" -> Just Indonesian
820+ "irish" -> Just Irish
821+ "italian" -> Just Italian
822+ "kp" -> Just Kp
823+ "latvian" -> Just Latvian
824+ "lithuanian" -> Just Lithuanian
825+ "lovins" -> Just Lovins
826+ "norwegian" -> Just Norwegian
827+ "persian" -> Just Persian
828+ "porter" -> Just Porter
829+ "portuguese" -> Just Portuguese
830+ "romanian" -> Just Romanian
831+ "russian" -> Just Russian
832+ "sorani" -> Just Sorani
833+ "spanish" -> Just Spanish
834+ "swedish" -> Just Swedish
835+ "thai" -> Just Thai
836+ "turkish" -> Just Turkish
837+ _ -> Nothing
838+
839+data Shingle = Shingle
840+ { shingleMaxSize :: Int
841+ , shingleMinSize :: Int
842+ , shingleOutputUnigrams :: Bool
843+ , shingleOutputUnigramsIfNoShingles :: Bool
844+ , shingleTokenSeparator :: Text
845+ , shingleFillerToken :: Text
846+ } deriving (Eq,Show,Generic,Typeable)
631847
632848data TokenizerDefinition
633849 = TokenizerDefinitionNgram Ngram
@@ -1135,6 +1351,8 @@ newtype Analyzer =
11351351 Analyzer Text deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
11361352newtype Tokenizer =
11371353 Tokenizer Text deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1354+newtype TokenFilter =
1355+ TokenFilter Text deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
11381356newtype MaxExpansions =
11391357 MaxExpansions Int deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
11401358
@@ -4779,6 +4997,11 @@ newtype StringlyTypedInt = StringlyTypedInt { unStringlyTypedInt :: Int }
47794997instance FromJSON StringlyTypedInt where
47804998 parseJSON = fmap StringlyTypedInt . parseJSON . unStringlyTypeJSON
47814999
5000+newtype StringlyTypedBool = StringlyTypedBool { unStringlyTypedBool :: Bool }
5001+
5002+instance FromJSON StringlyTypedBool where
5003+ parseJSON = fmap StringlyTypedBool . parseJSON . unStringlyTypeJSON
5004+
47825005instance FromJSON NodeFSTotalStats where
47835006 parseJSON = withObject "NodeFSTotalStats" parse
47845007 where
tests/V5/tests.hs+25−1
@@ -925,8 +925,12 @@ instance Arbitrary TokenChar where arbitrary = sopArbitrary; shrink = genericShr
925925instance Arbitrary Ngram where arbitrary = sopArbitrary; shrink = genericShrink
926926instance Arbitrary TokenizerDefinition where arbitrary = sopArbitrary; shrink = genericShrink
927927instance Arbitrary AnalyzerDefinition where arbitrary = sopArbitrary; shrink = genericShrink
928+instance Arbitrary TokenFilterDefinition where arbitrary = sopArbitrary; shrink = genericShrink
929+instance Arbitrary Shingle where arbitrary = sopArbitrary; shrink = genericShrink
930+instance Arbitrary Language where arbitrary = sopArbitrary; shrink = genericShrink
928931instance Arbitrary Analysis where arbitrary = sopArbitrary; shrink = genericShrink
929932instance Arbitrary Tokenizer where arbitrary = sopArbitrary; shrink = genericShrink
933+instance Arbitrary TokenFilter where arbitrary = sopArbitrary; shrink = genericShrink
930934instance Arbitrary UpdatableIndexSetting where arbitrary = sopArbitrary; shrink = genericShrink
931935instance Arbitrary Compression where arbitrary = sopArbitrary; shrink = genericShrink
932936instance Arbitrary Bytes where arbitrary = sopArbitrary; shrink = genericShrink
@@ -960,6 +964,7 @@ instance Arbitrary UpdatableIndexSetting' where
960964 where
961965 dropDuplicateAttrNames = NE.fromList . L.nubBy sameAttrName . NE.toList
962966 sameAttrName a b = nodeAttrFilterName a == nodeAttrFilterName b
967+ shrink (UpdatableIndexSetting' x) = map UpdatableIndexSetting' (genericShrink x)
963968
964969main :: IO ()
965970main = hspec $ do
@@ -1648,12 +1653,31 @@ main = hspec $ do
16481653 it "accepts customer analyzers" $ when' (atleast es50) $ withTestEnv $ do
16491654 _ <- deleteExampleIndex
16501655 let analysis = Analysis
1651− (M.singleton "ex_analyzer" (AnalyzerDefinition (Just (Tokenizer "ex_tokenizer"))))
1656+ (M.singleton "ex_analyzer"
1657+ ( AnalyzerDefinition
1658+ (Just (Tokenizer "ex_tokenizer"))
1659+ (map TokenFilter
1660+ [ "ex_filter_lowercase","ex_filter_uppercase","ex_filter_apostrophe"
1661+ , "ex_filter_reverse","ex_filter_snowball"
1662+ , "ex_filter_shingle"
1663+ ]
1664+ )
1665+ )
1666+ )
16521667 (M.singleton "ex_tokenizer"
16531668 ( TokenizerDefinitionNgram
16541669 ( Ngram 3 4 [TokenLetter,TokenDigit])
16551670 )
16561671 )
1672+ (M.fromList
1673+ [ ("ex_filter_lowercase",TokenFilterDefinitionLowercase (Just Greek))
1674+ , ("ex_filter_uppercase",TokenFilterDefinitionUppercase Nothing)
1675+ , ("ex_filter_apostrophe",TokenFilterDefinitionApostrophe)
1676+ , ("ex_filter_reverse",TokenFilterDefinitionReverse)
1677+ , ("ex_filter_snowball",TokenFilterDefinitionSnowball English)
1678+ , ("ex_filter_shingle",TokenFilterDefinitionShingle (Shingle 3 3 True False " " "_"))
1679+ ]
1680+ )
16571681 updates = [AnalysisSetting analysis]
16581682 createResp <- createIndexWith (updates ++ [NumberOfReplicas (ReplicaCount 0)]) 1 testIndex
16591683 liftIO $ validateStatus createResp 200