1 {-# LANGUAGE TemplateHaskell #-}
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
34 , QueryFieldsResult(..)
40 import Control.Applicative
41 import Data.Ratio (numerator, denominator)
42 import Text.JSON.Pretty (pp_value)
43 import Text.JSON.Types
46 import qualified Ganeti.Constants as C
49 -- * THH declarations, that require ordering.
51 -- | Status of a query field.
52 $(declareIADT "ResultStatus"
53 [ ("RSNormal", 'C.rsNormal )
54 , ("RSUnknown", 'C.rsUnknown )
55 , ("RSNoData", 'C.rsNodata )
56 , ("RSUnavail", 'C.rsUnavail )
57 , ("RSOffline", 'C.rsOffline )
59 $(makeJSONInstance ''ResultStatus)
61 -- | Type of a query field.
62 $(declareSADT "FieldType"
63 [ ("QFTUnknown", 'C.qftUnknown )
64 , ("QFTText", 'C.qftText )
65 , ("QFTBool", 'C.qftBool )
66 , ("QFTNumber", 'C.qftNumber )
67 , ("QFTUnit", 'C.qftUnit )
68 , ("QFTTimestamp", 'C.qftTimestamp )
69 , ("QFTOther", 'C.qftOther )
71 $(makeJSONInstance ''FieldType)
73 -- | Supported items on which Qlang works.
74 $(declareSADT "ItemType"
75 [ ("QRCluster", 'C.qrCluster )
76 , ("QRInstance", 'C.qrInstance )
77 , ("QRNode", 'C.qrNode )
78 , ("QRLock", 'C.qrLock )
79 , ("QRGroup", 'C.qrGroup )
81 , ("QRJob", 'C.qrJob )
82 , ("QRExport", 'C.qrExport )
84 $(makeJSONInstance ''ItemType)
86 -- * Main Qlang queries and responses.
89 data Query = Query ItemType Fields (Maybe Filter)
92 data QueryResult = QueryResult [ FieldDefinition ] [ ResultEntry ]
94 -- | Query2 Fields query.
95 -- (to get supported fields names, descriptions, and types)
96 data QueryFields = QueryFields ItemType Fields
98 -- | Query2 Fields result.
99 data QueryFieldsResult = QueryFieldsResult [ FieldDefinition ]
101 -- * Sub data types for query2 queries and responses.
103 -- | List of requested fields.
104 type Fields = [ String ]
106 -- | Query2 filter expression.
108 = EmptyFilter -- ^ No filter at all
109 | AndFilter [ Filter ] -- ^ & [<expression>, ...]
110 | OrFilter [ Filter ] -- ^ | [<expression>, ...]
111 | NotFilter Filter -- ^ ! <expression>
112 | TrueFilter FilterField -- ^ ? <field>
113 | EQFilter FilterField FilterValue -- ^ (=|!=) <field> <value>
114 | LTFilter FilterField FilterValue -- ^ < <field> <value>
115 | GTFilter FilterField FilterValue -- ^ > <field> <value>
116 | LEFilter FilterField FilterValue -- ^ <= <field> <value>
117 | GEFilter FilterField FilterValue -- ^ >= <field> <value>
118 | RegexpFilter FilterField FilterRegexp -- ^ =~ <field> <regexp>
119 | ContainsFilter FilterField FilterValue -- ^ =[] <list-field> <value>
120 deriving (Show, Read, Eq)
122 -- | Serialiser for the 'Filter' data type.
123 showFilter :: Filter -> JSValue
124 showFilter (EmptyFilter) = JSNull
125 showFilter (AndFilter exprs) =
126 JSArray $ (showJSON C.qlangOpAnd):(map showJSON exprs)
127 showFilter (OrFilter exprs) =
128 JSArray $ (showJSON C.qlangOpOr):(map showJSON exprs)
129 showFilter (NotFilter flt) =
130 JSArray [showJSON C.qlangOpNot, showJSON flt]
131 showFilter (TrueFilter field) =
132 JSArray [showJSON C.qlangOpTrue, showJSON field]
133 showFilter (EQFilter field value) =
134 JSArray [showJSON C.qlangOpEqual, showJSON field, showJSON value]
135 showFilter (LTFilter field value) =
136 JSArray [showJSON C.qlangOpLt, showJSON field, showJSON value]
137 showFilter (GTFilter field value) =
138 JSArray [showJSON C.qlangOpGt, showJSON field, showJSON value]
139 showFilter (LEFilter field value) =
140 JSArray [showJSON C.qlangOpLe, showJSON field, showJSON value]
141 showFilter (GEFilter field value) =
142 JSArray [showJSON C.qlangOpGe, showJSON field, showJSON value]
143 showFilter (RegexpFilter field regexp) =
144 JSArray [showJSON C.qlangOpRegexp, showJSON field, showJSON regexp]
145 showFilter (ContainsFilter field value) =
146 JSArray [showJSON C.qlangOpContains, showJSON field, showJSON value]
148 -- | Deserializer for the 'Filter' data type.
149 readFilter :: JSValue -> Result Filter
150 readFilter JSNull = Ok EmptyFilter
151 readFilter (JSArray (JSString op:args)) =
152 readFilterArray (fromJSString op) args
154 Error $ "Cannot deserialise filter: expected array [string, args], got " ++
157 -- | Helper to deserialise an array corresponding to a single filter
158 -- and return the built filter. Note this looks generic but is (at
159 -- least currently) only used for the NotFilter.
160 readFilterArg :: (Filter -> Filter) -- ^ Constructor
161 -> [JSValue] -- ^ Single argument
163 readFilterArg constr [flt] = constr <$> readJSON flt
164 readFilterArg _ v = Error $ "Cannot deserialise field, expected [filter]\
165 \ but got " ++ show (pp_value (showJSON v))
167 -- | Helper to deserialise an array corresponding to a single field
168 -- and return the built filter.
169 readFilterField :: (FilterField -> Filter) -- ^ Constructor
170 -> [JSValue] -- ^ Single argument
172 readFilterField constr [field] = constr <$> readJSON field
173 readFilterField _ v = Error $ "Cannot deserialise field, expected [fieldname]\
174 \ but got " ++ show (pp_value (showJSON v))
176 -- | Helper to deserialise an array corresponding to a field and
177 -- value, returning the built filter.
178 readFilterFieldValue :: (JSON a) =>
179 (FilterField -> a -> Filter) -- ^ Constructor
180 -> [JSValue] -- ^ Arguments array
182 readFilterFieldValue constr [field, value] =
183 constr <$> readJSON field <*> readJSON value
184 readFilterFieldValue _ v =
185 Error $ "Cannot deserialise field/value pair, expected [fieldname, value]\
186 \ but got " ++ show (pp_value (showJSON v))
188 -- | Inner deserialiser for 'Filter'.
189 readFilterArray :: String -> [JSValue] -> Result Filter
190 readFilterArray op args
191 | op == C.qlangOpAnd = AndFilter <$> mapM readJSON args
192 | op == C.qlangOpOr = OrFilter <$> mapM readJSON args
193 | op == C.qlangOpNot = readFilterArg NotFilter args
194 | op == C.qlangOpTrue = readFilterField TrueFilter args
195 | op == C.qlangOpEqual = readFilterFieldValue EQFilter args
196 | op == C.qlangOpLt = readFilterFieldValue LTFilter args
197 | op == C.qlangOpGt = readFilterFieldValue GTFilter args
198 | op == C.qlangOpLe = readFilterFieldValue LEFilter args
199 | op == C.qlangOpGe = readFilterFieldValue GEFilter args
200 | op == C.qlangOpRegexp = readFilterFieldValue RegexpFilter args
201 | op == C.qlangOpContains = readFilterFieldValue ContainsFilter args
202 | otherwise = Error $ "Unknown filter operand '" ++ op ++ "'"
204 instance JSON Filter where
205 showJSON = showFilter
206 readJSON = readFilter
208 -- | Field name to filter on.
209 type FilterField = String
211 -- | Value to compare the field value to, for filtering purposes.
212 data FilterValue = QuotedString String
213 | NumericValue Integer
214 deriving (Read, Show, Eq)
216 -- | Serialiser for 'FilterValue'. The Python code just sends this to
217 -- JSON as-is, so we'll do the same.
218 showFilterValue :: FilterValue -> JSValue
219 showFilterValue (QuotedString str) = showJSON str
220 showFilterValue (NumericValue val) = showJSON val
222 -- | Decoder for 'FilterValue'. We have to see what it contains, since
223 -- the context doesn't give us hints on what to expect.
224 readFilterValue :: JSValue -> Result FilterValue
225 readFilterValue (JSString a) = Ok . QuotedString $ fromJSString a
226 readFilterValue (JSRational _ x) =
227 if denominator x /= 1
228 then Error $ "Cannot deserialise numeric filter value,\
229 \ expecting integral but\
230 \ got a fractional value: " ++ show x
231 else Ok . NumericValue $ numerator x
232 readFilterValue v = Error $ "Cannot deserialise filter value, expecting\
233 \ string or integer, got " ++ show (pp_value v)
235 instance JSON FilterValue where
236 showJSON = showFilterValue
237 readJSON = readFilterValue
239 -- | Regexp to apply to the filter value, for filteriong purposes.
240 type FilterRegexp = String
242 -- | Definition of a field.
243 data FieldDefinition = FieldDefinition FieldName FieldTitle FieldType FieldDoc
245 -- | Name of a field.
246 type FieldName = String
247 -- | Title of a field, when represented in tabular format.
248 type FieldTitle = String
249 -- | Human redable description of a field.
250 type FieldDoc = String
252 --- | Single field entry result.
253 data ResultEntry = ResultEntry ResultStatus (Maybe ResultValue)
255 -- | Value of a field, in json encoding.
256 -- (its type will be depending on ResultStatus and FieldType)
257 type ResultValue = JSValue