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:
Hongbin Lu 2018-10-11 22:46:37 +00:00
parent 0ecceacb3e
commit dafbd30ef8
5 changed files with 111 additions and 11 deletions

View File

@ -174,16 +174,8 @@ def apply_filters(query, model, filters, context=None):
# do multiple equals matches
query = query.filter(
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))
elif isinstance(value, obj_utils.FilterObj):
query = query.filter(value.filter(column))
elif None in value:
# in_() operator does not support NULL element so we have
# to do multiple equals matches

View File

@ -10,8 +10,11 @@
# License for the specific language governing permissions and limitations
# under the License.
import abc
import copy
import six
from neutron_lib import exceptions
@ -25,7 +28,15 @@ def convert_filters(**kwargs):
return result
class StringMatchingFilterObj(object):
@six.add_metaclass(abc.ABCMeta)
class FilterObj(object):
@abc.abstractmethod
def filter(self, column):
pass
class StringMatchingFilterObj(FilterObj):
@property
def is_contains(self):
return bool(getattr(self, "contains", False))
@ -45,6 +56,9 @@ class StringContains(StringMatchingFilterObj):
super(StringContains, self).__init__()
self.contains = matching_string
def filter(self, column):
return column.contains(self.contains)
class StringStarts(StringMatchingFilterObj):
@ -52,9 +66,35 @@ class StringStarts(StringMatchingFilterObj):
super(StringStarts, self).__init__()
self.starts = matching_string
def filter(self, column):
return column.startswith(self.starts)
class StringEnds(StringMatchingFilterObj):
def __init__(self, matching_string):
super(StringEnds, self).__init__()
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

View 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))

View File

@ -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``.