module Ganeti.Query.Filter
( compileFilter
, evaluateFilter
, requestedNames
, FilterConstructor
, makeSimpleFilter
, makeHostnameFilter
) where
import Control.Applicative
import Control.Monad (liftM)
import qualified Data.Map as Map
import Data.Maybe (fromJust)
import Data.Traversable (traverse)
import Text.JSON (JSValue(..), fromJSString)
import Text.JSON.Pretty (pp_value)
import qualified Text.Regex.PCRE as PCRE
import Ganeti.BasicTypes
import Ganeti.Errors
import Ganeti.Objects
import Ganeti.Query.Language
import Ganeti.Query.Types
import Ganeti.JSON
compileFilter :: FieldMap a b
-> Filter FilterField
-> ErrorResult (Filter (FieldGetter a b, QffMode))
compileFilter fm =
traverse (\field -> maybe
(Bad . ParameterError $ "Can't find field named '" ++
field ++ "'")
(\(_, g, q) -> Ok (g, q)) (field `Map.lookup` fm))
qffField :: QffMode -> JSValue -> ErrorResult JSValue
qffField QffNormal v = Ok v
qffField QffTimestamp v =
case v of
JSArray [secs@(JSRational _ _), JSRational _ _] -> return secs
_ -> Bad $ ProgrammerError
"Internal error: Getter returned non-timestamp for QffTimestamp"
wrapGetter :: ConfigData
-> Maybe b
-> a
-> (FieldGetter a b, QffMode)
-> (JSValue -> ErrorResult Bool)
-> ErrorResult Bool
wrapGetter cfg b a (getter, qff) faction =
case tryGetter cfg b a getter of
Nothing -> Ok True
Just v ->
case v of
ResultEntry RSNormal (Just fval) -> qffField qff fval >>= faction
ResultEntry RSNormal Nothing ->
Bad $ ProgrammerError
"Internal error: Getter returned RSNormal/Nothing"
_ -> Ok True
trueFilter :: JSValue -> ErrorResult Bool
trueFilter (JSBool x) = Ok $! x
trueFilter v = Bad . ParameterError $
"Unexpected value '" ++ show (pp_value v) ++
"' in boolean context"
type Comparator = (Eq a, Ord a) => a -> a -> Bool
binOpFilter :: Comparator -> FilterValue -> JSValue -> ErrorResult Bool
binOpFilter comp (QuotedString y) (JSString x) =
Ok $! fromJSString x `comp` y
binOpFilter comp (NumericValue y) (JSRational _ x) =
Ok $! x `comp` fromIntegral y
binOpFilter _ expr actual =
Bad . ParameterError $ "Invalid types in comparison, trying to compare " ++
show (pp_value actual) ++ " with '" ++ show expr ++ "'"
regexpFilter :: FilterRegex -> JSValue -> ErrorResult Bool
regexpFilter re (JSString val) =
Ok $! PCRE.match (compiledRegex re) (fromJSString val)
regexpFilter _ x =
Bad . ParameterError $ "Invalid field value used in regexp matching,\
\ expecting string but got '" ++ show (pp_value x) ++ "'"
containsFilter :: FilterValue -> JSValue -> ErrorResult Bool
containsFilter (QuotedString val) lst = do
lst' <- fromJVal lst
return $! val `elem` lst'
containsFilter (NumericValue val) lst = do
lst' <- fromJVal lst
return $! val `elem` lst'
evaluateFilter :: ConfigData -> Maybe b -> a
-> Filter (FieldGetter a b, QffMode)
-> ErrorResult Bool
evaluateFilter _ _ _ EmptyFilter = Ok True
evaluateFilter c mb a (AndFilter flts) = helper flts
where helper [] = Ok True
helper (f:fs) = do
v <- evaluateFilter c mb a f
if v
then helper fs
else Ok False
evaluateFilter c mb a (OrFilter flts) = helper flts
where helper [] = Ok False
helper (f:fs) = do
v <- evaluateFilter c mb a f
if v
then Ok True
else helper fs
evaluateFilter c mb a (NotFilter flt) =
not <$> evaluateFilter c mb a flt
evaluateFilter c mb a (TrueFilter getter) =
wrapGetter c mb a getter trueFilter
evaluateFilter c mb a (EQFilter getter val) =
wrapGetter c mb a getter (binOpFilter (==) val)
evaluateFilter c mb a (LTFilter getter val) =
wrapGetter c mb a getter (binOpFilter (<) val)
evaluateFilter c mb a (LEFilter getter val) =
wrapGetter c mb a getter (binOpFilter (<=) val)
evaluateFilter c mb a (GTFilter getter val) =
wrapGetter c mb a getter (binOpFilter (>) val)
evaluateFilter c mb a (GEFilter getter val) =
wrapGetter c mb a getter (binOpFilter (>=) val)
evaluateFilter c mb a (RegexpFilter getter re) =
wrapGetter c mb a getter (regexpFilter re)
evaluateFilter c mb a (ContainsFilter getter val) =
wrapGetter c mb a getter (containsFilter val)
tryGetter :: ConfigData -> Maybe b -> a -> FieldGetter a b -> Maybe ResultEntry
tryGetter _ _ item (FieldSimple getter) = Just $ getter item
tryGetter cfg _ item (FieldConfig getter) = Just $ getter cfg item
tryGetter _ rt item (FieldRuntime getter) =
maybe Nothing (\rt' -> Just $ getter rt' item) rt
tryGetter _ _ _ FieldUnknown = Just $
ResultEntry RSUnknown Nothing
requestedNames :: FilterField -> Filter FilterField -> Maybe [FilterValue]
requestedNames _ EmptyFilter = Just []
requestedNames namefield (OrFilter flts) =
liftM concat $ mapM (requestedNames namefield) flts
requestedNames namefield (EQFilter fld val) =
if namefield == fld
then Just [val]
else Nothing
requestedNames _ _ = Nothing
type FilterConstructor = String -> [Either String Integer] -> Filter FilterField
makeSimpleFilter :: String -> [Either String Integer] -> Filter FilterField
makeSimpleFilter _ [] = EmptyFilter
makeSimpleFilter namefield vals =
OrFilter $ map (EQFilter namefield . either QuotedString NumericValue) vals
reSpecialSymbols :: String
reSpecialSymbols = "\\.|()[]"
quoteForRegex :: String -> String
quoteForRegex s = s >>= \x ->
if x `elem` reSpecialSymbols then ['\\', x] else [x]
makeHostnameFilter :: String -> [Either String Integer] -> Filter FilterField
makeHostnameFilter _ [] = EmptyFilter
makeHostnameFilter namefield vals =
OrFilter . flip map vals
$ either (RegexpFilter namefield . fromJust . mkRegex
. (\ s -> "^(" ++ s ++ "|" ++ s ++ "\\..*)$")
. quoteForRegex)
(EQFilter namefield . NumericValue)