-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathSearch.hs
More file actions
85 lines (67 loc) · 2.92 KB
/
Search.hs
File metadata and controls
85 lines (67 loc) · 2.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
module Search (
getKeyValues
, parseQuery
, query
) where
import FortuneIndexer (getTerm)
import qualified Data.Text as T
import Database.Redis.Redis
import Data.List.Split (splitEvery)
import Control.Monad (when)
import Text.ParserCombinators.Parsec
import Text.ParserCombinators.Parsec.Expr
import Text.ParserCombinators.Parsec.Token
import Text.ParserCombinators.Parsec.Language
import Text.ParserCombinators.Parsec.Error
data Query = Contains T.Text
| And Query Query
| Or Query Query
deriving (Show,Eq,Read)
-- Just a synonym to keep the type signatures from becoming unwieldy
type ZSetStoreFunc = Redis -> T.Text -> [T.Text] -> [Double] -> Aggregate -> IO (Reply Int)
-- http://www.haskell.org/haskellwiki/Parsing_expressions_and_statements
-- provides most of the boiler plate in easy to understand form
queryLang :: LanguageDef st
queryLang = emptyDef { identStart = letter
, identLetter = alphaNum
, reservedNames = ["and", "or"]
}
TokenParser { parens = m_parens
, identifier = m_identifier
, reserved = m_reserved
} = makeTokenParser queryLang
queryParser :: Parser Query
queryParser = buildExpressionParser table term <?> "query"
table :: OperatorTable Char () Query
table = [[binary "and" And AssocLeft, binary "or" Or AssocLeft]]
binary :: String -> (a -> a -> a) -> Assoc -> Operator Char () a
binary name fun = Infix (m_reserved name >> return fun)
term = m_parens queryParser
<|> fmap mkImplicitOr (many1 m_identifier)
<?> "query term"
mkImplicitOr :: [String] -> Query
mkImplicitOr xs = foldl1 Or $ map (Contains . T.pack) xs
parseQuery :: String -> Either String Query
parseQuery q = either processError (Right . id) x
where
x = parse queryParser "Web Request" q
processError :: ParseError -> Either String Query
processError p = Left (concatMap messageString $ errorMessages p)
binaryOp :: Redis -> T.Text -> Query -> Query -> ZSetStoreFunc -> IO T.Text
binaryOp r key lhs rhs op = do
args <- mapM (query r) [lhs,rhs]
cachedKey <- expire r key 30 >>= fromRInt
when (cachedKey == 0) (op r key args [] SUM >> expire r key 30 >> return ())
return key
getKey :: Query -> T.Text
getKey = T.pack . show
-- Returns the key that contains the answer to the query
query :: Redis -> Query -> IO T.Text
query r q@(And lhs rhs) = binaryOp r (getKey q) lhs rhs zinterStore
query r q@(Or lhs rhs) = binaryOp r (getKey q) lhs rhs zunionStore
query _ (Contains text) = return (getTerm text)
getKeyValues :: Redis -> T.Text -> IO [T.Text]
getKeyValues r key = do
x <- zrevrange r key (0,99999999) True >>= fromRMultiBulk'
let v = map head (splitEvery 2 x) :: [T.Text]
mapM (\z -> get r z >>= fromRBulk') v