So far this operator was not implemented. This patch adds an additional value preparation function to the function table for binary operators, used to compile the regular expression. Unittests are included.
Signed-off-by: Michael Hanselmann <[email protected]> --- lib/query.py | 31 +++++++++++++++++----- test/ganeti.query_unittest.py | 58 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 7 deletions(-) diff --git a/lib/query.py b/lib/query.py index a462b7b..79441a8 100644 --- a/lib/query.py +++ b/lib/query.py @@ -322,6 +322,16 @@ def _WrapNot(fn, lhs, rhs): return not fn(lhs, rhs) +def _PrepareRegex(pattern): + """Compiles a regular expression. + + """ + try: + return re.compile(pattern) + except re.error, err: + raise errors.ParameterError("Invalid regex pattern (%s)" % err) + + class _FilterCompilerHelper: """Converts a query filter to a callable usable for filtering. @@ -349,8 +359,9 @@ class _FilterCompilerHelper: _EQUALITY_CHECKS = [ (QFF_HOSTNAME, lambda lhs, rhs: utils.MatchNameComponent(rhs, [lhs], - case_sensitive=False)), - (None, operator.eq), + case_sensitive=False), + None), + (None, operator.eq, None), ] """Known operators @@ -377,12 +388,14 @@ class _FilterCompilerHelper: # 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]), + (_OPTYPE_BINARY, [(flags, compat.partial(_WrapNot, fn), valprepfn) + for (flags, fn, valprepfn) in _EQUALITY_CHECKS]), qlang.OP_GLOB: (_OPTYPE_BINARY, NotImplemented), - qlang.OP_REGEXP: (_OPTYPE_BINARY, NotImplemented), + qlang.OP_REGEXP: (_OPTYPE_BINARY, [ + (None, lambda lhs, rhs: rhs.search(lhs), _PrepareRegex), + ]), qlang.OP_CONTAINS: (_OPTYPE_BINARY, [ - (None, operator.contains), + (None, operator.contains, None), ]), } @@ -556,8 +569,12 @@ class _FilterCompilerHelper: if hints_fn: hints_fn(op, datakind, name, value) - for (fn_flags, fn) in op_data: + for (fn_flags, fn, valprepfn) in op_data: if fn_flags is None or fn_flags & field_flags: + # Prepare value if necessary (e.g. compile regular expression) + if valprepfn: + value = valprepfn(value) + return compat.partial(_WrapBinaryOp, fn, retrieval_fn, value) raise errors.ProgrammerError("Unable to find operator implementation" diff --git a/test/ganeti.query_unittest.py b/test/ganeti.query_unittest.py index d6233a9..c9cc012 100755 --- a/test/ganeti.query_unittest.py +++ b/test/ganeti.query_unittest.py @@ -1537,6 +1537,64 @@ class TestQueryFilter(unittest.TestCase): [(constants.RS_NORMAL, "node3"), (constants.RS_NORMAL, True)], ]) + def testFilterRegex(self): + fielddefs = query._PrepareFieldList([ + (query._MakeField("name", "Name", constants.QFT_TEXT, "Name"), + None, 0, lambda ctx, item: item["name"]), + ], []) + + data = [ + { "name": "node1.example.com", }, + { "name": "node2.site.example.com", }, + { "name": "node2.example.net", }, + + # Empty name + { "name": "", }, + ] + + q = query.Query(fielddefs, ["name"], namefield="name", + filter_=["=~", "name", "site"]) + self.assertTrue(q.RequestedNames() is None) + self.assertEqual(q.Query(data), [ + [(constants.RS_NORMAL, "node2.site.example.com")], + ]) + + q = query.Query(fielddefs, ["name"], namefield="name", + filter_=["=~", "name", "^node2"]) + self.assertTrue(q.RequestedNames() is None) + self.assertEqual(q.Query(data), [ + [(constants.RS_NORMAL, "node2.example.net")], + [(constants.RS_NORMAL, "node2.site.example.com")], + ]) + + q = query.Query(fielddefs, ["name"], namefield="name", + filter_=["=~", "name", r"(?i)\.COM$"]) + self.assertTrue(q.RequestedNames() is None) + self.assertEqual(q.Query(data), [ + [(constants.RS_NORMAL, "node1.example.com")], + [(constants.RS_NORMAL, "node2.site.example.com")], + ]) + + q = query.Query(fielddefs, ["name"], namefield="name", + filter_=["=~", "name", r"."]) + self.assertTrue(q.RequestedNames() is None) + self.assertEqual(q.Query(data), [ + [(constants.RS_NORMAL, "node1.example.com")], + [(constants.RS_NORMAL, "node2.example.net")], + [(constants.RS_NORMAL, "node2.site.example.com")], + ]) + + q = query.Query(fielddefs, ["name"], namefield="name", + filter_=["=~", "name", r"^$"]) + self.assertTrue(q.RequestedNames() is None) + self.assertEqual(q.Query(data), [ + [(constants.RS_NORMAL, "")], + ]) + + # Invalid regular expression + self.assertRaises(errors.ParameterError, query.Query, fielddefs, ["name"], + filter_=["=~", "name", r"["]) + if __name__ == "__main__": testutils.GanetiTestProgram() -- 1.7.3.5
