Merge "Support object string field filtering on "LIKE" statement"

This commit is contained in:
Jenkins 2017-07-07 22:58:04 +00:00 committed by Gerrit Code Review
commit e939092006
6 changed files with 119 additions and 3 deletions

View File

@ -71,6 +71,11 @@ using example of DNSNameServer:
dnses = DNSNameServer.get_objects(context) dnses = DNSNameServer.get_objects(context)
# will return list of all dns name servers from DB # will return list of all dns name servers from DB
# for fetching objects with substrings in a string field:
from neutron.objects import utils as obj_utils
dnses = DNSNameServer.get_objects(context, address=obj_utils.StringMatchingContains('10.0.0'))
# will return list of all dns name servers from DB that has '10.0.0' in their addresses
# to update fields: # to update fields:
dns = DNSNameServer.get_object(context, address='asd', subnet_id='xxx') dns = DNSNameServer.get_object(context, address='asd', subnet_id='xxx')
dns.order = 2 dns.order = 2

View File

@ -24,6 +24,7 @@ from sqlalchemy.ext import associationproxy
from neutron.common import utils from neutron.common import utils
from neutron.db import _utils as ndb_utils from neutron.db import _utils as ndb_utils
from neutron.objects import utils as obj_utils
# Classes implementing extensions will register hooks into this dictionary # Classes implementing extensions will register hooks into this dictionary
# for "augmenting" the "core way" of building a query for retrieving objects # for "augmenting" the "core way" of building a query for retrieving objects
@ -190,6 +191,16 @@ def apply_filters(query, model, filters, context=None):
# do multiple equals matches # do multiple equals matches
query = query.filter( query = query.filter(
or_(*[column == v for v in value])) or_(*[column == v for v in value]))
elif isinstance(value, obj_utils.StringMatchingFilterObj):
if value.is_contains:
query = query.filter(
column.contains(value.contains))
elif value.is_starts:
query = query.filter(
column.startswith(value.starts))
elif value.is_ends:
query = query.filter(
column.endswith(value.ends))
else: else:
query = query.filter(column.in_(value)) query = query.filter(column.in_(value))
elif key == 'shared' and hasattr(model, 'rbac_entries'): elif key == 'shared' and hasattr(model, 'rbac_entries'):

View File

@ -21,6 +21,7 @@ from neutron.db.models import agent as agent_model
from neutron.db.models import l3agent as rb_model from neutron.db.models import l3agent as rb_model
from neutron.objects import base from neutron.objects import base
from neutron.objects import common_types from neutron.objects import common_types
from neutron.objects import utils as obj_utils
@obj_base.VersionedObjectRegistry.register @obj_base.VersionedObjectRegistry.register
@ -50,11 +51,15 @@ class Agent(base.NeutronDbObject):
@classmethod @classmethod
def modify_fields_to_db(cls, fields): def modify_fields_to_db(cls, fields):
result = super(Agent, cls).modify_fields_to_db(fields) result = super(Agent, cls).modify_fields_to_db(fields)
if 'configurations' in result: if ('configurations' in result and
not isinstance(result['configurations'],
obj_utils.StringMatchingFilterObj)):
# dump configuration into string, set '' if empty '{}' # dump configuration into string, set '' if empty '{}'
result['configurations'] = ( result['configurations'] = (
cls.filter_to_json_str(result['configurations'], default='')) cls.filter_to_json_str(result['configurations'], default=''))
if 'resource_versions' in result: if ('resource_versions' in result and
not isinstance(result['resource_versions'],
obj_utils.StringMatchingFilterObj)):
# dump resource version into string, set None if empty '{}' or None # dump resource version into string, set None if empty '{}' or None
result['resource_versions'] = ( result['resource_versions'] = (
cls.filter_to_json_str(result['resource_versions'])) cls.filter_to_json_str(result['resource_versions']))

View File

