Implements in operator for complex query functionality

Implements: blueprint complex-filter-expressions-in-api-queries
Change-Id: I6ee7249f605f2afdeae59adf97f588cf1075a453
This commit is contained in:
Ildiko Vancsa
2014-01-14 19:26:14 +01:00
parent 0ca0a0ca65
commit 6ce311d000
7 changed files with 90 additions and 5 deletions

View File

@@ -1015,6 +1015,11 @@ class ValidatedComplexQuery(object):
"minProperties": 1,
"maxProperties": 1}
schema_value_in = {
"type": "array",
"items": {"oneOf": [{"type": "string"},
{"type": "number"}]}}
schema_field = {
"type": "object",
"patternProperties": {"[\S]+": schema_value},
@@ -1022,6 +1027,13 @@ class ValidatedComplexQuery(object):
"minProperties": 1,
"maxProperties": 1}
schema_field_in = {
"type": "object",
"patternProperties": {"[\S]+": schema_value_in},
"additionalProperties": False,
"minProperties": 1,
"maxProperties": 1}
schema_leaf = {
"type": "object",
"patternProperties": {simple_ops: schema_field},
@@ -1029,6 +1041,20 @@ class ValidatedComplexQuery(object):
"minProperties": 1,
"maxProperties": 1}
schema_leaf_in = {
"type": "object",
"patternProperties": {"(?i)^in$": schema_field_in},
"additionalProperties": False,
"minProperties": 1,
"maxProperties": 1}
schema_leaf_simple_ops = {
"type": "object",
"patternProperties": {simple_ops: schema_field},
"additionalProperties": False,
"minProperties": 1,
"maxProperties": 1}
schema_and_or_array = {
"type": "array",
"items": {"$ref": "#"},
@@ -1042,11 +1068,13 @@ class ValidatedComplexQuery(object):
"maxProperties": 1}
schema = {
"oneOf": [{"$ref": "#/definitions/leaf"},
"oneOf": [{"$ref": "#/definitions/leaf_simple_ops"},
{"$ref": "#/definitions/leaf_in"},
{"$ref": "#/definitions/and_or"}],
"minProperties": 1,
"maxProperties": 1,
"definitions": {"leaf": schema_leaf,
"definitions": {"leaf_simple_ops": schema_leaf_simple_ops,
"leaf_in": schema_leaf_in,
"and_or": schema_and_or}}
orderby_schema = {
@@ -1074,6 +1102,9 @@ class ValidatedComplexQuery(object):
self.schema_field["patternProperties"] = {
valid_fields: self.schema_value}
self.schema_field_in["patternProperties"] = {
valid_fields: self.schema_value_in}
self.orderby_schema["items"]["patternProperties"] = {
valid_fields: {"type": "string",
"pattern": self.order_directions}}

View File

@@ -344,7 +344,8 @@ class Connection(base.Connection):
"=<": "$lte",
">=": "$gte",
"=>": "$gte",
"!=": "$ne"}
"!=": "$ne",
"in": "$in"}
complex_operators = {"or": "$or",
"and": "$and"}

View File

@@ -169,6 +169,10 @@ def make_query_from_filter(session, query, sample_filter, require_meter=True):
return query
def operator_in(field_name, field_value):
return field_name.in_(field_value)
class Connection(base.Connection):
"""SqlAlchemy connection."""
@@ -179,7 +183,8 @@ class Connection(base.Connection):
"=<": operator.le,
">=": operator.ge,
"=>": operator.ge,
"!=": operator.ne}
"!=": operator.ne,
"in": operator_in}
complex_operators = {"or": or_,
"and": and_}
ordering_functions = {"asc": asc,

View File

@@ -300,6 +300,14 @@ class TestFilterSyntaxValidation(test.BaseTestCase):
{"=": {"counter_name": "value"}}]}
self.query._validate_filter(filter)
def test_complex_operator_with_in(self):
filter = {"and": [{"<": {"counter_volume": 42}},
{">=": {"counter_volume": 36}},
{"in": {"project_id": ["project_id1",
"project_id2",
"project_id3"]}}]}
self.query._validate_filter(filter)
def test_invalid_complex_operator(self):
filter = {"xor": [{"=": {"project_id": "string_value"}},
{"=": {"resource_id": "value"}}]}

View File

@@ -153,6 +153,18 @@ class TestQueryMetersController(tests_api.FunctionalTest,
self.assertIn(sample['project_id'],
(["project-id1", "project-id2"]))
def test_admin_tenant_sees_every_project_with_in_filter(self):
filter = ('{"In": ' +
'{"project_id": ["project-id1", "project-id2"]}}')
data = self.post_json(self.url,
params={"filter": filter},
headers=admin_header)
self.assertEqual(2, len(data.json))
for sample in data.json:
self.assertIn(sample['project_id'],
(["project-id1", "project-id2"]))
def test_admin_tenant_can_query_any_project(self):
data = self.post_json(self.url,
params={"filter":

View File

@@ -731,6 +731,14 @@ class ComplexSampleQueryTest(DBTestBase,
{"and":
[{"=": {"counter_name": "cpu_util"}},
{"and": and_expression}]}]}
in_expression = {"in": {"resource_id": ["resource-id-42",
"resource-id-43",
"resource-id-44"]}}
self.complex_filter_in = {"and":
[in_expression,
{"and":
[{"=": {"counter_name": "cpu_util"}},
{"and": and_expression}]}]}
def _create_samples(self):
for resource in range(42, 45):
@@ -882,6 +890,26 @@ class ComplexSampleQueryTest(DBTestBase,
orderby=orderby))
self.assertRaises(KeyError, query)
def test_query_complex_filter_with_in(self):
self._create_samples()
results = list(
self.conn.query_samples(filter_expr=self.complex_filter_in))
self.assertEqual(len(results), 9)
for sample in results:
self.assertIn(sample.resource_id,
set(["resource-id-42",
"resource-id-43",
"resource-id-44"]))
self.assertEqual(sample.counter_name,
"cpu_util")
self.assertTrue(sample.counter_volume > 0.4)
self.assertTrue(sample.counter_volume <= 0.8)
def test_query_filter_with_empty_in(self):
results = list(
self.conn.query_samples(filter_expr={"in": {"resource_id": []}}))
self.assertEqual(len(results), 0)
class StatisticsTest(DBTestBase,
tests_db.MixinTestsWithBackendScenarios):

View File

@@ -95,7 +95,7 @@ Complex Query
+++++++++++++
The filter expressions of the Complex Query feature operate on the fields
of *Sample*, *Alarm* and *AlarmChange*. The following comparison operators are
supported: *=*, *!=*, *<*, *<=*, *>* and *>=*; and the following logical
supported: *=*, *!=*, *<*, *<=*, *>*, *>=* and *in*; and the following logical
operators can be used: *and* and *or*. The field names are validated against
the database models.