Package Products :: Package ZenEvents :: Module WhereClause
[hide private]
[frames] | no frames]

Source Code for Module Products.ZenEvents.WhereClause

  1  ########################################################################### 
  2  # 
  3  # This program is part of Zenoss Core, an open source monitoring platform. 
  4  # Copyright (C) 2007, Zenoss Inc. 
  5  # 
  6  # This program is free software; you can redistribute it and/or modify it 
  7  # under the terms of the GNU General Public License version 2 or (at your 
  8  # option) any later version as published by the Free Software Foundation. 
  9  # 
 10  # For complete information please visit: http://www.zenoss.com/oss/ 
 11  # 
 12  ########################################################################### 
 13  import types 
 14  import logging 
 15  from Products.ZenUtils.jsonutils import json 
 16   
 17  log = logging.getLogger('zen.WhereClause') 
 18   
 19  new_name_mapping = { 
 20      'eventClass':'evt.event_class', 
 21      'summary':'evt.summary', 
 22      'message':'evt.message', 
 23      'eventKey':'evt.event_key', 
 24      'agent':'evt.agent', 
 25      'manager':'evt.monitor', 
 26      'severity':'evt.severity', 
 27      'eventState':'evt.status', 
 28      'count':'evt.count', 
 29      'prodState':'dev.production_state', 
 30      'device':'elem.name', 
 31      'devicePriority':'dev.priority', 
 32      'component':'sub_elem.name', 
 33      'eventClassKey':'evt.event_class_key', 
 34      'priority':'evt.syslog_priority', 
 35      'facility':'evt.syslog_facility', 
 36      'ntevid':'evt.nt_event_code', 
 37      'ownerId':'evt.current_user_name', 
 38      'deviceClass':'dev.device_class', 
 39      'systems':'dev.systems', 
 40      'deviceGroups':'dev.groups', 
 41      'ipAddress':'dev.ip_address', 
 42      'location':'dev.location', 
 43  } 
