The “?” operator is the equivalent of “if var” in Python.
---
 lib/qlang.py                  |    1 +
 lib/query.py                  |   36 +++++++++++++----
 test/ganeti.query_unittest.py |   83 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 111 insertions(+), 9 deletions(-)

diff --git a/lib/qlang.py b/lib/qlang.py
index 2a7f755..d255ef3 100644
--- a/lib/qlang.py
+++ b/lib/qlang.py
@@ -31,6 +31,7 @@ OP_AND = "&"
 
 # Unary operators
 OP_NOT = "!"
+OP_TRUE = "?"
 
 
 # Binary operators
diff --git a/lib/query.py b/lib/query.py
index 6497c60..7c74933 100644
--- a/lib/query.py
+++ b/lib/query.py
@@ -357,8 +357,7 @@ class _FilterCompilerHelper:
 
     - C{_OPTYPE_LOGIC}: Callable taking any number of arguments; used by
       L{_HandleLogicOp}
-    - C{_OPTYPE_UNARY}: Callable taking exactly one parameter; used by
-      L{_HandleUnaryOp}
+    - C{_OPTYPE_UNARY}: Always C{None}; details handled by L{_HandleUnaryOp}
     - C{_OPTYPE_BINARY}: Callable taking exactly two parameters, the left- and
       right-hand side of the operator, used by L{_HandleBinaryOp}
 
@@ -369,7 +368,8 @@ class _FilterCompilerHelper:
     qlang.OP_AND: (_OPTYPE_LOGIC, compat.all),
 
     # Unary operators
-    qlang.OP_NOT: (_OPTYPE_UNARY, operator.not_),
+    qlang.OP_NOT: (_OPTYPE_UNARY, None),
+    qlang.OP_TRUE: (_OPTYPE_UNARY, None),
 
     # Binary operators
     qlang.OP_EQUAL: (_OPTYPE_BINARY, _EQUALITY_CHECKS),
@@ -449,6 +449,15 @@ class _FilterCompilerHelper:
 
     return handler(hints_cb, level, op, op_data, operands)
 
+  def _LookupField(self, name):
+    """Returns a field definition by name.
+
+    """
+    try:
+      return self._fields[name]
+    except KeyError:
+      raise errors.ParameterError("Unknown field '%s'" % name)
+
   def _HandleLogicOp(self, hints_fn, level, op, op_fn, operands):
     """Handles logic operators.
 
@@ -485,6 +494,8 @@ class _FilterCompilerHelper:
     @param operands: List of operands
 
     """
+    assert op_fn is None
+
     if hints_fn:
       hints_fn(op)
 
@@ -492,8 +503,18 @@ class _FilterCompilerHelper:
       raise errors.ParameterError("Unary operator '%s' expects exactly one"
                                   " operand" % op)
 
