esqueleto-textsearch 1.2.1 → 1.3.0
raw patch · 5 files changed
+110/−31 lines, 5 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Database.Esqueleto.TextSearch: Weights :: !Double -> !Double -> !Double -> !Double -> Weights
- Database.Esqueleto.TextSearch: [aWeight] :: Weights -> !Double
- Database.Esqueleto.TextSearch: [bWeight] :: Weights -> !Double
- Database.Esqueleto.TextSearch: [cWeight] :: Weights -> !Double
- Database.Esqueleto.TextSearch: [dWeight] :: Weights -> !Double
- Database.Esqueleto.TextSearch: data RegConfig
- Database.Esqueleto.TextSearch: data SearchTerm
- Database.Esqueleto.TextSearch: data Weights
- Database.Esqueleto.TextSearch: defaultWeights :: Weights
- Database.Esqueleto.TextSearch: prefixAndQuery :: SearchTerm -> SqlExpr (Value (TsQuery Lexemes))
- Database.Esqueleto.TextSearch: prefixOrQuery :: SearchTerm -> SqlExpr (Value (TsQuery Lexemes))
- Database.Esqueleto.TextSearch.Language: data SearchTerm
- Database.Esqueleto.TextSearch.Language: instance GHC.Base.Semigroup Database.Esqueleto.TextSearch.Language.SearchTerm
- Database.Esqueleto.TextSearch.Language: instance GHC.Show.Show Database.Esqueleto.TextSearch.Language.SearchTerm
+ Database.Esqueleto.TextSearch: andWords :: NonEmpty (TsQuery Words) -> TsQuery Words
+ Database.Esqueleto.TextSearch: orWords :: NonEmpty (TsQuery Words) -> TsQuery Words
+ Database.Esqueleto.TextSearch: to_tsquery_en :: SqlExpr (Value (TsQuery Words)) -> SqlExpr (Value (TsQuery Lexemes))
+ Database.Esqueleto.TextSearch.Language: andWords :: NonEmpty (TsQuery Words) -> TsQuery Words
+ Database.Esqueleto.TextSearch.Language: orWords :: NonEmpty (TsQuery Words) -> TsQuery Words
+ Database.Esqueleto.TextSearch.Language: to_tsquery_en :: SqlExpr (Value (TsQuery Words)) -> SqlExpr (Value (TsQuery Lexemes))
- Database.Esqueleto.TextSearch: toSearchTerm :: Text -> Maybe SearchTerm
+ Database.Esqueleto.TextSearch: toSearchTerm :: Text -> Maybe (NonEmpty (TsQuery Words))
- Database.Esqueleto.TextSearch.Language: prefixAndQuery :: SearchTerm -> SqlExpr (Value (TsQuery Lexemes))
+ Database.Esqueleto.TextSearch.Language: prefixAndQuery :: NonEmpty (TsQuery Words) -> SqlExpr (Value (TsQuery Lexemes))
- Database.Esqueleto.TextSearch.Language: prefixAndQueryLang :: RegConfig -> SearchTerm -> SqlExpr (Value (TsQuery Lexemes))
+ Database.Esqueleto.TextSearch.Language: prefixAndQueryLang :: RegConfig -> NonEmpty (TsQuery Words) -> SqlExpr (Value (TsQuery Lexemes))
- Database.Esqueleto.TextSearch.Language: prefixOrQuery :: SearchTerm -> SqlExpr (Value (TsQuery Lexemes))
+ Database.Esqueleto.TextSearch.Language: prefixOrQuery :: NonEmpty (TsQuery Words) -> SqlExpr (Value (TsQuery Lexemes))
- Database.Esqueleto.TextSearch.Language: prefixOrQueryLang :: RegConfig -> SearchTerm -> SqlExpr (Value (TsQuery Lexemes))
+ Database.Esqueleto.TextSearch.Language: prefixOrQueryLang :: RegConfig -> NonEmpty (TsQuery Words) -> SqlExpr (Value (TsQuery Lexemes))
- Database.Esqueleto.TextSearch.Language: toSearchTerm :: Text -> Maybe SearchTerm
+ Database.Esqueleto.TextSearch.Language: toSearchTerm :: Text -> Maybe (NonEmpty (TsQuery Words))
- Database.Esqueleto.TextSearch.Language: toSearchTermWeighted :: [Weight] -> Text -> Maybe SearchTerm
+ Database.Esqueleto.TextSearch.Language: toSearchTermWeighted :: [Weight] -> Text -> Maybe (NonEmpty (TsQuery Words))
Files
- Changelog.md +11/−0
- esqueleto-textsearch.cabal +1/−1
- src/Database/Esqueleto/TextSearch.hs +5/−8
- src/Database/Esqueleto/TextSearch/Language.hs +54/−20
- test/Database/Esqueleto/TextSearchSpec.hs +39/−2
Changelog.md view
@@ -1,5 +1,16 @@ # Change log for esqueleto text search +## Version 1.3.0++ Update misleading tsquery and and or++ expose underlying lists in search terms+ + you may get some breaking change since SearchTerm no longer exists.+ It should be replaced with `NonEmpty (TsQuery Words)`.+ I thought it better to just cleanup since the library is still rather young.++ remove search term datatype in favor of underlying.+ I realized the newtype would be heavy to maintain for little benefit++ Add alternative api which is simpler using the word algebra,+ added some examples in the docs which use these.+ ## Version 1.2.1 + Update misleading docs
esqueleto-textsearch.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: esqueleto-textsearch-version: 1.2.1+version: 1.3.0 synopsis: PostgreSQL full text search for Esqueleto description: PostgreSQL text search functions for Esqueleto. homepage: https://github.com/jappeace/esqueleto-textsearch
src/Database/Esqueleto/TextSearch.hs view
@@ -4,15 +4,12 @@ -- -- see the [readme](https://hackage.haskell.org/package/esqueleto-textsearch#tutorial) for a full tutorial. module Database.Esqueleto.TextSearch (- (@@.)- , prefixAndQuery- , prefixOrQuery- , toSearchTerm- , SearchTerm+ toSearchTerm+ , to_tsquery_en+ , andWords+ , orWords+ , (@@.) , ts_rank- , defaultWeights- , Weights (..)- , RegConfig , NormalizationOption(..) , module Database.Esqueleto.TextSearch.Language , module Database.Esqueleto.TextSearch.Types
src/Database/Esqueleto/TextSearch/Language.hs view
@@ -12,9 +12,11 @@ , prefixOrQueryLang , toSearchTerm , toSearchTermWeighted- , SearchTerm+ , andWords+ , orWords , to_tsvector , to_tsquery+ , to_tsquery_en , plainto_tsquery , ts_rank , ts_rank_cd@@ -42,7 +44,7 @@ -- for example: -- -- @--- searchCompany :: SqlExpr (Entity CompanySearchIndex) -> SearchTerm -> SqlQuery ()+-- searchCompany :: SqlExpr (Entity CompanySearchIndex) -> NonEmpty (TsQuery Words) -> SqlQuery () -- searchCompany company term = do -- let query = prefixAndQuery term -- norm = val []@@ -62,12 +64,34 @@ -> SqlExpr (Value TsVector) to_tsvector a b = unsafeSqlFunction "to_tsvector" (a, b) +-- | constructs a lexeme query out of a word algebra+-- english is the internal model used by postgres.+--+-- @+-- searchCompany :: SqlExpr (Entity CompanySearchIndex) -> Text -> SqlQuery ()+-- searchCompany company term = do+-- let query = to_tsquery (val "english") $ val $ andWords $ prefixAndQuery term+-- where_ $ (company ^. CompanySearchIndexDocument) @@. query+-- @+-- to_tsquery :: SqlExpr (Value RegConfig) -> SqlExpr (Value (TsQuery Words)) -> SqlExpr (Value (TsQuery Lexemes) ) to_tsquery a b = unsafeSqlFunction "to_tsquery" (a, b) +-- | 'to_tsquery' defaulted to english+--+-- @+-- searchCompany :: SqlExpr (Entity CompanySearchIndex) -> Text -> SqlQuery ()+-- searchCompany company term = do+-- let query = to_tsquery_en $ val $ andWords $ prefixAndQuery term+-- where_ $ (company ^. CompanySearchIndexDocument) @@. query+-- @+--+to_tsquery_en :: SqlExpr (Value (TsQuery Words)) -> SqlExpr (Value (TsQuery Lexemes))+to_tsquery_en = to_tsquery (val "english")+ plainto_tsquery :: SqlExpr (Value RegConfig) -> SqlExpr (Value Text)@@ -79,7 +103,7 @@ -- for example: -- -- @--- searchCompany :: SqlExpr (Entity CompanySearchIndex) -> SearchTerm -> SqlQuery ()+-- searchCompany :: SqlExpr (Entity CompanySearchIndex) -> NonEmpty (TsQuery Words) -> SqlQuery () -- searchCompany company term = do -- let query = prefixAndQuery term -- norm = val []@@ -113,25 +137,34 @@ -- | (&&) for tsquery. This function would be called (&&.) but -- Esqueleto's (&&.) confines that fn to sql boolean expressions.+--+-- @ -- x::tsquery && y::tsquery == to_tsquery('x & y')+-- @+-- tsquery_and :: SqlExpr (Value (TsQuery Lexemes)) -> SqlExpr (Value (TsQuery Lexemes)) -> SqlExpr (Value (TsQuery Lexemes)) tsquery_and = unsafeSqlBinOp "&&" -- | (||) for tsquery. This function would be called (&&.) but--- Esqueleto's (&&.) confines that fn to sql boolean expressions.--- x::tsquery && y::tsquery == to_tsquery('x & y')+-- Esqueleto's (||.) confines that fn to sql boolean expressions.+--+-- @+-- x::tsquery || y::tsquery == to_tsquery('x | y')+-- @+-- tsquery_or :: SqlExpr (Value (TsQuery Lexemes)) -> SqlExpr (Value (TsQuery Lexemes)) -> SqlExpr (Value (TsQuery Lexemes)) tsquery_or = unsafeSqlBinOp "||" +{-# DEPRECATED prefixAndQuery, prefixAndQueryLang, prefixOrQuery, prefixOrQueryLang, prefixAndQueryLangWith "these functions are simple wrappers for 'to_tsquery', use that directly instead" #-} -- | format the query into lexemes -- the result can be used in '@@.' for example: -- -- @--- searchCompany :: SqlExpr (Entity CompanySearchIndex) -> SearchTerm -> SqlQuery ()+-- searchCompany :: SqlExpr (Entity CompanySearchIndex) -> (NonEmpty (TsQuery Words)) -> SqlQuery () -- searchCompany company term = do -- let query = prefixAndQuery term -- norm = val []@@ -139,34 +172,35 @@ -- @ -- -- this uses && to combine queries-prefixAndQuery :: SearchTerm -> SqlExpr (Value (TsQuery Lexemes))+prefixAndQuery :: (NonEmpty (TsQuery Words)) -> SqlExpr (Value (TsQuery Lexemes)) prefixAndQuery = prefixAndQueryLang "english" -- | specify a language to be used with the query.-prefixAndQueryLang :: RegConfig -> SearchTerm -> SqlExpr (Value (TsQuery Lexemes))+prefixAndQueryLang :: RegConfig -> (NonEmpty (TsQuery Words)) -> SqlExpr (Value (TsQuery Lexemes)) prefixAndQueryLang = prefixAndQueryLangWith tsquery_and -prefixOrQuery :: SearchTerm -> SqlExpr (Value (TsQuery Lexemes))+prefixOrQuery :: (NonEmpty (TsQuery Words)) -> SqlExpr (Value (TsQuery Lexemes)) prefixOrQuery = prefixOrQueryLang "english" -- | same as 'prefixAndQueryLang' but uses || to combine quereis-prefixOrQueryLang :: RegConfig -> SearchTerm -> SqlExpr (Value (TsQuery Lexemes))+prefixOrQueryLang :: RegConfig -> (NonEmpty (TsQuery Words)) -> SqlExpr (Value (TsQuery Lexemes)) prefixOrQueryLang = prefixAndQueryLangWith tsquery_or -- | allows specifying which binary operation is used for combining queries. prefixAndQueryLangWith :: (SqlExpr (Value (TsQuery Lexemes)) -> SqlExpr (Value (TsQuery Lexemes))- -> SqlExpr (Value (TsQuery Lexemes))) -> RegConfig -> SearchTerm -> SqlExpr (Value (TsQuery Lexemes))-prefixAndQueryLangWith binOp language (SearchTerm ts) =+ -> SqlExpr (Value (TsQuery Lexemes))) -> RegConfig -> (NonEmpty (TsQuery Words)) -> SqlExpr (Value (TsQuery Lexemes))+prefixAndQueryLangWith binOp language ts = foldr1 binOp $ map (to_tsquery (val language) . val) $ toList ts +-- | same as 'prefixAndQuery' without wrapping 'to_tsquery'.+andWords :: NonEmpty (TsQuery Words) -> TsQuery Words+andWords = foldr1 (:&) --- | A valid search term.--- created with 'toSearchTerm'.-newtype SearchTerm = SearchTerm { unQuery :: NonEmpty (TsQuery Words) }- deriving stock Show- deriving newtype Semigroup+-- | same as 'prefixOrQuery' without wrapping 'to_tsquery'.+orWords :: NonEmpty (TsQuery Words) -> TsQuery Words+orWords = foldr1 (:|) -- | Constructs a valid search query, removes a bunch of illegal -- characters and splits the terms for better results.@@ -174,14 +208,14 @@ -- -- using a search term is optional, but it's probably what you want. -- all underlying primitives are exposed.-toSearchTerm :: Text -> Maybe SearchTerm+toSearchTerm :: Text -> Maybe (NonEmpty (TsQuery Words)) toSearchTerm = toSearchTermWeighted [] -- | create a search term with some weight, this allows for restricting on specific weighs. -- see: https://www.postgresql.org/docs/current/textsearch-controls.html#TEXTSEARCH-PARSING-QUERIES -- use the semigroup instance on search term to combine searchterms.-toSearchTermWeighted :: [Weight] -> Text -> Maybe SearchTerm-toSearchTermWeighted weights q = SearchTerm . fmap (Word Prefix weights) <$> nonEmpty qs+toSearchTermWeighted :: [Weight] -> Text -> Maybe (NonEmpty (TsQuery Words))+toSearchTermWeighted weights q = fmap (Word Prefix weights) <$> nonEmpty qs -- We disallow whitespace, \ and ' for the sake of producing a Text -- that can fit postgresql's requirements for to_tsquery's text -- argument. Note that this is not done nor needed for security reasons
test/Database/Esqueleto/TextSearchSpec.hs view
@@ -20,7 +20,7 @@ module Database.Esqueleto.TextSearchSpec (main, spec) where import Control.Monad (forM_)-import Data.Maybe (fromJust)+import Data.Maybe import Data.Text (Text, pack) import Control.Monad.IO.Class (MonadIO(liftIO))@@ -51,7 +51,7 @@ ) import Database.Persist.TH (mkMigrate, mkPersist, persistUpperCase, share, sqlSettings)-import Test.Hspec (Spec, describe, hspec, it, shouldBe)+import Test.Hspec import Test.QuickCheck (Arbitrary(..), choose, elements, listOf, listOf1, oneof, property) @@ -272,6 +272,43 @@ where_ $ (a^. ArticleTextsearch) @@. query2 return a liftIO $ length result2 `shouldBe` 0++ it "works with andwords" $ run $ do+ let article = Article "some title" "some content" defaultTsVector+ arId <- insert article+ update $ \a -> do+ set a [ArticleTextsearch =. to_etsvector (a^.ArticleContent)]+ let query = to_tsquery (val "english") (val $ andWords $ fromMaybe (error "empty") $ toSearchTerm "some content")+ result <- select $ from $ \a -> do+ where_ $ (a^. ArticleTextsearch) @@. query+ return a+ liftIO $ do+ length result `shouldBe` 1+ map entityKey result `shouldBe` [arId]+ let query2 = to_tsquery (val "english") (val $ andWords $ fromMaybe (error "empty") $ toSearchTerm "foo content")+ result2 <- select $ from $ \a -> do+ where_ $ (a^. ArticleTextsearch) @@. query2+ return a+ liftIO $ length result2 `shouldBe` 0++ it "works with orwords" $ run $ do+ let article = Article "some title" "some content" defaultTsVector+ arId <- insert article+ update $ \a -> do+ set a [ArticleTextsearch =. to_etsvector (a^.ArticleContent)]+ let query = to_tsquery (val "english") (val $ orWords $ fromMaybe (error "empty") $ toSearchTerm "some content")+ result <- select $ from $ \a -> do+ where_ $ (a^. ArticleTextsearch) @@. query+ return a+ liftIO $ do+ length result `shouldBe` 1+ map entityKey result `shouldBe` [arId]+ let query2 = to_tsquery (val "english") (val $ orWords $ fromMaybe (error "empty") $ toSearchTerm "foo content")+ result2 <- select $ from $ \a -> do+ where_ $ (a^. ArticleTextsearch) @@. query2+ return a+ liftIO $ length result2 `shouldBe` 1+ describe "ts_rank_cd" $ do it "works as expected" $ run $ do