On Mon, Feb 28, 2011 at 01:30:43PM +0100, Michael Hanselmann wrote:
> This patch adds a “compiler” for query filters, converting them to a
> callable function used while preparing the query result. In addition, a
> hints call allows some analysis to be done on the query (e.g. referenced
> names), making data collection more efficient.
> 
> The depth of filters is limited to avoid exceeding the runtime's maximum
> recursion depth.
> 
> More operators and other improvements can be implemented using this
> base. Extensive unittests are provided.
> ---
>  lib/query.py                  |  397 +++++++++++++++++++++++++++++++++++++++-
>  test/ganeti.query_unittest.py |  340 +++++++++++++++++++++++++++++++++++
>  2 files changed, 727 insertions(+), 10 deletions(-)
> 
> diff --git a/lib/query.py b/lib/query.py
> index aa94616..49cc157 100644
> --- a/lib/query.py
> +++ b/lib/query.py
> @@ -63,6 +63,7 @@ from ganeti import utils
>  from ganeti import compat
>  from ganeti import objects
>  from ganeti import ht
> +from ganeti import qlang
>  
>  from ganeti.constants import (QFT_UNKNOWN, QFT_TEXT, QFT_BOOL, QFT_NUMBER,
>                                QFT_UNIT, QFT_TIMESTAMP, QFT_OTHER,
> @@ -176,8 +177,330 @@ def GetAllFields(fielddefs):
>    return [fdef for (fdef, _, _, _) in fielddefs]
>  
>  
> +class _FilterHints:

Please document the purpose of this class (and ideally also its
@ivars). I'm interested in explanations how the requested names part is
supposed to work.

> +  def __init__(self, namefield):
> +    """Initializes this class.
> +
> +    @type namefield: string
> +    @param namefield: Field caller is interested in
> +
> +    """
> +    self._namefield = namefield
> +    self._allnames = False
> +    self._names = None
> +    self._datakinds = set()
> +
> +  def RequestedNames(self):
> +    """Returns all requested values.
> +
> +    Returns C{None} if list of values can't be determined (e.g. encountered
> +    non-equality operators).
> +
> +    @rtype: list
> +
> +    """
> +    if self._allnames or self._names is None:
> +      return None
> +
> +    return utils.UniqueSequence(self._names)
> +
> +  def ReferencedData(self):
> +    """Returns all kinds of data referenced by the filter.
> +
> +    """
> +    return frozenset(self._datakinds)
> +
> +  def _NeedAllNames(self):
> +    """Changes internal state to request all names.
> +
> +    """
> +    self._allnames = True
> +    self._names = None
> +
> +  def NoteLogicOp(self, op):
> +    """Called when handling a logic operation.
> +
> +    @type op: string
> +    @param op: Operator
> +
> +    """
> +    if op != qlang.OP_OR:
> +      self._NeedAllNames()
> +
> +  def NoteUnaryOp(self, op): # pylint: disable-msg=W0613
> +    """Called when handling an unary operation.
> +
> +    @type op: string
> +    @param op: Operator
> +
> +    """
> +    self._NeedAllNames()
> +
> +  def NoteBinaryOp(self, op, datakind, name, value):
> +    """Called when handling a binary operation.
> +
> +    @type op: string
> +    @param op: Operator
> +    @type name: string
> +    @param name: Left-hand side of operator (field name)
> +    @param value: Right-hand side of operator
> +
> +    """
> +    if datakind is not None:
> +      self._datakinds.add(datakind)
> +
> +    if self._allnames:
> +      return
> +
> +    # If any operator other than equality was used, all items need to be
> +    # retrieved
> +    if op == qlang.OP_EQUAL and name == self._namefield:
> +      if self._names is None:
> +        self._names = []
> +      self._names.append(value)
> +    else:
> +      self._NeedAllNames()
> +
> +
> +def _WrapLogicOp(op_fn, sentences, ctx, item):
> +  """Wrapper for logic operator functions.
> +
> +  """
> +  return op_fn(fn(ctx, item) for fn in sentences)
> +
> +
> +def _WrapUnaryOp(op_fn, inner, ctx, item):
> +  """Wrapper for unary operator functions.
> +
> +  """
> +  return op_fn(inner(ctx, item))
> +
> +
> +def _WrapBinaryOp(op_fn, retrieval_fn, value, ctx, item):
> +  """Wrapper for binary operator functions.
> +
> +  """
> +  return op_fn(retrieval_fn(ctx, item), value)
> +
> +
> +def _WrapNot(fn, lhs, rhs):
> +  """Negates the result of a wrapped function.
> +
> +  """
> +  return not fn(lhs, rhs)
> +
> +
> +class _FilterCompiler:
> +  """Converts a query filter to a callable usable for filtering.
> +
> +  """
> +  #: How deep filters can be nested
> +  _LEVELS_MAX = 10

Hah. 10? I'm curious to see the person who can reasonable use a
10-levels deep filter :)

> +
> +  (_OPTYPE_LOGIC,
> +   _OPTYPE_UNARY,
> +   _OPTYPE_BINARY) = range(1, 4)

Documentation.

> +  _EQUALITY_CHECKS = [
> +    (QFF_HOSTNAME,
> +     lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs],
> +                                               case_sensitive=False)),
> +    (None, operator.eq),
> +    ]

Same here.

> +
> +  _OPS = {
> +    # Logic operators
> +    qlang.OP_OR: (_OPTYPE_LOGIC, compat.any),
> +    qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
> +
> +    # Unary operators
> +    qlang.OP_NOT: (_OPTYPE_UNARY, operator.not_),
> +
> +    # Binary operators
> +    qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS),
> +    qlang.OP_NOT_EQUAL:
> +      (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn))
> +                        for (flags, fn) in _EQUALITY_CHECKS]),
> +    qlang.OP_GLOB: (_OPTYPE_BINARY, NotImplemented),
> +    qlang.OP_REGEXP: (_OPTYPE_BINARY, NotImplemented),
> +    qlang.OP_CONTAINS: (_OPTYPE_BINARY, [
> +      (None, operator.contains),
> +      ]),
> +    }

And here.

thanks,
iustin

Reply via email to