Statistics
| Branch: | Tag: | Revision:

root / lib / qlang.py @ 45df0793

History | View | Annotate | Download (7 kB)

1 a123dc19 Michael Hanselmann
#
2 a123dc19 Michael Hanselmann
#
3 a123dc19 Michael Hanselmann
4 341ff8e9 Michael Hanselmann
# Copyright (C) 2010, 2011 Google Inc.
5 a123dc19 Michael Hanselmann
#
6 a123dc19 Michael Hanselmann
# This program is free software; you can redistribute it and/or modify
7 a123dc19 Michael Hanselmann
# it under the terms of the GNU General Public License as published by
8 a123dc19 Michael Hanselmann
# the Free Software Foundation; either version 2 of the License, or
9 a123dc19 Michael Hanselmann
# (at your option) any later version.
10 a123dc19 Michael Hanselmann
#
11 a123dc19 Michael Hanselmann
# This program is distributed in the hope that it will be useful, but
12 a123dc19 Michael Hanselmann
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 a123dc19 Michael Hanselmann
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 a123dc19 Michael Hanselmann
# General Public License for more details.
15 a123dc19 Michael Hanselmann
#
16 a123dc19 Michael Hanselmann
# You should have received a copy of the GNU General Public License
17 a123dc19 Michael Hanselmann
# along with this program; if not, write to the Free Software
18 a123dc19 Michael Hanselmann
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 a123dc19 Michael Hanselmann
# 02110-1301, USA.
20 a123dc19 Michael Hanselmann
21 a123dc19 Michael Hanselmann
22 341ff8e9 Michael Hanselmann
"""Module for a simple query language
23 341ff8e9 Michael Hanselmann

24 341ff8e9 Michael Hanselmann
A query filter is always a list. The first item in the list is the operator
25 341ff8e9 Michael Hanselmann
(e.g. C{[OP_AND, ...]}), while the other items depend on the operator. For
26 341ff8e9 Michael Hanselmann
logic operators (e.g. L{OP_AND}, L{OP_OR}), they are subfilters whose results
27 341ff8e9 Michael Hanselmann
are combined. Unary operators take exactly one other item (e.g. a subfilter for
28 341ff8e9 Michael Hanselmann
L{OP_NOT} and a field name for L{OP_TRUE}). Binary operators take exactly two
29 341ff8e9 Michael Hanselmann
operands, usually a field name and a value to compare against. Filters are
30 341ff8e9 Michael Hanselmann
converted to callable functions by L{query._CompileFilter}.
31 341ff8e9 Michael Hanselmann

32 341ff8e9 Michael Hanselmann
"""
33 a123dc19 Michael Hanselmann
34 7578ab0a Michael Hanselmann
import re
35 3f2f55bb Michael Hanselmann
import string # pylint: disable-msg=W0402
36 7578ab0a Michael Hanselmann
37 7578ab0a Michael Hanselmann
import pyparsing as pyp
38 7578ab0a Michael Hanselmann
39 7578ab0a Michael Hanselmann
from ganeti import errors
40 3f2f55bb Michael Hanselmann
from ganeti import netutils
41 7578ab0a Michael Hanselmann
42 7578ab0a Michael Hanselmann
43 341ff8e9 Michael Hanselmann
# Logic operators with one or more operands, each of which is a filter on its
44 341ff8e9 Michael Hanselmann
# own
45 a123dc19 Michael Hanselmann
OP_OR = "|"
46 88076fd1 Michael Hanselmann
OP_AND = "&"
47 88076fd1 Michael Hanselmann
48 88076fd1 Michael Hanselmann
49 341ff8e9 Michael Hanselmann
# Unary operators with exactly one operand
50 88076fd1 Michael Hanselmann
OP_NOT = "!"
51 3b877f08 Michael Hanselmann
OP_TRUE = "?"
52 88076fd1 Michael Hanselmann
53 88076fd1 Michael Hanselmann
54 341ff8e9 Michael Hanselmann
# Binary operators with exactly two operands, the field name and an
55 341ff8e9 Michael Hanselmann
# operator-specific value
56 a123dc19 Michael Hanselmann
OP_EQUAL = "="
57 88076fd1 Michael Hanselmann
OP_NOT_EQUAL = "!="
58 88076fd1 Michael Hanselmann
OP_REGEXP = "=~"
59 88076fd1 Michael Hanselmann
OP_CONTAINS = "=[]"
60 a123dc19 Michael Hanselmann
61 a123dc19 Michael Hanselmann
62 3f2f55bb Michael Hanselmann
#: Characters used for detecting user-written filters (see L{MaybeFilter})
63 3f2f55bb Michael Hanselmann
FILTER_DETECTION_CHARS = frozenset("()=/!~" + string.whitespace)
64 3f2f55bb Michael Hanselmann
65 3f2f55bb Michael Hanselmann
66 60cba7f8 Michael Hanselmann
def MakeSimpleFilter(namefield, values):
67 31554d0a Michael Hanselmann
  """Builds simple a filter.
68 60cba7f8 Michael Hanselmann

69 60cba7f8 Michael Hanselmann
  @param namefield: Name of field containing item name
70 60cba7f8 Michael Hanselmann
  @param values: List of names
71 60cba7f8 Michael Hanselmann

72 60cba7f8 Michael Hanselmann
  """
