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