1 {-# LANGUAGE TemplateHaskell, CPP #-}
3 {-| Implementation of the Ganeti Query2 language.
9 Copyright (C) 2012 Google Inc.
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
16 This program is distributed in the hope that it will be useful, but
17 WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
28 module Ganeti.Query.Language
32 , FilterRegex -- note: we don't export the constructor, must use helpers
40 , QueryFieldsResult(..)
53 import Control.Applicative
55 import Data.Traversable (Traversable, traverse, fmapDefault, foldMapDefault)
56 import Data.Ratio (numerator, denominator)
57 import Text.JSON.Pretty (pp_value)
58 import Text.JSON.Types
61 import qualified Text.Regex.PCRE as PCRE
64 import qualified Ganeti.Constants as C
68 -- * THH declarations, that require ordering.
70 -- | Status of a query field.
71 $(declareIADT "ResultStatus"
72 [ ("RSNormal", 'C.rsNormal )
73 , ("RSUnknown", 'C.rsUnknown )
74 , ("RSNoData", 'C.rsNodata )
75 , ("RSUnavail", 'C.rsUnavail )
76 , ("RSOffline", 'C.rsOffline )
78 $(makeJSONInstance ''ResultStatus)
80 -- | Check that ResultStatus is success or fail with descriptive
82 checkRS :: (Monad m) => ResultStatus -> a -> m a
83 checkRS RSNormal val = return val
84 checkRS RSUnknown _ = fail "Unknown field"
85 checkRS RSNoData _ = fail "No data for a field"
86 checkRS RSUnavail _ = fail "Ganeti reports unavailable data"
87 checkRS RSOffline _ = fail "Ganeti reports resource as offline"
89 -- | Type of a query field.
90 $(declareSADT "FieldType"
91 [ ("QFTUnknown", 'C.qftUnknown )
92 , ("QFTText", 'C.qftText )
93 , ("QFTBool", 'C.qftBool )
94 , ("QFTNumber", 'C.qftNumber )
95 , ("QFTUnit", 'C.qftUnit )
96 , ("QFTTimestamp", 'C.qftTimestamp )
97 , ("QFTOther", 'C.qftOther )
99 $(makeJSONInstance ''FieldType)
101 -- | Supported items on which Qlang works.
102 $(declareSADT "ItemType"
103 [ ("QRCluster", 'C.qrCluster )
104 , ("QRInstance", 'C.qrInstance )
105 , ("QRNode", 'C.qrNode )
106 , ("QRLock", 'C.qrLock )
107 , ("QRGroup", 'C.qrGroup )
109 , ("QRJob", 'C.qrJob )
110 , ("QRExport", 'C.qrExport )
112 $(makeJSONInstance ''ItemType)
114 -- * Sub data types for query2 queries and responses.
116 -- | Internal type of a regex expression (not exported).
117 #ifndef NO_REGEX_PCRE
118 type RegexType = PCRE.Regex
123 -- | List of requested fields.
124 type Fields = [ String ]
126 -- | Query2 filter expression. It's a parameteric type since we can
127 -- filter different \"things\"; e.g. field names, or actual field
130 = EmptyFilter -- ^ No filter at all
131 | AndFilter [ Filter a ] -- ^ & [<expression>, ...]
132 | OrFilter [ Filter a ] -- ^ | [<expression>, ...]
133 | NotFilter (Filter a) -- ^ ! <expression>
134 | TrueFilter a -- ^ ? <field>
135 | EQFilter a FilterValue -- ^ (=|!=) <field> <value>
136 | LTFilter a FilterValue -- ^ < <field> <value>
137 | GTFilter a FilterValue -- ^ > <field> <value>
138 | LEFilter a FilterValue -- ^ <= <field> <value>
139 | GEFilter a FilterValue -- ^ >= <field> <value>
140 | RegexpFilter a FilterRegex -- ^ =~ <field> <regexp>
141 | ContainsFilter a FilterValue -- ^ =[] <list-field> <value>
142 deriving (Show, Read, Eq)
144 -- | Serialiser for the 'Filter' data type.
145 showFilter :: (JSON a) => Filter a -> JSValue
146 showFilter (EmptyFilter) = JSNull
147 showFilter (AndFilter exprs) =
148 JSArray $ showJSON C.qlangOpAnd : map showJSON exprs
149 showFilter (OrFilter exprs) =
150 JSArray $ showJSON C.qlangOpOr : map showJSON exprs
151 showFilter (NotFilter flt) =
152 JSArray [showJSON C.qlangOpNot, showJSON flt]
153 showFilter (TrueFilter field) =
154 JSArray [showJSON C.qlangOpTrue, showJSON field]
155 showFilter (EQFilter field value) =
156 JSArray [showJSON C.qlangOpEqual, showJSON field, showJSON value]
157 showFilter (LTFilter field value) =
158 JSArray [showJSON C.qlangOpLt, showJSON field, showJSON value]
159 showFilter (GTFilter field value) =
160 JSArray [showJSON C.qlangOpGt, showJSON field, showJSON value]
161 showFilter (LEFilter field value) =
162 JSArray [showJSON C.qlangOpLe, showJSON field, showJSON value]
163 showFilter (GEFilter field value) =
164 JSArray [showJSON C.qlangOpGe, showJSON field, showJSON value]
165 showFilter (RegexpFilter field regexp) =
166 JSArray [showJSON C.qlangOpRegexp, showJSON field, showJSON regexp]
167 showFilter (ContainsFilter field value) =
168 JSArray [showJSON C.qlangOpContains, showJSON field, showJSON value]
170 -- | Deserializer for the 'Filter' data type.
171 readFilter :: (JSON a) => JSValue -> Result (Filter a)
172 readFilter JSNull = Ok EmptyFilter
173 readFilter (JSArray (JSString op:args)) =
174 readFilterArray (fromJSString op) args
176 Error $ "Cannot deserialise filter: expected array [string, args], got " ++
179 -- | Helper to deserialise an array corresponding to a single filter
180 -- and return the built filter. Note this looks generic but is (at
181 -- least currently) only used for the NotFilter.
182 readFilterArg :: (JSON a) =>
183 (Filter a -> Filter a) -- ^ Constructor
184 -> [JSValue] -- ^ Single argument
186 readFilterArg constr [flt] = constr <$> readJSON flt
187 readFilterArg _ v = Error $ "Cannot deserialise field, expected [filter]" ++
188 " but got " ++ show (pp_value (showJSON v))
190 -- | Helper to deserialise an array corresponding to a single field
191 -- and return the built filter.
192 readFilterField :: (JSON a) =>
193 (a -> Filter a) -- ^ Constructor
194 -> [JSValue] -- ^ Single argument
196 readFilterField constr [field] = constr <$> readJSON field
197 readFilterField _ v = Error $ "Cannot deserialise field, expected" ++
198 " [fieldname] but got " ++
199 show (pp_value (showJSON v))
201 -- | Helper to deserialise an array corresponding to a field and
202 -- value, returning the built filter.
203 readFilterFieldValue :: (JSON a, JSON b) =>
204 (a -> b -> Filter a) -- ^ Constructor
205 -> [JSValue] -- ^ Arguments array
207 readFilterFieldValue constr [field, value] =
208 constr <$> readJSON field <*> readJSON value
209 readFilterFieldValue _ v =
210 Error $ "Cannot deserialise field/value pair, expected [fieldname, value]" ++
211 " but got " ++ show (pp_value (showJSON v))
213 -- | Inner deserialiser for 'Filter'.
214 readFilterArray :: (JSON a) => String -> [JSValue] -> Result (Filter a)
215 readFilterArray op args
216 | op == C.qlangOpAnd = AndFilter <$> mapM readJSON args
217 | op == C.qlangOpOr = OrFilter <$> mapM readJSON args
218 | op == C.qlangOpNot = readFilterArg NotFilter args
219 | op == C.qlangOpTrue = readFilterField TrueFilter args
220 | op == C.qlangOpEqual = readFilterFieldValue EQFilter args
221 | op == C.qlangOpLt = readFilterFieldValue LTFilter args
222 | op == C.qlangOpGt = readFilterFieldValue GTFilter args
223 | op == C.qlangOpLe = readFilterFieldValue LEFilter args
224 | op == C.qlangOpGe = readFilterFieldValue GEFilter args
225 | op == C.qlangOpRegexp = readFilterFieldValue RegexpFilter args
226 | op == C.qlangOpContains = readFilterFieldValue ContainsFilter args
227 | otherwise = Error $ "Unknown filter operand '" ++ op ++ "'"
229 instance (JSON a) => JSON (Filter a) where
230 showJSON = showFilter
231 readJSON = readFilter
233 -- Traversable implementation for 'Filter'.
234 traverseFlt :: (Applicative f) => (a -> f b) -> Filter a -> f (Filter b)
235 traverseFlt _ EmptyFilter = pure EmptyFilter
236 traverseFlt f (AndFilter flts) = AndFilter <$> traverse (traverseFlt f) flts
237 traverseFlt f (OrFilter flts) = OrFilter <$> traverse (traverseFlt f) flts
238 traverseFlt f (NotFilter flt) = NotFilter <$> traverseFlt f flt
239 traverseFlt f (TrueFilter a) = TrueFilter <$> f a
240 traverseFlt f (EQFilter a fval) = EQFilter <$> f a <*> pure fval
241 traverseFlt f (LTFilter a fval) = LTFilter <$> f a <*> pure fval
242 traverseFlt f (GTFilter a fval) = GTFilter <$> f a <*> pure fval
243 traverseFlt f (LEFilter a fval) = LEFilter <$> f a <*> pure fval
244 traverseFlt f (GEFilter a fval) = GEFilter <$> f a <*> pure fval
245 traverseFlt f (RegexpFilter a re) = RegexpFilter <$> f a <*> pure re
246 traverseFlt f (ContainsFilter a fval) = ContainsFilter <$> f a <*> pure fval
248 instance Traversable Filter where
249 traverse = traverseFlt
251 instance Functor Filter where
254 instance Foldable Filter where
255 foldMap = foldMapDefault
257 -- | Field name to filter on.
258 type FilterField = String
260 -- | Value to compare the field value to, for filtering purposes.
261 data FilterValue = QuotedString String
262 | NumericValue Integer
263 deriving (Read, Show, Eq)
265 -- | Serialiser for 'FilterValue'. The Python code just sends this to
266 -- JSON as-is, so we'll do the same.
267 showFilterValue :: FilterValue -> JSValue
268 showFilterValue (QuotedString str) = showJSON str
269 showFilterValue (NumericValue val) = showJSON val
271 -- | Decoder for 'FilterValue'. We have to see what it contains, since
272 -- the context doesn't give us hints on what to expect.
273 readFilterValue :: JSValue -> Result FilterValue
274 readFilterValue (JSString a) = Ok . QuotedString $ fromJSString a
275 readFilterValue (JSRational _ x) =
276 if denominator x /= 1
277 then Error $ "Cannot deserialise numeric filter value," ++
278 " expecting integral but got a fractional value: " ++
280 else Ok . NumericValue $ numerator x
281 readFilterValue v = Error $ "Cannot deserialise filter value, expecting" ++
282 " string or integer, got " ++ show (pp_value v)
284 instance JSON FilterValue where
285 showJSON = showFilterValue
286 readJSON = readFilterValue
288 -- | Regexp to apply to the filter value, for filtering purposes. It
289 -- holds both the string format, and the \"compiled\" format, so that
290 -- we don't re-compile the regex at each match attempt.
291 data FilterRegex = FilterRegex
292 { stringRegex :: String -- ^ The string version of the regex
293 , compiledRegex :: RegexType -- ^ The compiled regex
296 -- | Builder for 'FilterRegex'. We always attempt to compile the
297 -- regular expression on the initialisation of the data structure;
298 -- this might fail, if the RE is not well-formed.
299 mkRegex :: (Monad m) => String -> m FilterRegex
300 #ifndef NO_REGEX_PCRE
302 compiled <- case PCRE.getVersion of
303 Nothing -> fail $ "regex-pcre library compiled without" ++
304 " libpcre, regex functionality not available"
305 _ -> PCRE.makeRegexM str
306 return $ FilterRegex str compiled
309 fail $ "regex-pcre not found at compile time," ++
310 " regex functionality not available"
313 -- | 'Show' instance: we show the constructor plus the string version
315 instance Show FilterRegex where
316 show (FilterRegex re _) = "mkRegex " ++ show re
318 -- | 'Read' instance: we manually read \"mkRegex\" followed by a
319 -- string, and build the 'FilterRegex' using that.
320 instance Read FilterRegex where
322 ("mkRegex", s') <- lex str
323 (re, s'') <- reads s'
324 filterre <- mkRegex re
325 return (filterre, s'')
327 -- | 'Eq' instance: we only compare the string versions of the regexes.
328 instance Eq FilterRegex where
329 (FilterRegex re1 _) == (FilterRegex re2 _) = re1 == re2
331 -- | 'JSON' instance: like for show and read instances, we work only
332 -- with the string component.
333 instance JSON FilterRegex where
334 showJSON (FilterRegex re _) = showJSON re
339 -- | Name of a field.
340 type FieldName = String
341 -- | Title of a field, when represented in tabular format.
342 type FieldTitle = String
343 -- | Human redable description of a field.
344 type FieldDoc = String
346 -- | Definition of a field.
347 $(buildObject "FieldDefinition" "fdef"
348 [ simpleField "name" [t| FieldName |] -- FIXME: the name has restrictions
349 , simpleField "title" [t| FieldTitle |]
350 , simpleField "kind" [t| FieldType |]
351 , simpleField "doc" [t| FieldDoc |]
354 --- | Single field entry result.
355 data ResultEntry = ResultEntry
356 { rentryStatus :: ResultStatus -- ^ The result status
357 , rentryValue :: Maybe ResultValue -- ^ The (optional) result value
358 } deriving (Show, Read, Eq)
360 instance JSON ResultEntry where
361 showJSON (ResultEntry rs rv) =
362 showJSON (showJSON rs, maybe JSNull showJSON rv)
364 (rs, rv) <- readJSON v
366 JSNull -> return Nothing
367 x -> Just <$> readJSON x
368 return $ ResultEntry rs rv'
370 -- | The type of one result row.
371 type ResultRow = [ ResultEntry ]
373 -- | Value of a field, in json encoding.
374 -- (its type will be depending on ResultStatus and FieldType)
375 type ResultValue = JSValue
377 -- * Main Qlang queries and responses.
380 data Query = Query ItemType Fields (Filter FilterField)
383 $(buildObject "QueryResult" "qres"
384 [ simpleField "fields" [t| [ FieldDefinition ] |]
385 , simpleField "data" [t| [ ResultRow ] |]
388 -- | Query2 Fields query.
389 -- (to get supported fields names, descriptions, and types)
390 data QueryFields = QueryFields ItemType Fields
392 -- | Query2 Fields result.
393 $(buildObject "QueryFieldsResult" "qfieldres"
394 [ simpleField "fields" [t| [FieldDefinition ] |]