44 45 46 -def getName(str):
47 # lookup the old to new map 48 if str in new_name_mapping: 49 return new_name_mapping[str] 50 # don't know a better way to error yet. 51 return str
52
53 -def getValue(val):
54 # escape things 55 if isinstance(val, basestring): 56 return '"%s"' % val.replace('"', '\"') 57 else: 58 return val
59
60 -def getEndsWith(name, value):
61 return "{name}.endswith({value})".format( 62 name=getName(name), 63 value=getValue(value) 64 )
65
66 -def getStartsWith(name, value):
67 return "{name}.startswith({value})".format( 68 name=getName(name), 69 value=getValue(value) 70 )
71
72 -def getIn(name, value):
73 return "{value} in {name}".format( 74 name=getName(name), 75 value=getValue(value) 76 )
77
78 -def getNotIn(name, value):
79 return "{value} not in {name}".format( 80 name=getName(name), 81 value=getValue(value) 82 )
83
84 -def getEquality(name, op, value):
85 return "{name} {op} {value}".format( 86 name=getName(name), 87 op=op, 88 value=getValue(value) 89 )
90 91 92 negativeModes = ( 93 '!', # is not 94 '!~', # does not contain 95 '!^', # does not begin with 96 )
97 98 -def q(s):
99 # turn string "fo'o" -> "'fo''o'" 100 return "'%s'" % "''".join(s.split("'"))
101
102 -class Error(Exception): pass
103
104 -class WhereJavaScript:
105 "Base class for converting to/from javascript" 106 type = 'unknown' 107
108 - def __init__(self, label):
109 self.label = label
110
111 - def genProperties(self, name):
112 return '%s:{type:"%s",label:"%s"}' % (name, self.type, self.label)
113
114 - def buildClause(self, name, value, mode):
115 foundNegativeMatch = False 116 result = [] 117 for v in value: 118 if not v: return None 119 if mode in negativeModes: foundNegativeMatch = True 120 result.append(self.buildClause1(name, v, mode)) 121 if not result: 122 return None 123 if foundNegativeMatch: 124 return ' and '.join(result) 125 else: 126 return ' or '.join(result)
127
128 -class Text(WhereJavaScript):
129 "Convert to/from javascript for text entries" 130 type = 'text' 131
132 - def toJS(self, mode, value):
133 if mode == 'like': 134 if value.startswith('%') and not value.endswith('%'): 135 return '$', [value[1:]] 136 elif not value.startswith('%') and value.endswith('%'): 137 return '^', [value[:-1]] 138 elif value.startswith('%') and value.endswith('%'): 139 return '~', [value[1:-1]] 140 if mode == 'not like': 141 return '!~', [value[1:-1]] 142 if mode == '=': 143 return '', [value] 144 if mode == '!=': 145 return '!', [value]
146
147 - def buildPython(self, name, mode, value):
148 if mode == 'like': 149 if value.startswith('%') and not value.endswith('%'): 150 return getEndsWith(name, value[1:]) 151 elif not value.startswith('%') and value.endswith('%'): 152 return getStartsWith(name, value[:-1]) 153 elif value.startswith('%') and value.endswith('%'): 154 return getIn(name, value[1:-1]) 155 if mode == 'not like': 156 return getNotIn(name, value[1:-1]) 157 if mode == '=': 158 return getEquality(name, '==', value) 159 if mode == '!=': 160 return getEquality(name, '!=', value)
161
162 - def buildClause1(self, name, v, mode):
163 if mode == '~': 164 return "%s like %s" % (name, q('%' + v + '%')) 165 if mode == '^': 166 return "%s like %s" % (name, q(v + '%')) 167 if mode == '$': 168 return "%s like %s" % (name, q('%' + v)) 169 if mode == '!~': 170 return "%s not like %s" % (name, q('%' + v + '%')) 171 if mode == '': 172 return "%s = %s" % (name, q(v)) 173 if mode == '!': 174 return "%s != %s" % (name, q(v))
175
176 177 -class Select(WhereJavaScript):
178 "Convert to/from javascript and where clause element for select entries" 179 type = 'select' 180
181 - def __init__(self, label, options):
182 WhereJavaScript.__init__(self, label) 183 if options: 184 if type(options[0]) != type(()): 185 options = zip(range(len(options)), options) 186 self.options = options
187
188 - def labelFromValue(self, value):
189 return dict(self.options).get(value, 'Unknown')
190
191 - def valueFromLabel(self, value):
192 return dict([(v, l) for l, v in self.options]).get(value, -1)
193
194 - def toJS(self, operator, value):
195 if operator == '=': 196 return ('', [self.labelFromValue(value)]) 197 if operator == '!=': 198 return ('!', [self.labelFromValue(value)]) 199 result = [] 200 if operator in ('<', '>', '<=', '>='): 201 for i, name in self.options: 202 if eval('%d %s %d' % (i, operator, value)): 203 result.append(name) 204 return ('', result)
205
206 - def buildPython(self, name, operator, value):
207 if operator == '=': 208 return getEquality(name, '==', value) 209 else: 210 return getEquality(name, operator, value)
211
212 - def genProperties(self, name):
213 return '%s:{type:"%s",label:"%s", options:%r}' % ( 214 name, self.type, self.label, [s[1] for s in self.options])
215
216 - def buildClause1(self, name, v, mode):
217 v = self.valueFromLabel(v) 218 if type(v) in types.StringTypes: 219 v = q(v) 220 if mode == '': 221 return "%s = %s" % (name, v) 222 else: 223 return "%s != %s" % (name, v)
224
225 - def buildMultiValuePython(self, name, mode, value):
226 227 value = value.strip('%|') 228 229 # if mode == 'like': 230 # if value.startswith('%') and not value.endswith('%'): 231 # return 'any(d.endswith({value}) for d in {name})'.format(value=getValue(bare_value), name=getName(name)) 232 # elif not value.startswith('%') and value.endswith('%'): 233 # return 'any(d.startswith({value}) for d in {name})'.format(value=getValue(bare_value), name=getName(name)) 234 # elif value.startswith('%') and value.endswith('%'): 235 # return 'any({value} in d for d in {name})'.format(value=getValue(bare_value), name=getName(name)) 236 # else: 237 # return getIn(name, bare_value) 238 # 239 # if mode == 'not like': 240 # if value.startswith('%') and not value.endswith('%'): 241 # return 'not any(d.endswith({value}) for d in {name})'.format(value=getValue(bare_value), name=getName(name)) 242 # elif not value.startswith('%') and value.endswith('%'): 243 # return 'not any(d.startswith({value}) for d in {name})'.format(value=getValue(bare_value), name=getName(name)) 244 # elif value.startswith('%') and value.endswith('%'): 245 # return 'not any({value} in d for d in {name})'.format(value=getValue(bare_value), name=getName(name)) 246 # else: 247 # return getNotIn(name, bare_value) 248 249 if mode == 'like': 250 return getIn(name, value) 251 if mode == 'not like': 252 return getNotIn(name, value)
253
254 255 -class Compare(WhereJavaScript):
256 "Convert to/from javascript and where clause elements for numeric comparisons" 257 type = 'compare' 258
259 - def toJS(self, operator, value):
260 return operator, [value]
261
262 - def buildPython(self, name, operator, value):
263 if operator == '=': 264 return getEquality(name, '==', value) 265 else: 266 return getEquality(name, operator, value)
267
268 - def buildClause1(self, name, v, mode):
269 return "%s %s %s" % (name, mode, v)
270
271 272 -class DeviceGroup(Select):
273 - def toJS(self, operator, value):
274 if operator == 'like': 275 return ['', [value[2:-1]]] 276 if operator == 'not like': 277 return ['!', [value[2:-1]]]
278
279 - def buildPython(self, name, operator, value):
280 return self.buildMultiValuePython(name, operator, value)
281
282 - def buildClause1(self, name, v, mode):
283 if mode == '': 284 return "%s like %s" % (name, q('%|' + v + '%')) 285 else: 286 return "%s not like %s" % (name, q('%|' + v + '%'))
287
288 -class EventClass(Select):
289 type = 'evtClass' 290
291 - def toJS(self, operator, value):
292 value = value.rstrip('%') 293 if operator == '=': 294 return ['', [value]] 295 if operator == '!=': 296 return ['!', [value]] 297 if operator == 'like': 298 return ['^', [value]] 299 if operator == 'not like': 300 return ['!^', [value]]
301
302 - def buildPython(self, name, operator, value):
303 304 mode = operator 305 if mode == 'like': 306 if value.startswith('%') and not value.endswith('%'): 307 return getEndsWith(name, value[1:]) 308 elif not value.startswith('%') and value.endswith('%'): 309 return getStartsWith(name, value[:-1]) 310 elif value.startswith('%') and value.endswith('%'): 311 return getIn(name, value[1:-1]) 312 else: 313 return 314 if mode == 'not like': 315 return getNotIn(name, value[1:-1]) 316 if mode == '=': 317 return getEquality(name, '==', value) 318 if mode == '!=': 319 return getEquality(name, '!=', value)
320
321 - def buildClause1(self, name, v, mode):
322 if mode == '': 323 return "%s = %s" % (name, q(v)) 324 elif mode == '^': 325 return "%s like %s" % (name, q(v + '%')) 326 elif mode == '!^': 327 return "%s not like %s" % (name, q(v + '%')) 328 else: 329 return "%s != %s" % (name, q(v))
330
331 332 333 -class Enumerated(Select):
334 "Convert to/from javascript and where clause elements for enumerated types" 335 type='cselect' 336
337 - def toJS(self, operator, value):
338 return operator, [self.labelFromValue(value)]
339
340 - def buildPython(self, name, operator, value):
341 if operator == '=': 342 return getEquality(name, '==', value) 343 else: 344 return getEquality(name, operator, value)
345
346 - def buildClause1(self, name, v, mode):
347 return "%s %s %s" % (name, mode, self.valueFromLabel(v))
348 349 _Definitions = r''' 350 def u(s): 351 # turn string "'fo''o'" -> "fo'o" 352 c = s[0] 353 s = c.join(s.split(c+c)) 354 return s[1:-1] 355 356 ''' 357 358 _ParseSpec = r''' 359 parser WhereClause: 360 ignore: "[ \r\t\n]+" 361 token END: "$" 362 token NUM: "[0-9]+" 363 token VAR: "[a-zA-Z0-9_]+" 364 token BIN: ">=|<=|==|=|<|>|!=|<>" 365 token STR: r'"([^\\"]+|\\.)*"' 366 token STR2: r"'([^\\']+'{2,}|[^\\']+|\\.)*'" 367 368 rule goal: andexp ? END {{ return locals().get('andexp', None) }} 369 370 rule andexp: orexp {{ e = orexp }} 371 ( "and" orexp {{ e = ('and', e, orexp) }} 372 )* {{ return e }} 373 374 rule orexp: binary {{ e = binary }} 375 ( "or" binary {{ e = ('or', e, binary) }} 376 )* {{ return e }} 377 378 rule binary: term {{ e = term }} 379 ( BIN term {{ e = (BIN, e, term) }} 380 | 'like' term {{ e = ('like', e, term) }} 381 | 'not' 'like' term 382 {{ e = ('not like', e, term) }} 383 )* {{ return e }} 384 385 rule term: NUM {{ return int(NUM) }} 386 | VAR {{ return VAR }} 387 | STR {{ return u(STR) }} 388 | STR2 {{ return u(STR2) }} 389 | "\\(" andexp "\\)" {{ return andexp }} 390 '''
391 392 -class _Parser:
393 - def __init__(self, spec):
394 from yapps import grammar, yappsrt 395 from StringIO import StringIO 396 397 scanner = grammar.ParserDescriptionScanner(spec) 398 parser = grammar.ParserDescription(scanner) 399 parser = yappsrt.wrap_error_reporter(parser, 'Parser') 400 parser.preparser = _Definitions 401 parser.output = StringIO() 402 parser.generate_output() 403 exec parser.output.getvalue() in self.__dict__
404 405 where = _Parser(_ParseSpec)
406 407 @json 408 -def toJavaScript(meta, clause):
409 # sql is case insensitive, map column names to lower-case versions 410 lmeta = dict([(n.lower(), n) for n in meta.keys()]) 411 tree = where.parse('goal', clause) 412 413 def recurse(root, result): 414 if type(root) == types.TupleType: 415 n = len(root) 416 if n == 1: 417 recurse(root[0], result) 418 op, name, value = root 419 if op in ('and', 'or'): 420 recurse(root[1], result) 421 recurse(root[2], result) 422 else: 423 name = lmeta.get(name.lower(), None) 424 if name is not None: 425 op, value = meta[name].toJS(op, value) 426 result.append([name, op, value])
427 428 result = [] 429 recurse(tree, result) 430 result.sort() 431 return result 432
433 -def collapse(tree):
434 """ 435 Collapses adjacent and/ors into one statement. 436 and(and(a, b), c) becomes and(a, b, c). 437 """ 438 def _collapse(term, curopr=None, oprstack=None): 439 op = term[0] 440 if op == curopr: 441 _collapse(term[1], op, oprstack) 442 _collapse(term[2], op, oprstack) 443 elif op in ('and', 'or'): 444 s1, s2 = [], [] 445 _collapse(term[1], op, s1) 446 _collapse(term[2], op, s2) 447 combined = [op] + s1 + s2 448 oprstack.append(combined) 449 else: 450 oprstack.append(term)
451 452 exprs = [] 453 _collapse(tree, None, exprs) 454 return exprs 455
456 -class PythonConversionException(Exception):
457 """ 458 Exception thrown when a where clause fails conversion to a Python expression. 459 """ 460 pass
461
462 -def toPython(meta, clause):
463 # sql is case insensitive, map column names to lower-case versions 464 lmeta = dict([(n.lower(), n) for n in meta.keys()]) 465 tree = where.parse('goal', clause) 466 467 def recurse(root, result): 468 if isinstance(root, (list, tuple)): 469 op = root[0] 470 if op in ('and', 'or'): 471 all_sub_results = [] 472 for clause in root[1:]: 473 sub_result = [] 474 recurse(clause, sub_result) 475 all_sub_results.extend(sub_result) 476 result.append('(' + (' %s ' % op).join(all_sub_results) + ')') 477 else: 478 name, value = root[1], root[2] 479 orig_name = name.lower() 480 name = lmeta.get(orig_name, None) 481 if name is not None: 482 # Special case - ntevid must be convertable to integer and operator must be == or != 483 if orig_name == 'ntevid': 484 if op not in ('=','!='): 485 raise PythonConversionException('Unable to migrate ntevid starts/ends-with clause') 486 try: 487 value = int(value) 488 except ValueError: 489 raise PythonConversionException('Failed to convert ntevid to integer') 490 491 python_statement = meta[name].buildPython(name, op, value) 492 result.append('(%s)' % python_statement)
493 494 result = [] 495 tree = collapse(tree) 496 recurse(tree[0], result) 497 rule = result[0] 498 # And's and Or's already add parentheses - we need to strip the outermost parens 499 if tree[0][0] in ('and', 'or'): 500 rule = rule[1:-1] 501 return rule 502
503 504 -def fromFormVariables(meta, form):
505 result = [] 506 for n, attrType in meta.items(): 507 if form.has_key(n): 508 value = form[n] 509 if type(value) != type([]): 510 value = [value] 511 mode = form.get(n + "_mode", None) 512 clause = attrType.buildClause(n, value, mode) 513 if clause: 514 result.append('(%s)' % clause) 515 return ' and '.join(result)
516 517 518 if __name__ == '__main__': 519 meta = {} 520 toJavaScript(meta, 'severity = 3 or severity = 4') 521 toJavaScript(meta, "severity >= 4 and eventState = 0 and prodState = 1000") 522 toJavaScript(meta, "severity >= 2 and eventState = 0 and prodState = 1000") 523 print toJavaScript(meta, '(prodState = 1000) and (eventState = 0 or eventState = 1) and (severity >= 3)') 524 print fromFormVariables(meta, 525 dict(severity='Info', 526 severity_mode='>', 527 eventState='New', 528 eventState_mode='=', 529 prodState='Production', 530 prodState_mode='=')) 531