Support custom filters in OVO
In sqlalchemy, there are some advanced filter criterion but such filter criterion is not supported in OVO. An example is the "not in" criterion which is achieved by "query.filter(~db_model.column.in_(values))". Another example is the "not equal" criterion which is achieved by "query.filter(db_model.column != value)" This commit adds support for custom filtering. We introduce a base class called "FilterObj" from which the custom filter class should inherit. This commit also implements two filter class: one for implementing the "not in" criterion and the other for the "not equal" criterion. In addition, it makes StringMatchingFilterObj inherit from the FilterObj class. Needed-By: https://review.openstack.org/#/c/609848/ Change-Id: I9ac7fb000d2bed445efbc226c30abdcd981b90cb Partial-Implements: blueprint adopt-oslo-versioned-objects-for-db
This commit is contained in:
parent
0ecceacb3e
commit
dafbd30ef8
@ -174,16 +174,8 @@ 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):
|
elif isinstance(value, obj_utils.FilterObj):
|
||||||
if value.is_contains:
|
query = query.filter(value.filter(column))
|
||||||
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))
|
|
||||||
elif None in value:
|
elif None in value:
|
||||||
# in_() operator does not support NULL element so we have
|
# in_() operator does not support NULL element so we have
|
||||||
# to do multiple equals matches
|
# to do multiple equals matches
|
||||||
|
@ -10,8 +10,11 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import abc
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
from neutron_lib import exceptions
|
from neutron_lib import exceptions
|
||||||
|
|
||||||
|
|
||||||
@ -25,7 +28,15 @@ def convert_filters(**kwargs):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
class StringMatchingFilterObj(object):
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class FilterObj(object):
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def filter(self, column):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class StringMatchingFilterObj(FilterObj):
|
||||||
@property
|
@property
|
||||||
def is_contains(self):
|
def is_contains(self):
|
||||||
return bool(getattr(self, "contains", False))
|
return bool(getattr(self, "contains", False))
|
||||||
@ -45,6 +56,9 @@ class StringContains(StringMatchingFilterObj):
|
|||||||
super(StringContains, self).__init__()
|
super(StringContains, self).__init__()
|
||||||
self.contains = matching_string
|
self.contains = matching_string
|
||||||
|
|
||||||
|
def filter(self, column):
|
||||||
|
return column.contains(self.contains)
|
||||||
|
|
||||||
|
|
||||||
class StringStarts(StringMatchingFilterObj):
|
class StringStarts(StringMatchingFilterObj):
|
||||||
|
|
||||||
@ -52,9 +66,35 @@ class StringStarts(StringMatchingFilterObj):
|
|||||||
super(StringStarts, self).__init__()
|
super(StringStarts, self).__init__()
|
||||||
self.starts = matching_string
|
self.starts = matching_string
|
||||||
|
|
||||||
|
def filter(self, column):
|
||||||
|
return column.startswith(self.starts)
|
||||||
|
|
||||||
|
|
||||||
class StringEnds(StringMatchingFilterObj):
|
class StringEnds(StringMatchingFilterObj):
|
||||||
|
|
||||||
def __init__(self, matching_string):
|
def __init__(self, matching_string):
|
||||||
super(StringEnds, self).__init__()
|
super(StringEnds, self).__init__()
|
||||||
self.ends = matching_string
|
self.ends = matching_string
|
||||||
|
|
||||||
|
def filter(self, column):
|
||||||
|
return column.endswith(self.ends)
|
||||||
|
|
||||||
|
|
||||||
|
class NotIn(FilterObj):
|
||||||
|
|
||||||
|
def __init__(self, value):
|
||||||
|
super(NotIn, self).__init__()
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def filter(self, column):
|
||||||
|
return ~column.in_(self.value)
|
||||||
|
|
||||||
|
|
||||||
|
class NotEqual(FilterObj):
|
||||||
|
|
||||||
|
def __init__(self, value):
|
||||||
|
super(NotEqual, self).__init__()
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def filter(self, column):
|
||||||
|
return column != self.value
|
||||||
|
0
neutron_lib/tests/unit/objects/__init__.py
Normal file
0
neutron_lib/tests/unit/objects/__init__.py
Normal file
59
neutron_lib/tests/unit/objects/test_utils.py
Normal file
59
neutron_lib/tests/unit/objects/test_utils.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from neutron_lib.objects import utils as obj_utils
|
||||||
|
from neutron_lib.tests import _base as base
|
||||||
|
|
||||||
|
|
||||||
|
class TestUtils(base.BaseTestCase):
|
||||||
|
|
||||||
|
def test_get_objects_with_filters_not_in(self):
|
||||||
|
|
||||||
|
class FakeColumn(object):
|
||||||
|
def __init__(self, column):
|
||||||
|
self.column = column
|
||||||
|
|
||||||
|
def in_(self, value):
|
||||||
|
self.value = value
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __invert__(self):
|
||||||
|
return list(set(self.column) - set(self.value))
|
||||||
|
|
||||||
|
filter_obj = obj_utils.NotIn([1, 2, 3])
|
||||||
|
fake_column = FakeColumn([1, 2, 4, 5])
|
||||||
|
self.assertEqual([4, 5], filter_obj.filter(fake_column))
|
||||||
|
|
||||||
|
fake_column = FakeColumn([1, 2])
|
||||||
|
self.assertEqual([], filter_obj.filter(fake_column))
|
||||||
|
|
||||||
|
fake_column = FakeColumn([4, 5])
|
||||||
|
self.assertEqual([4, 5], filter_obj.filter(fake_column))
|
||||||
|
|
||||||
|
def test_get_objects_with_filters_not_equal(self):
|
||||||
|
|
||||||
|
class FakeColumn(object):
|
||||||
|
def __init__(self, column):
|
||||||
|
self.column = column
|
||||||
|
|
||||||
|
def __ne__(self, value):
|
||||||
|
return [item for item in self.column if item != value]
|
||||||
|
|
||||||
|
filter_obj = obj_utils.NotEqual(1)
|
||||||
|
fake_column = FakeColumn([1, 2, 4, 5])
|
||||||
|
self.assertEqual([2, 4, 5], filter_obj.filter(fake_column))
|
||||||
|
|
||||||
|
fake_column = FakeColumn([1])
|
||||||
|
self.assertEqual([], filter_obj.filter(fake_column))
|
||||||
|
|
||||||
|
fake_column = FakeColumn([4, 5])
|
||||||
|
self.assertEqual([4, 5], filter_obj.filter(fake_column))
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
prelude: >
|
||||||
|
This release adds support for custom filtering in versioned object.
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
A class called ``FilterObj`` is introduced.
|
||||||
|
This is the base class from which the custom filter class should inherit.
|
||||||
|
This release also implements two filter class: ``NotIn`` and ``NotEqual``.
|
||||||
|
The class ``StringMatchingFilterObj`` is now a subclass of ``FilterObj``.
|
Loading…
Reference in New Issue
Block a user