@ -17,6 +17,7 @@ from neutron_lib import exceptions as n_exc
from oslo_utils import uuidutils from oslo_utils import uuidutils
from neutron.db import _model_query as model_query from neutron.db import _model_query as model_query
from neutron.objects import utils as obj_utils
# Common database operation implementations # Common database operation implementations
@ -36,7 +37,9 @@ def count(context, model, **kwargs):
def _kwargs_to_filters(**kwargs): def _kwargs_to_filters(**kwargs):
return {k: v if isinstance(v, list) else [v] return {k: v if (isinstance(v, list) or
isinstance(v, obj_utils.StringMatchingFilterObj))
else [v]
for k, v in kwargs.items()} for k, v in kwargs.items()}

View File

@ -23,3 +23,39 @@ def convert_filters(**kwargs):
result['project_id'] = result.pop('tenant_id') result['project_id'] = result.pop('tenant_id')
return result return result
class StringMatchingFilterObj(object):
@property
def is_contains(self):
return bool(getattr(self, "contains"))
@property
def is_starts(self):
return bool(getattr(self, "starts"))
@property
def is_ends(self):
return bool(getattr(self, "ends"))
class StringContains(StringMatchingFilterObj):
def __init__(self, matching_string):
super(StringContains, self).__init__()
self.contains = matching_string
class StringStarts(StringMatchingFilterObj):
def __init__(self, matching_string):
super(StringStarts, self).__init__()
self.starts = matching_string
class StringEnds(StringMatchingFilterObj):
def __init__(self, matching_string):
super(StringEnds, self).__init__()
self.ends = matching_string

View File

@ -47,6 +47,7 @@ from neutron.objects.qos import policy as qos_policy
from neutron.objects import rbac_db from neutron.objects import rbac_db
from neutron.objects import securitygroup from neutron.objects import securitygroup
from neutron.objects import subnet from neutron.objects import subnet
from neutron.objects import utils as obj_utils
from neutron.tests import base as test_base from neutron.tests import base as test_base
from neutron.tests import tools from neutron.tests import tools
from neutron.tests.unit.db import test_db_base_plugin_v2 from neutron.tests.unit.db import test_db_base_plugin_v2
@ -855,6 +856,61 @@ class BaseObjectIfaceTestCase(_BaseObjectTestCase, test_base.BaseTestCase):
self.context, {'unknown_filter': 'new_value'}, self.context, {'unknown_filter': 'new_value'},
validate_filters=False, unknown_filter='value') validate_filters=False, unknown_filter='value')
def _prep_string_field(self):
self.filter_string_field = None
# find the first string field to use as string matching filter
for field in self.obj_fields[0]:
if isinstance(field, obj_fields.StringField):
self.filter_string_field = field
break
if self.filter_string_field is None:
self.skipTest('There is no string field in this object')
def test_get_objects_with_string_matching_filters_contains(self):
self._prep_string_field()
filter_dict_contains = {
self.filter_string_field: obj_utils.StringContains(
"random_thing")}
with mock.patch.object(
obj_db_api, 'get_objects',
side_effect=self.fake_get_objects):
res = self._test_class.get_objects(self.context,
**filter_dict_contains)
self.assertEqual([], res)
def test_get_objects_with_string_matching_filters_starts(self):
self._prep_string_field()
filter_dict_starts = {
self.filter_string_field: obj_utils.StringStarts(
"random_thing")
}
with mock.patch.object(
obj_db_api, 'get_objects',
side_effect=self.fake_get_objects):
res = self._test_class.get_objects(self.context,
**filter_dict_starts)
self.assertEqual([], res)
def test_get_objects_with_string_matching_filters_ends(self):
self._prep_string_field()
filter_dict_ends = {
self.filter_string_field: obj_utils.StringEnds(
"random_thing")
}
with mock.patch.object(
obj_db_api, 'get_objects',
side_effect=self.fake_get_objects):
res = self._test_class.get_objects(self.context,
**filter_dict_ends)
self.assertEqual([], res)
def test_delete_objects(self): def test_delete_objects(self):
'''Test that delete_objects calls to underlying db_api.''' '''Test that delete_objects calls to underlying db_api.'''
with mock.patch.object( with mock.patch.object(