git.iko.soy/typeable/bloodhound
on master help sign in
Add support for token filters. The supported filters

include: lowercase, uppercase, shingle, snowball, apostrophe, and reverse. This commit also adds tests for all of these.

commit7fce32ab3bf758b2b959e8b4d1baa36cadb2904a authorAndrew Martin <andrew.thaddeus@gmail.com> dateTue 19 Dec 2017, 19:32 (9 years ago) parent1e14794
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
@@ -203,6 +203,7 @@ module Database.V5.Bloodhound.Types
203203 , CutoffFrequency(..)
204204 , Analyzer(..)
205205 , Tokenizer(..)
206+ , TokenFilter(..)
206207 , MaxExpansions(..)
207208 , Lenient(..)
208209 , MatchQueryType(..)
@@ -381,8 +382,11 @@ module Database.V5.Bloodhound.Types
381382 , Analysis(..)
382383 , AnalyzerDefinition(..)
383384 , TokenizerDefinition(..)
385+ , TokenFilterDefinition(..)
384386 , Ngram(..)
385387 , TokenChar(..)
388+ , Shingle(..)
389+ , Language(..)
386390 ) where
387391
388392import Control.Applicative as A
@@ -597,32 +601,244 @@ data UpdatableIndexSetting = NumberOfReplicas ReplicaCount
597601data Analysis = Analysis
598602 { analysisAnalyzer :: M.Map Text AnalyzerDefinition
599603 , analysisTokenizer :: M.Map Text TokenizerDefinition
604+ , analysisTokenFilter :: M.Map Text TokenFilterDefinition
600605 } deriving (Eq,Show,Generic,Typeable)
601606
602607instance ToJSON Analysis where
603− toJSON (Analysis analyzer tokenizer) = object
608+ toJSON (Analysis analyzer tokenizer tokenFilter) = object
604609 [ "analyzer" .= analyzer
605610 , "tokenizer" .= tokenizer
611+ , "filter" .= tokenFilter
606612 ]
607613
608614instance FromJSON Analysis where
609615 parseJSON = withObject "Analysis" $ \m -> Analysis
610616 <$> m .: "analyzer"
611− <*> m .: "tokenizer"
617+ <*> m .:? "tokenizer" .!= M.empty
618+ <*> m .:? "filter" .!= M.empty
612619
613620data AnalyzerDefinition = AnalyzerDefinition
614621 { analyzerDefinitionTokenizer :: Maybe Tokenizer
622+ , analyzerDefinitionFilter :: [TokenFilter]
615623 } deriving (Eq,Show,Generic,Typeable)
616624
617625instance ToJSON AnalyzerDefinition where
618− toJSON (AnalyzerDefinition tokenizer) = object $ catMaybes
626+ toJSON (AnalyzerDefinition tokenizer tokenFilter) = object $ catMaybes
619627 [ fmap ("tokenizer" .=) tokenizer
628+ , Just $ "filter" .= tokenFilter
620629 ]
621630
622631instance FromJSON AnalyzerDefinition where
623632 parseJSON = withObject "AnalyzerDefinition" $ \m -> AnalyzerDefinition
624633 <$> m .:? "tokenizer"
634+ <*> m .:? "filter" .!= []
625635
636+-- | Token filters are used to create custom analyzers.
637+data TokenFilterDefinition
638+ = TokenFilterDefinitionLowercase Language
639+ | TokenFilterDefinitionUppercase Language
640+ | TokenFilterDefinitionApostrophe
641+ | TokenFilterDefinitionReverse
642+ | TokenFilterDefinitionSnowball Language
643+ | TokenFilterDefinitionShingle Shingle
644+ deriving (Eq,Show,Generic)
645+
646+instance ToJSON TokenFilterDefinition where
647+ toJSON x = case x of
648+ TokenFilterDefinitionLowercase lang -> object
649+ [ "type" .= ("lowercase" :: Text)
650+ , "language" .= languageToText lang
651+ ]
652+ TokenFilterDefinitionUppercase lang -> object
653+ [ "type" .= ("uppercase" :: Text)
654+ , "language" .= languageToText lang
655+ ]
656+ TokenFilterDefinitionApostrophe -> object
657+ [ "type" .= ("apostrophe" :: Text)
658+ ]
659+ TokenFilterDefinitionReverse -> object
660+ [ "type" .= ("reverse" :: Text)
661+ ]
662+ TokenFilterDefinitionSnowball lang -> object
663+ [ "type" .= ("snowball" :: Text)
664+ , "language" .= languageToText lang
665+ ]
666+ TokenFilterDefinitionShingle s -> object
667+ [ "type" .= ("shingle" :: Text)
668+ , "max_shingle_size" .= shingleMaxSize s
669+ , "min_shingle_size" .= shingleMinSize s
670+ , "output_unigrams" .= shingleOutputUnigrams s
671+ , "output_unigrams_if_no_shingles" .= shingleOutputUnigramsIfNoShingles s
672+ , "token_separator" .= shingleTokenSeparator s
673+ , "filler_token" .= shingleFillerToken s
674+ ]
675+
676+instance FromJSON TokenFilterDefinition where
677+ parseJSON = withObject "TokenFilterDefinition" $ \m -> do
678+ t <- m .: "type"
679+ case (t :: Text) of
680+ "reverse" -> return TokenFilterDefinitionReverse
681+ "apostrophe" -> return TokenFilterDefinitionApostrophe
682+ "lowercase" -> TokenFilterDefinitionLowercase
683+ <$> m .:? "language" .!= English
684+ "uppercase" -> TokenFilterDefinitionUppercase
685+ <$> m .:? "language" .!= English
686+ "snowball" -> TokenFilterDefinitionSnowball
687+ <$> m .:? "language" .!= English
688+ "shingle" -> fmap TokenFilterDefinitionShingle $ Shingle
689+ <$> (fmap.fmap) unStringlyTypedInt (m .:? "max_shingle_size") .!= 2
690+ <*> (fmap.fmap) unStringlyTypedInt (m .:? "min_shingle_size") .!= 2
691+ <*> (fmap.fmap) unStringlyTypedBool (m .:? "output_unigrams") .!= True
692+ <*> (fmap.fmap) unStringlyTypedBool (m .:? "output_unigrams_if_no_shingles") .!= False
693+ <*> m .:? "token_separator" .!= " "
694+ <*> m .:? "filler_token" .!= "_"
695+ _ -> fail ("unrecognized token filter type: " ++ T.unpack t)
696+
697+-- | The set of languages that can be passed to various analyzers,
698+-- filters, etc. in ElasticSearch. Most data types in this module
699+-- that have a 'Language' field are actually only actually to
700+-- handle a subset of these languages. Consult the official
701+-- ElasticSearch documentation to see what is actually supported.
702+data Language
703+ = Arabic
704+ | Armenian
705+ | Basque
706+ | Bengali
707+ | Brazilian
708+ | Bulgarian
709+ | Catalan
710+ | Cjk
711+ | Czech
712+ | Danish
713+ | Dutch
714+ | English
715+ | Finnish
716+ | French
717+ | Galician
718+ | German
719+ | German2
720+ | Greek
721+ | Hindi
722+ | Hungarian
723+ | Indonesian
724+ | Irish
725+ | Italian
726+ | Kp
727+ | Latvian
728+ | Lithuanian
729+ | Lovins
730+ | Norwegian
731+ | Persian
732+ | Porter
733+ | Portuguese
734+ | Romanian
735+ | Russian
736+ | Sorani
737+ | Spanish
738+ | Swedish
739+ | Thai
740+ | Turkish
741+ deriving (Show,Eq,Generic)
742+
743+instance ToJSON Language where
744+ toJSON = Data.Aeson.String . languageToText
745+
746+instance FromJSON Language where
747+ parseJSON = withText "Language" $ \t -> case languageFromText t of
748+ Nothing -> fail "not a supported ElasticSearch language"
749+ Just lang -> return lang
750+
751+languageToText :: Language -> Text
752+languageToText x = case x of
753+ Arabic -> "arabic"
754+ Armenian -> "armenian"
755+ Basque -> "basque"
756+ Bengali -> "bengali"
757+ Brazilian -> "brazilian"
758+ Bulgarian -> "bulgarian"
759+ Catalan -> "catalan"
760+ Cjk -> "cjk"
761+ Czech -> "czech"
762+ Danish -> "danish"
763+ Dutch -> "dutch"
764+ English -> "english"
765+ Finnish -> "finnish"
766+ French -> "french"
767+ Galician -> "galician"
768+ German -> "german"
769+ German2 -> "german2"
770+ Greek -> "greek"
771+ Hindi -> "hindi"
772+ Hungarian -> "hungarian"
773+ Indonesian -> "indonesian"
774+ Irish -> "irish"
775+ Italian -> "italian"
776+ Kp -> "kp"
777+ Latvian -> "latvian"
778+ Lithuanian -> "lithuanian"
779+ Lovins -> "lovins"
780+ Norwegian -> "norwegian"
781+ Persian -> "persian"
782+ Porter -> "porter"
783+ Portuguese -> "portuguese"
784+ Romanian -> "romanian"
785+ Russian -> "russian"
786+ Sorani -> "sorani"
787+ Spanish -> "spanish"
788+ Swedish -> "swedish"
789+ Thai -> "thai"
790+ Turkish -> "turkish"
791+
792+languageFromText :: Text -> Maybe Language
793+languageFromText x = case x of
794+ "arabic" -> Just Arabic
795+ "armenian" -> Just Armenian
796+ "basque" -> Just Basque
797+ "bengali" -> Just Bengali
798+ "brazilian" -> Just Brazilian
799+ "bulgarian" -> Just Bulgarian
800+ "catalan" -> Just Catalan
801+ "cjk" -> Just Cjk
802+ "czech" -> Just Czech
803+ "danish" -> Just Danish
804+ "dutch" -> Just Dutch
805+ "english" -> Just English
806+ "finnish" -> Just Finnish
807+ "french" -> Just French
808+ "galician" -> Just Galician
809+ "german" -> Just German
810+ "german2" -> Just German2
811+ "greek" -> Just Greek
812+ "hindi" -> Just Hindi
813+ "hungarian" -> Just Hungarian
814+ "indonesian" -> Just Indonesian
815+ "irish" -> Just Irish
816+ "italian" -> Just Italian
817+ "kp" -> Just Kp
818+ "latvian" -> Just Latvian
819+ "lithuanian" -> Just Lithuanian
820+ "lovins" -> Just Lovins
821+ "norwegian" -> Just Norwegian
822+ "persian" -> Just Persian
823+ "porter" -> Just Porter
824+ "portuguese" -> Just Portuguese
825+ "romanian" -> Just Romanian
826+ "russian" -> Just Russian
827+ "sorani" -> Just Sorani
828+ "spanish" -> Just Spanish
829+ "swedish" -> Just Swedish
830+ "thai" -> Just Thai
831+ "turkish" -> Just Turkish
832+ _ -> Nothing
833+
834+data Shingle = Shingle
835+ { shingleMaxSize :: Int
836+ , shingleMinSize :: Int
837+ , shingleOutputUnigrams :: Bool
838+ , shingleOutputUnigramsIfNoShingles :: Bool
839+ , shingleTokenSeparator :: Text
840+ , shingleFillerToken :: Text
841+ } deriving (Eq,Show,Generic,Typeable)
626842
627843data TokenizerDefinition
628844 = TokenizerDefinitionNgram Ngram
@@ -1110,6 +1326,8 @@ newtype Analyzer =
11101326 Analyzer Text deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
11111327newtype Tokenizer =
11121328 Tokenizer Text deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
1329+newtype TokenFilter =
1330+ TokenFilter Text deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
11131331newtype MaxExpansions =
11141332 MaxExpansions Int deriving (Eq, Read, Show, Generic, ToJSON, FromJSON, Typeable)
11151333
@@ -4751,6 +4969,11 @@ newtype StringlyTypedInt = StringlyTypedInt { unStringlyTypedInt :: Int }
47514969instance FromJSON StringlyTypedInt where
47524970 parseJSON = fmap StringlyTypedInt . parseJSON . unStringlyTypeJSON
47534971
4972+newtype StringlyTypedBool = StringlyTypedBool { unStringlyTypedBool :: Bool }
4973+
4974+instance FromJSON StringlyTypedBool where
4975+ parseJSON = fmap StringlyTypedBool . parseJSON . unStringlyTypeJSON
4976+
47544977instance FromJSON NodeFSTotalStats where
47554978 parseJSON = withObject "NodeFSTotalStats" parse
47564979 where
tests/V5/tests.hs+25−1
@@ -911,8 +911,12 @@ instance Arbitrary TokenChar where arbitrary = sopArbitrary; shrink = genericShr
911911instance Arbitrary Ngram where arbitrary = sopArbitrary; shrink = genericShrink
912912instance Arbitrary TokenizerDefinition where arbitrary = sopArbitrary; shrink = genericShrink
913913instance Arbitrary AnalyzerDefinition where arbitrary = sopArbitrary; shrink = genericShrink
914+instance Arbitrary TokenFilterDefinition where arbitrary = sopArbitrary; shrink = genericShrink
915+instance Arbitrary Shingle where arbitrary = sopArbitrary; shrink = genericShrink
916+instance Arbitrary Language where arbitrary = sopArbitrary; shrink = genericShrink
914917instance Arbitrary Analysis where arbitrary = sopArbitrary; shrink = genericShrink
915918instance Arbitrary Tokenizer where arbitrary = sopArbitrary; shrink = genericShrink
919+instance Arbitrary TokenFilter where arbitrary = sopArbitrary; shrink = genericShrink
916920instance Arbitrary UpdatableIndexSetting where arbitrary = sopArbitrary; shrink = genericShrink
917921instance Arbitrary Bytes where arbitrary = sopArbitrary; shrink = genericShrink
918922instance Arbitrary AllocationPolicy where arbitrary = sopArbitrary; shrink = genericShrink
@@ -943,6 +947,7 @@ instance Arbitrary UpdatableIndexSetting' where
943947 where
944948 dropDuplicateAttrNames = NE.fromList . L.nubBy sameAttrName . NE.toList
945949 sameAttrName a b = nodeAttrFilterName a == nodeAttrFilterName b
950+ shrink (UpdatableIndexSetting' x) = map UpdatableIndexSetting' (genericShrink x)
946951
947952main :: IO ()
948953main = hspec $ do
@@ -1631,12 +1636,31 @@ main = hspec $ do
16311636 it "accepts customer analyzers" $ when' (atleast es50) $ withTestEnv $ do
16321637 _ <- deleteExampleIndex
16331638 let analysis = Analysis
1634− (M.singleton "ex_analyzer" (AnalyzerDefinition (Just (Tokenizer "ex_tokenizer"))))
1639+ (M.singleton "ex_analyzer"
1640+ ( AnalyzerDefinition
1641+ (Just (Tokenizer "ex_tokenizer"))
1642+ (map TokenFilter
1643+ [ "ex_filter_lowercase","ex_filter_uppercase","ex_filter_apostrophe"
1644+ , "ex_filter_reverse","ex_filter_snowball"
1645+ , "ex_filter_shingle"
1646+ ]
1647+ )
1648+ )
1649+ )
16351650 (M.singleton "ex_tokenizer"
16361651 ( TokenizerDefinitionNgram
16371652 ( Ngram 3 4 [TokenLetter,TokenDigit])
16381653 )
16391654 )
1655+ (M.fromList
1656+ [ ("ex_filter_lowercase",TokenFilterDefinitionLowercase Greek)
1657+ , ("ex_filter_uppercase",TokenFilterDefinitionUppercase English)
1658+ , ("ex_filter_apostrophe",TokenFilterDefinitionApostrophe)
1659+ , ("ex_filter_reverse",TokenFilterDefinitionReverse)
1660+ , ("ex_filter_snowball",TokenFilterDefinitionSnowball English)
1661+ , ("ex_filter_shingle",TokenFilterDefinitionShingle (Shingle 3 3 True False " " "_"))
1662+ ]
1663+ )
16401664 updates = [AnalysisSetting analysis]
16411665 createResp <- createIndexWith (updates ++ [NumberOfReplicas (ReplicaCount 0)]) 1 testIndex
16421666 liftIO $ validateStatus createResp 200