-    return compat.partial(_WrapUnaryOp, op_fn,
-                          self._Compile(operands[0], level + 1))
+    if op == qlang.OP_TRUE:
+      (_, _, _, retrieval_fn) = self._LookupField(operands[0])
+
+      op_fn = operator.truth
+      arg = retrieval_fn
+    elif op == qlang.OP_NOT:
+      op_fn = operator.not_
+      arg = self._Compile(operands[0], level + 1)
+    else:
+      raise errors.ProgrammerError("Can't handle operator '%s'" % op)
+
+    return compat.partial(_WrapUnaryOp, op_fn, arg)
 
   def _HandleBinaryOp(self, hints_fn, level, op, op_data, operands):
     """Handles binary operators.
@@ -516,10 +537,7 @@ class _FilterCompilerHelper:
       raise errors.ParameterError("Invalid binary operator, expected exactly"
                                   " two operands")
 
-    try:
-      (fdef, datakind, field_flags, retrieval_fn) = self._fields[name]
-    except KeyError:
-      raise errors.ParameterError("Unknown field '%s'" % name)
+    (fdef, datakind, field_flags, retrieval_fn) = self._LookupField(name)
 
     assert fdef.kind != QFT_UNKNOWN
 
diff --git a/test/ganeti.query_unittest.py b/test/ganeti.query_unittest.py
index 38e67f4..0a2691f 100755
--- a/test/ganeti.query_unittest.py
+++ b/test/ganeti.query_unittest.py
@@ -1317,6 +1317,7 @@ class TestQueryFilter(unittest.TestCase):
       { "name": "node2", "other": ["x", "y", "bar"], },
       { "name": "node3", "other": "Hello", },
       { "name": "node1", "other": ["a", "b", "foo"], },
+      { "name": "empty", "other": []},
       ]
 
     q = query.Query(fielddefs, ["name", "other"], namefield="name",
@@ -1343,6 +1344,21 @@ class TestQueryFilter(unittest.TestCase):
       ["node2", ["x", "y", "bar"]],
       ])
 
+    # Boolean test
+    q = query.Query(fielddefs, ["name", "other"], namefield="name",
+                    filter_=["?", "other"])
+    self.assertEqual(q.OldStyleQuery(data), [
+      ["node1", ["a", "b", "foo"]],
+      ["node2", ["x", "y", "bar"]],
+      ["node3", "Hello"],
+      ])
+
+    q = query.Query(fielddefs, ["name", "other"], namefield="name",
+                    filter_=["!", ["?", "other"]])
+    self.assertEqual(q.OldStyleQuery(data), [
+      ["empty", []],
+      ])
+
   def testFilterHostname(self):
     fielddefs = query._PrepareFieldList([
       (query._MakeField("name", "Name", constants.QFT_TEXT, "Name"),
@@ -1402,6 +1418,73 @@ class TestQueryFilter(unittest.TestCase):
       ["node2.example.net"],
       ])
 
+  def testFilterBoolean(self):
+    fielddefs = query._PrepareFieldList([
+      (query._MakeField("name", "Name", constants.QFT_TEXT, "Name"),
+       None, query.QFF_HOSTNAME, lambda ctx, item: item["name"]),
+      (query._MakeField("value", "Value", constants.QFT_BOOL, "Value"),
+       None, 0, lambda ctx, item: item["value"]),
+      ], [])
+
+    data = [
+      { "name": "node1", "value": False, },
+      { "name": "node2", "value": True, },
+      { "name": "node3", "value": True, },
+      ]
+
+    q = query.Query(fielddefs, ["name", "value"],
+                    filter_=["|", ["=", "value", False],
+                                  ["=", "value", True]])
+    self.assertTrue(q.RequestedNames() is None)
+    self.assertEqual(q.Query(data), [
+      [(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, False)],
+      [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, True)],
+      [(constants.RS_NORMAL, "node3"), (constants.RS_NORMAL, True)],
+      ])
+
+    q = query.Query(fielddefs, ["name", "value"],
+                    filter_=["|", ["=", "value", False],
+                                  ["!", ["=", "value", False]]])
+    self.assertTrue(q.RequestedNames() is None)
+    self.assertEqual(q.Query(data), [
+      [(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, False)],
+      [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, True)],
+      [(constants.RS_NORMAL, "node3"), (constants.RS_NORMAL, True)],
+      ])
+
+    # Comparing bool with string
+    for i in ["False", "True", "0", "1", "no", "yes", "N", "Y"]:
+      self.assertRaises(errors.ParameterError, query.Query,
+                        fielddefs, ["name", "value"],
+                        filter_=["=", "value", i])
+
+    # Truth filter
+    q = query.Query(fielddefs, ["name", "value"], filter_=["?", "value"])
+    self.assertTrue(q.RequestedNames() is None)
+    self.assertEqual(q.Query(data), [
+      [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, True)],
+      [(constants.RS_NORMAL, "node3"), (constants.RS_NORMAL, True)],
+      ])
+
+    # Negative bool filter
+    q = query.Query(fielddefs, ["name", "value"], filter_=["!", ["?", 
"value"]])
+    self.assertTrue(q.RequestedNames() is None)
+    self.assertEqual(q.Query(data), [
+      [(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, False)],
+      ])
+
+    # Complex truth filter
+    q = query.Query(fielddefs, ["name", "value"],
+                    filter_=["|", ["&", ["=", "name", "node1"],
+                                        ["!", ["?", "value"]]],
+                                  ["?", "value"]])
+    self.assertTrue(q.RequestedNames() is None)
+    self.assertEqual(q.Query(data), [
+      [(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, False)],
+      [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, True)],
+      [(constants.RS_NORMAL, "node3"), (constants.RS_NORMAL, True)],
+      ])
+
 
 if __name__ == "__main__":
   testutils.GanetiTestProgram()
-- 
1.7.3.5

Reply via email to