73 60cba7f8 Michael Hanselmann
  if values:
74 60cba7f8 Michael Hanselmann
    return [OP_OR] + [[OP_EQUAL, namefield, i] for i in values]
75 60cba7f8 Michael Hanselmann
76 60cba7f8 Michael Hanselmann
  return None
77 7578ab0a Michael Hanselmann
78 7578ab0a Michael Hanselmann
79 7578ab0a Michael Hanselmann
def _ConvertLogicOp(op):
80 7578ab0a Michael Hanselmann
  """Creates parsing action function for logic operator.
81 7578ab0a Michael Hanselmann

82 7578ab0a Michael Hanselmann
  @type op: string
83 7578ab0a Michael Hanselmann
  @param op: Operator for data structure, e.g. L{OP_AND}
84 7578ab0a Michael Hanselmann

85 7578ab0a Michael Hanselmann
  """
86 7578ab0a Michael Hanselmann
  def fn(toks):
87 7578ab0a Michael Hanselmann
    """Converts parser tokens to query operator structure.
88 7578ab0a Michael Hanselmann

89 7578ab0a Michael Hanselmann
    @rtype: list
90 7578ab0a Michael Hanselmann
    @return: Query operator structure, e.g. C{[OP_AND, ["=", "foo", "bar"]]}
91 7578ab0a Michael Hanselmann

92 7578ab0a Michael Hanselmann
    """
93 7578ab0a Michael Hanselmann
    operands = toks[0]
94 7578ab0a Michael Hanselmann
95 7578ab0a Michael Hanselmann
    if len(operands) == 1:
96 7578ab0a Michael Hanselmann
      return operands[0]
97 7578ab0a Michael Hanselmann
98 7578ab0a Michael Hanselmann
    # Build query operator structure
99 7578ab0a Michael Hanselmann
    return [[op] + operands.asList()]
100 7578ab0a Michael Hanselmann
101 7578ab0a Michael Hanselmann
  return fn
102 7578ab0a Michael Hanselmann
103 7578ab0a Michael Hanselmann
104 7578ab0a Michael Hanselmann
_KNOWN_REGEXP_DELIM = "/#^|"
105 7578ab0a Michael Hanselmann
_KNOWN_REGEXP_FLAGS = frozenset("si")
106 7578ab0a Michael Hanselmann
107 7578ab0a Michael Hanselmann
108 7578ab0a Michael Hanselmann
def _ConvertRegexpValue(_, loc, toks):
109 7578ab0a Michael Hanselmann
  """Regular expression value for condition.
110 7578ab0a Michael Hanselmann

111 7578ab0a Michael Hanselmann
  """
