Merge "Support object string field filtering on "LIKE" statement"
This commit is contained in:
commit
e939092006
@ -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
|
||||||
|
@ -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'):
|
||||||
|
@ -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']))
|
||||||
|
@ -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()}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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(
|
||||||
|
Loading…
Reference in New Issue
Block a user