module Ganeti.Query.Language
( Filter(..)
, FilterField
, FilterValue(..)
, FilterRegex
, mkRegex
, stringRegex
, compiledRegex
, Fields
, Query(..)
, QueryResult(..)
, QueryFields(..)
, QueryFieldsResult(..)
, FieldName
, FieldTitle
, FieldType(..)
, FieldDoc
, FieldDefinition(..)
, ResultEntry(..)
, ResultStatus(..)
, ResultValue
, ItemType(..)
, QueryTypeOp(..)
, queryTypeOpToRaw
, QueryTypeLuxi(..)
, checkRS
) where
import Control.Applicative
import Control.DeepSeq
import Data.Foldable
import Data.Traversable (Traversable, traverse, fmapDefault, foldMapDefault)
import Data.Ratio (numerator, denominator)
import Text.JSON.Pretty (pp_value)
import Text.JSON.Types
import Text.JSON
#ifndef NO_REGEX_PCRE
import qualified Text.Regex.PCRE as PCRE
#endif
import qualified Ganeti.Constants as C
import Ganeti.THH
$(declareIADT "ResultStatus"
[ ("RSNormal", 'C.rsNormal )
, ("RSUnknown", 'C.rsUnknown )
, ("RSNoData", 'C.rsNodata )
, ("RSUnavail", 'C.rsUnavail )
, ("RSOffline", 'C.rsOffline )
])
$(makeJSONInstance ''ResultStatus)
instance NFData ResultStatus
checkRS :: (Monad m) => ResultStatus -> a -> m a
checkRS RSNormal val = return val
checkRS RSUnknown _ = fail "Unknown field"
checkRS RSNoData _ = fail "No data for a field"
checkRS RSUnavail _ = fail "Ganeti reports unavailable data"
checkRS RSOffline _ = fail "Ganeti reports resource as offline"
$(declareSADT "FieldType"
[ ("QFTUnknown", 'C.qftUnknown )
, ("QFTText", 'C.qftText )
, ("QFTBool", 'C.qftBool )
, ("QFTNumber", 'C.qftNumber )
, ("QFTUnit", 'C.qftUnit )
, ("QFTTimestamp", 'C.qftTimestamp )
, ("QFTOther", 'C.qftOther )
])
$(makeJSONInstance ''FieldType)
$(declareSADT "QueryTypeOp"
[ ("QRCluster", 'C.qrCluster )
, ("QRInstance", 'C.qrInstance )
, ("QRNode", 'C.qrNode )
, ("QRGroup", 'C.qrGroup )
, ("QROs", 'C.qrOs )
, ("QRExport", 'C.qrExport )
, ("QRNetwork", 'C.qrNetwork )
])
$(makeJSONInstance ''QueryTypeOp)
$(declareSADT "QueryTypeLuxi"
[ ("QRLock", 'C.qrLock )
, ("QRJob", 'C.qrJob )
])
$(makeJSONInstance ''QueryTypeLuxi)
data ItemType = ItemTypeLuxi QueryTypeLuxi
| ItemTypeOpCode QueryTypeOp
deriving (Show, Eq)
decodeItemType :: (Monad m) => JSValue -> m ItemType
decodeItemType (JSString s) =
case queryTypeOpFromRaw s' of
Just v -> return $ ItemTypeOpCode v
Nothing ->
case queryTypeLuxiFromRaw s' of
Just v -> return $ ItemTypeLuxi v
Nothing ->
fail $ "Can't parse value '" ++ s' ++ "' as neither"
++ "QueryTypeLuxi nor QueryTypeOp"
where s' = fromJSString s
decodeItemType v = fail $ "Invalid value '" ++ show (pp_value v) ++
"for query type"
instance JSON ItemType where
showJSON (ItemTypeLuxi x) = showJSON x
showJSON (ItemTypeOpCode y) = showJSON y
readJSON = decodeItemType
#ifndef NO_REGEX_PCRE
type RegexType = PCRE.Regex
#else
type RegexType = ()
#endif
type Fields = [ String ]
data Filter a
= EmptyFilter
| AndFilter [ Filter a ]
| OrFilter [ Filter a ]
| NotFilter (Filter a)
| TrueFilter a
| EQFilter a FilterValue
| LTFilter a FilterValue
| GTFilter a FilterValue
| LEFilter a FilterValue
| GEFilter a FilterValue
| RegexpFilter a FilterRegex
| ContainsFilter a FilterValue
deriving (Show, Eq)
showFilter :: (JSON a) => Filter a -> JSValue
showFilter (EmptyFilter) = JSNull
showFilter (AndFilter exprs) =
JSArray $ showJSON C.qlangOpAnd : map showJSON exprs
showFilter (OrFilter exprs) =
JSArray $ showJSON C.qlangOpOr : map showJSON exprs
showFilter (NotFilter flt) =
JSArray [showJSON C.qlangOpNot, showJSON flt]
showFilter (TrueFilter field) =
JSArray [showJSON C.qlangOpTrue, showJSON field]
showFilter (EQFilter field value) =
JSArray [showJSON C.qlangOpEqual, showJSON field, showJSON value]
showFilter (LTFilter field value) =
JSArray [showJSON C.qlangOpLt, showJSON field, showJSON value]
showFilter (GTFilter field value) =
JSArray [showJSON C.qlangOpGt, showJSON field, showJSON value]
showFilter (LEFilter field value) =
JSArray [showJSON C.qlangOpLe, showJSON field, showJSON value]
showFilter (GEFilter field value) =
JSArray [showJSON C.qlangOpGe, showJSON field, showJSON value]
showFilter (RegexpFilter field regexp) =
JSArray [showJSON C.qlangOpRegexp, showJSON field, showJSON regexp]
showFilter (ContainsFilter field value) =
JSArray [showJSON C.qlangOpContains, showJSON field, showJSON value]
readFilter :: (JSON a) => JSValue -> Result (Filter a)
readFilter JSNull = Ok EmptyFilter
readFilter (JSArray (JSString op:args)) =
readFilterArray (fromJSString op) args
readFilter v =
Error $ "Cannot deserialise filter: expected array [string, args], got " ++
show (pp_value v)
readFilterArg :: (JSON a) =>
(Filter a -> Filter a)
-> [JSValue]
-> Result (Filter a)
readFilterArg constr [flt] = constr <$> readJSON flt
readFilterArg _ v = Error $ "Cannot deserialise field, expected [filter]" ++
" but got " ++ show (pp_value (showJSON v))
readFilterField :: (JSON a) =>
(a -> Filter a)
-> [JSValue]
-> Result (Filter a)
readFilterField constr [field] = constr <$> readJSON field
readFilterField _ v = Error $ "Cannot deserialise field, expected" ++
" [fieldname] but got " ++
show (pp_value (showJSON v))
readFilterFieldValue :: (JSON a, JSON b) =>
(a -> b -> Filter a)
-> [JSValue]
-> Result (Filter a)
readFilterFieldValue constr [field, value] =
constr <$> readJSON field <*> readJSON value
readFilterFieldValue _ v =
Error $ "Cannot deserialise field/value pair, expected [fieldname, value]" ++
" but got " ++ show (pp_value (showJSON v))
readFilterArray :: (JSON a) => String -> [JSValue] -> Result (Filter a)
readFilterArray op args
| op == C.qlangOpAnd = AndFilter <$> mapM readJSON args
| op == C.qlangOpOr = OrFilter <$> mapM readJSON args
| op == C.qlangOpNot = readFilterArg NotFilter args
| op == C.qlangOpTrue = readFilterField TrueFilter args
| op == C.qlangOpEqual = readFilterFieldValue EQFilter args
| op == C.qlangOpLt = readFilterFieldValue LTFilter args
| op == C.qlangOpGt = readFilterFieldValue GTFilter args
| op == C.qlangOpLe = readFilterFieldValue LEFilter args
| op == C.qlangOpGe = readFilterFieldValue GEFilter args
| op == C.qlangOpRegexp = readFilterFieldValue RegexpFilter args
| op == C.qlangOpContains = readFilterFieldValue ContainsFilter args
| otherwise = Error $ "Unknown filter operand '" ++ op ++ "'"
instance (JSON a) => JSON (Filter a) where
showJSON = showFilter
readJSON = readFilter
traverseFlt :: (Applicative f) => (a -> f b) -> Filter a -> f (Filter b)
traverseFlt _ EmptyFilter = pure EmptyFilter
traverseFlt f (AndFilter flts) = AndFilter <$> traverse (traverseFlt f) flts
traverseFlt f (OrFilter flts) = OrFilter <$> traverse (traverseFlt f) flts
traverseFlt f (NotFilter flt) = NotFilter <$> traverseFlt f flt
traverseFlt f (TrueFilter a) = TrueFilter <$> f a
traverseFlt f (EQFilter a fval) = EQFilter <$> f a <*> pure fval
traverseFlt f (LTFilter a fval) = LTFilter <$> f a <*> pure fval
traverseFlt f (GTFilter a fval) = GTFilter <$> f a <*> pure fval
traverseFlt f (LEFilter a fval) = LEFilter <$> f a <*> pure fval
traverseFlt f (GEFilter a fval) = GEFilter <$> f a <*> pure fval
traverseFlt f (RegexpFilter a re) = RegexpFilter <$> f a <*> pure re
traverseFlt f (ContainsFilter a fval) = ContainsFilter <$> f a <*> pure fval
instance Traversable Filter where
traverse = traverseFlt
instance Functor Filter where
fmap = fmapDefault
instance Foldable Filter where
foldMap = foldMapDefault
type FilterField = String
data FilterValue = QuotedString String
| NumericValue Integer
deriving (Show, Eq)
showFilterValue :: FilterValue -> JSValue
showFilterValue (QuotedString str) = showJSON str
showFilterValue (NumericValue val) = showJSON val
readFilterValue :: JSValue -> Result FilterValue
readFilterValue (JSString a) = Ok . QuotedString $ fromJSString a
readFilterValue (JSRational _ x) =
if denominator x /= 1
then Error $ "Cannot deserialise numeric filter value," ++
" expecting integral but got a fractional value: " ++
show x
else Ok . NumericValue $ numerator x
readFilterValue v = Error $ "Cannot deserialise filter value, expecting" ++
" string or integer, got " ++ show (pp_value v)
instance JSON FilterValue where
showJSON = showFilterValue
readJSON = readFilterValue
data FilterRegex = FilterRegex
{ stringRegex :: String
, compiledRegex :: RegexType
}
mkRegex :: (Monad m) => String -> m FilterRegex
#ifndef NO_REGEX_PCRE
mkRegex str = do
compiled <- case PCRE.getVersion of
Nothing -> fail $ "regex-pcre library compiled without" ++
" libpcre, regex functionality not available"
_ -> PCRE.makeRegexM str
return $ FilterRegex str compiled
#else
mkRegex _ =
fail $ "regex-pcre not found at compile time," ++
" regex functionality not available"
#endif
instance Show FilterRegex where
show (FilterRegex re _) = "mkRegex " ++ show re
instance Eq FilterRegex where
(FilterRegex re1 _) == (FilterRegex re2 _) = re1 == re2
instance JSON FilterRegex where
showJSON (FilterRegex re _) = showJSON re
readJSON s = readJSON s >>= mkRegex
type FieldName = String
type FieldTitle = String
type FieldDoc = String
$(buildObject "FieldDefinition" "fdef"
[ simpleField "name" [t| FieldName |]
, simpleField "title" [t| FieldTitle |]
, simpleField "kind" [t| FieldType |]
, simpleField "doc" [t| FieldDoc |]
])
data ResultEntry = ResultEntry
{ rentryStatus :: ResultStatus
, rentryValue :: Maybe ResultValue
} deriving (Show, Eq)
instance NFData ResultEntry where
rnf (ResultEntry rs rv) = rnf rs `seq` rnf rv
instance JSON ResultEntry where
showJSON (ResultEntry rs rv) =
showJSON (showJSON rs, maybe JSNull showJSON rv)
readJSON v = do
(rs, rv) <- readJSON v
rv' <- case rv of
JSNull -> return Nothing
x -> Just <$> readJSON x
return $ ResultEntry rs rv'
type ResultRow = [ ResultEntry ]
type ResultValue = JSValue
data Query = Query ItemType Fields (Filter FilterField)
$(buildObject "QueryResult" "qres"
[ simpleField "fields" [t| [ FieldDefinition ] |]
, simpleField "data" [t| [ ResultRow ] |]
])
data QueryFields = QueryFields ItemType Fields
$(buildObject "QueryFieldsResult" "qfieldres"
[ simpleField "fields" [t| [FieldDefinition ] |]
])