112 7578ab0a Michael Hanselmann
  (regexp, flags) = toks[0]
113 7578ab0a Michael Hanselmann
114 7578ab0a Michael Hanselmann
  # Ensure only whitelisted flags are used
115 7578ab0a Michael Hanselmann
  unknown_flags = (frozenset(flags) - _KNOWN_REGEXP_FLAGS)
116 7578ab0a Michael Hanselmann
  if unknown_flags:
117 7578ab0a Michael Hanselmann
    raise pyp.ParseFatalException("Unknown regular expression flags: '%s'" %
118 7578ab0a Michael Hanselmann
                                  "".join(unknown_flags), loc)
119 7578ab0a Michael Hanselmann
120 7578ab0a Michael Hanselmann
  if flags:
121 7578ab0a Michael Hanselmann
    re_flags = "(?%s)" % "".join(sorted(flags))
122 7578ab0a Michael Hanselmann
  else:
123 7578ab0a Michael Hanselmann
    re_flags = ""
124 7578ab0a Michael Hanselmann
125 7578ab0a Michael Hanselmann
  re_cond = re_flags + regexp
126 7578ab0a Michael Hanselmann
127 7578ab0a Michael Hanselmann
  # Test if valid
128 7578ab0a Michael Hanselmann
  try:
129 7578ab0a Michael Hanselmann
    re.compile(re_cond)
130 7578ab0a Michael Hanselmann
  except re.error, err:
131 7578ab0a Michael Hanselmann
    raise pyp.ParseFatalException("Invalid regular expression (%s)" % err, loc)
132 7578ab0a Michael Hanselmann
133 7578ab0a Michael Hanselmann
  return [re_cond]
134 7578ab0a Michael Hanselmann
135 7578ab0a Michael Hanselmann
136 7578ab0a Michael Hanselmann
def BuildFilterParser():
137 7578ab0a Michael Hanselmann
  """Builds a parser for query filter strings.
138 7578ab0a Michael Hanselmann

139 7578ab0a Michael Hanselmann
  @rtype: pyparsing.ParserElement
140 7578ab0a Michael Hanselmann

141 7578ab0a Michael Hanselmann
  """
142 7578ab0a Michael Hanselmann
  field_name = pyp.Word(pyp.alphas, pyp.alphanums + "_/.")
143 7578ab0a Michael Hanselmann
144 7578ab0a Michael Hanselmann
  # Integer
145 7578ab0a Michael Hanselmann
  num_sign = pyp.Word("-+", exact=1)
146 7578ab0a Michael Hanselmann
  number = pyp.Combine(pyp.Optional(num_sign) + pyp.Word(pyp.nums))
147 7578ab0a Michael Hanselmann
  number.setParseAction(lambda toks: int(toks[0]))
148 7578ab0a Michael Hanselmann
149 7578ab0a Michael Hanselmann
  # Right-hand-side value
150 7578ab0a Michael Hanselmann
  rval = (number | pyp.quotedString.setParseAction(pyp.removeQuotes))
151 7578ab0a Michael Hanselmann
152 7578ab0a Michael Hanselmann
  # Boolean condition
153 7578ab0a Michael Hanselmann
  bool_cond = field_name.copy()
154 7578ab0a Michael Hanselmann
  bool_cond.setParseAction(lambda (fname, ): [[OP_TRUE, fname]])
155 7578ab0a Michael Hanselmann
156 7578ab0a Michael Hanselmann
  # Simple binary conditions
157 7578ab0a Michael Hanselmann
  binopstbl = {
158 7578ab0a Michael Hanselmann
    "==": OP_EQUAL,
159 7578ab0a Michael Hanselmann
    "!=": OP_NOT_EQUAL,
160 7578ab0a Michael Hanselmann
    }
