query2: Add <, >, <=, >= comparison operators
authorMichael Hanselmann <hansmi@google.com>
Wed, 13 Jun 2012 16:35:13 +0000 (18:35 +0200)
committerMichael Hanselmann <hansmi@google.com>
Thu, 14 Jun 2012 11:18:14 +0000 (13:18 +0200)
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
lib/query.py
man/ganeti.rst
test/ganeti.qlang_unittest.py
test/ganeti.query_unittest.py

index 839a615..2923194 100644 (file)
@@ -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)
index ffd4d20..a8f19f0 100644 (file)
@@ -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),
       ]),
index 2d5c82f..d31adfc 100644 (file)
@@ -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
 *!~*
index 02b9534..df249ec 100755 (executable)
@@ -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)
index 8703443..ca83d1c 100755 (executable)
@@ -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()