Skip to content

Commit d78efb5

Browse files
committed
implemented ts_rank/ts_rank_cd
1 parent 0c72ecc commit d78efb5

File tree

3 files changed

+72
-5
lines changed

3 files changed

+72
-5
lines changed

src/Database/Esqueleto/TextSearch/Language.hs

+2
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,6 @@ instance TextSearch SqlQuery SqlExpr SqlBackend where
4848
to_tsvector a b = unsafeSqlFunction "to_tsvector" (a, b)
4949
to_tsquery a b = unsafeSqlFunction "to_tsquery" (a, b)
5050
plainto_tsquery a b = unsafeSqlFunction "plainto_tsquery" (a, b)
51+
ts_rank a b c d = unsafeSqlFunction "ts_rank" (a, b, c, d)
52+
ts_rank_cd a b c d = unsafeSqlFunction "ts_rank_cd" (a, b, c, d)
5153
setweight a b = unsafeSqlFunction "setweight" (a, b)

src/Database/Esqueleto/TextSearch/Types.hs

+22-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module Database.Esqueleto.TextSearch.Types (
1212
, Lexemes
1313
, TsVector
1414
, RegConfig
15-
, NormalizationOption
15+
, NormalizationOption (..)
1616
, Weight (..)
1717
, Weights (..)
1818
, Position (..)
@@ -23,6 +23,9 @@ module Database.Esqueleto.TextSearch.Types (
2323
) where
2424

2525
import Control.Applicative (pure, many, optional, (<$>), (*>), (<*), (<|>))
26+
import Data.Bits ((.|.), (.&.))
27+
import Data.Int (Int64)
28+
import Data.List (foldl')
2629
import Data.Monoid ((<>))
2730
import Data.String (IsString(fromString))
2831
import Text.Printf (printf)
@@ -46,7 +49,24 @@ data NormalizationOption
4649
| NormUniqueWords
4750
| Norm1LogUniqueWords
4851
| Norm1Self
49-
deriving (Eq, Show)
52+
deriving (Eq, Show, Enum, Bounded)
53+
54+
normToInt :: NormalizationOption -> Int64
55+
normToInt n
56+
| fromEnum n == 0 = 0
57+
| otherwise = 2 ^ (fromEnum n - 1)
58+
59+
instance PersistField [NormalizationOption] where
60+
toPersistValue = PersistInt64 . foldl' (.|.) 0 . map normToInt
61+
fromPersistValue (PersistInt64 n) = Right $ foldl' go [] [minBound..maxBound]
62+
where go acc v = case normToInt v .&. n of
63+
0 -> acc
64+
_ -> v:acc
65+
fromPersistValue f
66+
= Left $
67+
"TextSearch/[NormalizationOption]: Unexpected Persist field: " <> tShow f
68+
instance PersistFieldSql [NormalizationOption] where
69+
sqlType = const SqlInt32
5070

5171
data Weight
5272
= Highest

test/Database/Esqueleto/TextSearchSpec.hs

+48-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
{-# LANGUAGE QuasiQuotes #-}
77
{-# LANGUAGE RankNTypes #-}
88
{-# LANGUAGE FlexibleContexts #-}
9+
{-# LANGUAGE FlexibleInstances #-}
910
{-# LANGUAGE TypeFamilies #-}
1011
{-# LANGUAGE GADTs #-}
1112
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
13+
{-# LANGUAGE ScopedTypeVariables #-}
1214
module Database.Esqueleto.TextSearchSpec (main, spec) where
1315

1416
import Control.Monad (forM_)
@@ -19,15 +21,17 @@ import Control.Monad.Logger (MonadLogger(..), runStderrLoggingT)
1921
import Control.Monad.Trans.Resource (
2022
MonadBaseControl, MonadThrow, ResourceT, runResourceT)
2123
import Database.Esqueleto (
22-
SqlExpr, Value(..), update, select, set, val, from, where_, (=.), (^.))
23-
import Database.Persist (entityKey, insert, get)
24+
SqlExpr, Value(..), unValue, update, select, set, val, from, where_
25+
, (=.), (^.))
26+
import Database.Persist (entityKey, insert, get, PersistField(..))
2427
import Database.Persist.Postgresql (
2528
SqlPersistT, ConnectionString, runSqlConn, transactionUndo
2629
, withPostgresqlConn, runMigration)
2730
import Database.Persist.TH (
2831
mkPersist, mkMigrate, persistUpperCase, share, sqlSettings)
2932
import Test.Hspec (Spec, hspec, describe, it, shouldBe)
30-
import Test.QuickCheck (Arbitrary(..), property, oneof, listOf, listOf1, choose)
33+
import Test.QuickCheck (
34+
Arbitrary(..), property, elements, oneof, listOf, listOf1, choose)
3135

3236
import Database.Esqueleto.TextSearch
3337

@@ -152,25 +156,29 @@ spec = do
152156
textToQuery "'foo'" `shouldBe` Right (lexm "foo")
153157
it "can parse it surrounded by spaces" $
154158
textToQuery " 'foo' " `shouldBe` Right (lexm "foo")
159+
155160
describe "infix lexeme with weights" $ do
156161
it "can parse it" $
157162
textToQuery "'foo':AB"
158163
`shouldBe` Right (Lexeme Infix [Highest,High] "foo")
159164
it "can parse it surrounded by spaces" $
160165
textToQuery " 'foo':AB "
161166
`shouldBe` Right (Lexeme Infix [Highest,High] "foo")
167+
162168
describe "prefix lexeme" $ do
163169
it "can parse it" $
164170
textToQuery "'foo':*" `shouldBe` Right (Lexeme Prefix [] "foo")
165171
it "can parse it surrounded byb spaces" $
166172
textToQuery " 'foo':* " `shouldBe` Right (Lexeme Prefix [] "foo")
173+
167174
describe "prefix lexeme with weights" $ do
168175
it "can parse it" $
169176
textToQuery "'foo':*AB" `shouldBe`
170177
Right (Lexeme Prefix [Highest,High] "foo")
171178
it "can parse it surrounded by spaces" $
172179
textToQuery " 'foo':*AB " `shouldBe`
173180
Right (Lexeme Prefix [Highest,High] "foo")
181+
174182
describe "&" $ do
175183
it "can parse it" $
176184
textToQuery "'foo'&'bar'" `shouldBe`
@@ -187,21 +195,25 @@ spec = do
187195
it "can parse several" $
188196
textToQuery "'foo'&'bar'&'car'" `shouldBe`
189197
Right (lexm "foo" :& lexm "bar" :& lexm "car")
198+
190199
describe "|" $ do
191200
it "can parse it" $
192201
textToQuery "'foo'|'bar'" `shouldBe` Right (lexm "foo" :| lexm "bar")
193202
it "can parse several" $
194203
textToQuery "'foo'|'bar'|'car'" `shouldBe`
195204
Right (lexm "foo" :| lexm "bar" :| lexm "car")
205+
196206
describe "mixed |s and &s" $ do
197207
it "respects precedence" $ do
198208
textToQuery "'foo'|'bar'&'car'" `shouldBe`
199209
Right (lexm "foo" :| lexm "bar" :& lexm "car")
200210
textToQuery "'foo'&'bar'|'car'" `shouldBe`
201211
Right (lexm "foo" :& lexm "bar" :| lexm "car")
212+
202213
describe "!" $ do
203214
it "can parse it" $
204215
textToQuery "!'foo'" `shouldBe` Right (Not (lexm "foo"))
216+
205217
describe "! and &" $ do
206218
it "can parse it" $ do
207219
textToQuery "!'foo'&'car'" `shouldBe`
@@ -237,6 +249,39 @@ spec = do
237249
return a
238250
liftIO $ length result2 `shouldBe` 0
239251

252+
describe "ts_rank_cd" $ do
253+
it "works as expected" $ run $ do
254+
let vector = to_tsvector (val "english") (val content)
255+
content = "content" :: Text
256+
query = to_tsquery (val "english") (val "content")
257+
norm = val []
258+
ret <- select $ return $ ts_rank_cd (val def) vector query norm
259+
liftIO $ map unValue ret `shouldBe` [0.1]
260+
261+
describe "ts_rank" $ do
262+
it "works as expected" $ run $ do
263+
let vector = to_tsvector (val "english") (val content)
264+
content = "content" :: Text
265+
query = to_tsquery (val "english") (val "content")
266+
norm = val []
267+
ret <- select $ return $ ts_rank (val def) vector query norm
268+
liftIO $ map unValue ret `shouldBe` [6.07927e-2]
269+
270+
describe "NormalizationOption" $ do
271+
describe "fromPersistValue . toPersistValue" $ do
272+
let isEqual [] [] = True
273+
isEqual [NormNone] [] = True
274+
isEqual [] [NormNone] = True
275+
isEqual a b = a == b
276+
toRight (Right a) = a
277+
toRight _ = error "unexpected Left"
278+
it "is isomorphism" $ property $ \(q :: [NormalizationOption]) ->
279+
isEqual ((toRight . fromPersistValue . toPersistValue) q) q
280+
`shouldBe` True
281+
282+
instance Arbitrary ([NormalizationOption]) where
283+
arbitrary = (:[]) <$> elements [minBound..maxBound]
284+
240285
instance a ~ Lexemes => Arbitrary (TsQuery a) where
241286
arbitrary = query 0
242287
where

0 commit comments

Comments
 (0)