161 7578ab0a Michael Hanselmann
162 7578ab0a Michael Hanselmann
  binary_cond = (field_name + pyp.oneOf(binopstbl.keys()) + rval)
163 7578ab0a Michael Hanselmann
  binary_cond.setParseAction(lambda (lhs, op, rhs): [[binopstbl[op], lhs, rhs]])
164 7578ab0a Michael Hanselmann
165 7578ab0a Michael Hanselmann
  # "in" condition
166 7578ab0a Michael Hanselmann
  in_cond = (rval + pyp.Suppress("in") + field_name)
167 7578ab0a Michael Hanselmann
  in_cond.setParseAction(lambda (value, field): [[OP_CONTAINS, field, value]])
168 7578ab0a Michael Hanselmann
169 7578ab0a Michael Hanselmann
  # "not in" condition
170 7578ab0a Michael Hanselmann
  not_in_cond = (rval + pyp.Suppress("not") + pyp.Suppress("in") + field_name)
171 7578ab0a Michael Hanselmann
  not_in_cond.setParseAction(lambda (value, field): [[OP_NOT, [OP_CONTAINS,
172 7578ab0a Michael Hanselmann
                                                               field, value]]])
173 7578ab0a Michael Hanselmann
174 7578ab0a Michael Hanselmann
  # Regular expression, e.g. m/foobar/i
175 7578ab0a Michael Hanselmann
  regexp_val = pyp.Group(pyp.Optional("m").suppress() +
176 7578ab0a Michael Hanselmann
                         pyp.MatchFirst([pyp.QuotedString(i, escChar="\\")
177 7578ab0a Michael Hanselmann
                                         for i in _KNOWN_REGEXP_DELIM]) +
178 7578ab0a Michael Hanselmann
                         pyp.Optional(pyp.Word(pyp.alphas), default=""))
179 7578ab0a Michael Hanselmann
  regexp_val.setParseAction(_ConvertRegexpValue)
180 7578ab0a Michael Hanselmann
  regexp_cond = (field_name + pyp.Suppress("=~") + regexp_val)
181 7578ab0a Michael Hanselmann
  regexp_cond.setParseAction(lambda (field, value): [[OP_REGEXP, field, value]])
182 7578ab0a Michael Hanselmann
183 7578ab0a Michael Hanselmann
  not_regexp_cond = (field_name + pyp.Suppress("!~") + regexp_val)
184 7578ab0a Michael Hanselmann
  not_regexp_cond.setParseAction(lambda (field, value):
185 7578ab0a Michael Hanselmann
                                 [[OP_NOT, [OP_REGEXP, field, value]]])
186 7578ab0a Michael Hanselmann
187 7578ab0a Michael Hanselmann
  # All possible conditions
188 7578ab0a Michael Hanselmann
  condition = (binary_cond ^ bool_cond ^
189 7578ab0a Michael Hanselmann
               in_cond ^ not_in_cond ^
190 7578ab0a Michael Hanselmann
               regexp_cond ^ not_regexp_cond)
191 7578ab0a Michael Hanselmann
192 7578ab0a Michael Hanselmann
  # Associativity operators
193 7578ab0a Michael Hanselmann
  filter_expr = pyp.operatorPrecedence(condition, [
194 7578ab0a Michael Hanselmann
    (pyp.Keyword("not").suppress(), 1, pyp.opAssoc.RIGHT,
195 7578ab0a Michael Hanselmann
     lambda toks: [[OP_NOT, toks[0][0]]]),
196 7578ab0a Michael Hanselmann
    (pyp.Keyword("and").suppress(), 2, pyp.opAssoc.LEFT,
197 7578ab0a Michael Hanselmann
     _ConvertLogicOp(OP_AND)),
198 7578ab0a Michael Hanselmann
    (pyp.Keyword("or").suppress(), 2, pyp.opAssoc.LEFT,
199 7578ab0a Michael Hanselmann
     _ConvertLogicOp(OP_OR)),
200 7578ab0a Michael Hanselmann
    ])
