From ad48eaccfe610134932c9eb393e8ac183a8c5242 Mon Sep 17 00:00:00 2001 From: Michael Hanselmann <hansmi@google.com> Date: Wed, 13 Jun 2012 18:35:13 +0200 Subject: [PATCH] query2: Add <, >, <=, >= comparison operators These can be used, for example, to get jobs submitted after a certain timestamp. Signed-off-by: Michael Hanselmann <hansmi@google.com> Reviewed-by: Iustin Pop <iustin@google.com> --- lib/qlang.py | 10 +++++++++- lib/query.py | 12 ++++++++++++ man/ganeti.rst | 10 +++++++++- test/ganeti.qlang_unittest.py | 10 ++++++++++ test/ganeti.query_unittest.py | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 2 deletions(-) diff --git a/lib/qlang.py b/lib/qlang.py index 839a615ba..2923194f9 100644 --- a/lib/qlang.py +++ b/lib/qlang.py @@ -58,12 +58,16 @@ OP_TRUE = "?" # operator-specific value OP_EQUAL = "=" OP_NOT_EQUAL = "!=" +OP_LT = "<" +OP_LE = "<=" +OP_GT = ">" +OP_GE = ">=" OP_REGEXP = "=~" OP_CONTAINS = "=[]" #: Characters used for detecting user-written filters (see L{_CheckFilter}) -FILTER_DETECTION_CHARS = frozenset("()=/!~'\"\\" + string.whitespace) +FILTER_DETECTION_CHARS = frozenset("()=/!~'\"\\<>" + string.whitespace) #: Characters used to detect globbing filters (see L{_CheckGlobbing}) GLOB_DETECTION_CHARS = frozenset("*?") @@ -165,6 +169,10 @@ def BuildFilterParser(): binopstbl = { "==": OP_EQUAL, "!=": OP_NOT_EQUAL, + "<": OP_LT, + "<=": OP_LE, + ">": OP_GT, + ">=": OP_GE, } binary_cond = (field_name + pyp.oneOf(binopstbl.keys()) + rval) diff --git a/lib/query.py b/lib/query.py index ffd4d204c..a8f19f0a5 100644 --- a/lib/query.py +++ b/lib/query.py @@ -403,6 +403,18 @@ class _FilterCompilerHelper: qlang.OP_NOT_EQUAL: (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn) for (flags, fn, valprepfn) in _EQUALITY_CHECKS]), + qlang.OP_LT: (_OPTYPE_BINARY, [ + (None, operator.lt, None), + ]), + qlang.OP_GT: (_OPTYPE_BINARY, [ + (None, operator.gt, None), + ]), + qlang.OP_LE: (_OPTYPE_BINARY, [ + (None, operator.le, None), + ]), + qlang.OP_GE: (_OPTYPE_BINARY, [ + (None, operator.ge, None), + ]), qlang.OP_REGEXP: (_OPTYPE_BINARY, [ (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex), ]), diff --git a/man/ganeti.rst b/man/ganeti.rst index 2d5c82f72..d31adfca1 100644 --- a/man/ganeti.rst +++ b/man/ganeti.rst @@ -362,7 +362,7 @@ Syntax in pseudo-BNF:: <condition> ::= { /* Value comparison */ - <field> { == | != } <value> + <field> { == | != | < | <= | >= | > } <value> /* Collection membership */ | <value> [ not ] in <field> @@ -389,6 +389,14 @@ Operators: Equality *!=* Inequality +*<* + Less than +*<=* + Less than or equal +*>* + Greater than +*>=* + Greater than or equal *=~* Pattern match using regular expression *!~* diff --git a/test/ganeti.qlang_unittest.py b/test/ganeti.qlang_unittest.py index 02b953406..df249ec68 100755 --- a/test/ganeti.qlang_unittest.py +++ b/test/ganeti.qlang_unittest.py @@ -147,6 +147,11 @@ class TestParseFilter(unittest.TestCase): [qlang.OP_NOT, [qlang.OP_REGEXP, "field", utils.DnsNameGlobPattern("*.example.*")]]) + self._Test("ctime < 1234", [qlang.OP_LT, "ctime", 1234]) + self._Test("ctime > 1234", [qlang.OP_GT, "ctime", 1234]) + self._Test("mtime <= 9999", [qlang.OP_LE, "mtime", 9999]) + self._Test("mtime >= 9999", [qlang.OP_GE, "mtime", 9999]) + def testAllFields(self): for name in frozenset(i for d in query.ALL_FIELD_LISTS for i in d.keys()): self._Test("%s == \"value\"" % name, [qlang.OP_EQUAL, name, "value"]) @@ -167,6 +172,11 @@ class TestParseFilter(unittest.TestCase): # Non-matching regexp delimiters tests.append("name =~ /foobarbaz#") + # Invalid operators + tests.append("name <> value") + tests.append("name => value") + tests.append("name =< value") + for qfilter in tests: try: qlang.ParseFilter(qfilter, parser=self.parser) diff --git a/test/ganeti.query_unittest.py b/test/ganeti.query_unittest.py index 87034436a..ca83d1cc9 100755 --- a/test/ganeti.query_unittest.py +++ b/test/ganeti.query_unittest.py @@ -1745,6 +1745,38 @@ class TestQueryFilter(unittest.TestCase): self.assertRaises(errors.ParameterError, query.Query, fielddefs, ["name"], qfilter=["=~", "name", r"["]) + def testFilterLessGreater(self): + fielddefs = query._PrepareFieldList([ + (query._MakeField("value", "Value", constants.QFT_NUMBER, "Value"), + None, 0, lambda ctx, item: item), + ], []) + + data = range(100) + + q = query.Query(fielddefs, ["value"], + qfilter=["<", "value", 20]) + self.assertTrue(q.RequestedNames() is None) + self.assertEqual(q.Query(data), + [[(constants.RS_NORMAL, i)] for i in range(20)]) + + q = query.Query(fielddefs, ["value"], + qfilter=["<=", "value", 30]) + self.assertTrue(q.RequestedNames() is None) + self.assertEqual(q.Query(data), + [[(constants.RS_NORMAL, i)] for i in range(31)]) + + q = query.Query(fielddefs, ["value"], + qfilter=[">", "value", 40]) + self.assertTrue(q.RequestedNames() is None) + self.assertEqual(q.Query(data), + [[(constants.RS_NORMAL, i)] for i in range(41, 100)]) + + q = query.Query(fielddefs, ["value"], + qfilter=[">=", "value", 50]) + self.assertTrue(q.RequestedNames() is None) + self.assertEqual(q.Query(data), + [[(constants.RS_NORMAL, i)] for i in range(50, 100)]) + if __name__ == "__main__": testutils.GanetiTestProgram() -- GitLab