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