201 7578ab0a Michael Hanselmann
202 7578ab0a Michael Hanselmann
  parser = pyp.StringStart() + filter_expr + pyp.StringEnd()
203 7578ab0a Michael Hanselmann
  parser.parseWithTabs()
204 7578ab0a Michael Hanselmann
205 7578ab0a Michael Hanselmann
  # Originally C{parser.validate} was called here, but there seems to be some
206 7578ab0a Michael Hanselmann
  # issue causing it to fail whenever the "not" operator is included above.
207 7578ab0a Michael Hanselmann
208 7578ab0a Michael Hanselmann
  return parser
209 7578ab0a Michael Hanselmann
210 7578ab0a Michael Hanselmann
211 7578ab0a Michael Hanselmann
def ParseFilter(text, parser=None):
212 7578ab0a Michael Hanselmann
  """Parses a query filter.
213 7578ab0a Michael Hanselmann

214 7578ab0a Michael Hanselmann
  @type text: string
215 7578ab0a Michael Hanselmann
  @param text: Query filter
216 7578ab0a Michael Hanselmann
  @type parser: pyparsing.ParserElement
217 7578ab0a Michael Hanselmann
  @param parser: Pyparsing object
218 7578ab0a Michael Hanselmann
  @rtype: list
219 7578ab0a Michael Hanselmann

220 7578ab0a Michael Hanselmann
  """
221 7578ab0a Michael Hanselmann
  if parser is None:
222 7578ab0a Michael Hanselmann
    parser = BuildFilterParser()
223 7578ab0a Michael Hanselmann
224 7578ab0a Michael Hanselmann
  try:
225 7578ab0a Michael Hanselmann
    return parser.parseString(text)[0]
226 7578ab0a Michael Hanselmann
  except pyp.ParseBaseException, err:
227 7578ab0a Michael Hanselmann
    raise errors.QueryFilterParseError("Failed to parse query filter"
228 7578ab0a Michael Hanselmann
                                       " '%s': %s" % (text, err), err)
229 3f2f55bb Michael Hanselmann
230 3f2f55bb Michael Hanselmann
231 3f2f55bb Michael Hanselmann
def MaybeFilter(text):
232 3f2f55bb Michael Hanselmann
  """Try to determine if a string is a filter or a name.
233 3f2f55bb Michael Hanselmann

234 3f2f55bb Michael Hanselmann
  If in doubt, this function treats a text as a name.
235 3f2f55bb Michael Hanselmann

236 3f2f55bb Michael Hanselmann
  @type text: string
237 3f2f55bb Michael Hanselmann
  @param text: String to be examined
238 3f2f55bb Michael Hanselmann
  @rtype: bool
239 3f2f55bb Michael Hanselmann

240 3f2f55bb Michael Hanselmann
  """
241 3f2f55bb Michael Hanselmann
  # Quick check for punctuation and whitespace
242 3f2f55bb Michael Hanselmann
  if frozenset(text) & FILTER_DETECTION_CHARS:
243 3f2f55bb Michael Hanselmann
    return True
244 3f2f55bb Michael Hanselmann
245 3f2f55bb Michael Hanselmann
  try:
246 3f2f55bb Michael Hanselmann
    netutils.Hostname.GetNormalizedName(text)
247 3f2f55bb Michael Hanselmann
  except errors.OpPrereqError:
248 3f2f55bb Michael Hanselmann
    # Not a valid hostname, treat as filter
249 3f2f55bb Michael Hanselmann
    return True
250 3f2f55bb Michael Hanselmann
251 3f2f55bb Michael Hanselmann
  # Most probably a name
252 3f2f55bb Michael Hanselmann
  return False