diff options
Diffstat (limited to 'keystone-moon/keystone/common/sql/core.py')
-rw-r--r-- | keystone-moon/keystone/common/sql/core.py | 117 |
1 files changed, 76 insertions, 41 deletions
diff --git a/keystone-moon/keystone/common/sql/core.py b/keystone-moon/keystone/common/sql/core.py index bf168701..ebd61bb7 100644 --- a/keystone-moon/keystone/common/sql/core.py +++ b/keystone-moon/keystone/common/sql/core.py @@ -239,6 +239,39 @@ def truncated(f): return wrapper +class _WontMatch(Exception): + """Raised to indicate that the filter won't match. + + This is raised to short-circuit the computation of the filter as soon as + it's discovered that the filter requested isn't going to match anything. + + A filter isn't going to match anything if the value is too long for the + field, for example. + + """ + + @classmethod + def check(cls, value, col_attr): + """Check if the value can match given the column attributes. + + Raises this class if the value provided can't match any value in the + column in the table given the column's attributes. For example, if the + column is a string and the value is longer than the column then it + won't match any value in the column in the table. + + """ + col = col_attr.property.columns[0] + if isinstance(col.type, sql.types.Boolean): + # The column is a Boolean, we should have already validated input. + return + if not col.type.length: + # The column doesn't have a length so can't validate anymore. + return + if len(value) > col.type.length: + raise cls() + # Otherwise the value could match a value in the column. + + def _filter(model, query, hints): """Applies filtering to a query. @@ -251,16 +284,14 @@ def _filter(model, query, hints): :returns query: query, updated with any filters satisfied """ - def inexact_filter(model, query, filter_, satisfied_filters, hints): + def inexact_filter(model, query, filter_, satisfied_filters): """Applies an inexact filter to a query. :param model: the table model in question :param query: query to apply filters to - :param filter_: the dict that describes this filter - :param satisfied_filters: a cumulative list of satisfied filters, to - which filter_ will be added if it is - satisfied. - :param hints: contains the list of filters yet to be satisfied. + :param dict filter_: describes this filter + :param list satisfied_filters: filter_ will be added if it is + satisfied. :returns query: query updated to add any inexact filters we could satisfy @@ -278,10 +309,13 @@ def _filter(model, query, hints): return query if filter_['comparator'] == 'contains': + _WontMatch.check(filter_['value'], column_attr) query_term = column_attr.ilike('%%%s%%' % filter_['value']) elif filter_['comparator'] == 'startswith': + _WontMatch.check(filter_['value'], column_attr) query_term = column_attr.ilike('%s%%' % filter_['value']) elif filter_['comparator'] == 'endswith': + _WontMatch.check(filter_['value'], column_attr) query_term = column_attr.ilike('%%%s' % filter_['value']) else: # It's a filter we don't understand, so let the caller @@ -291,53 +325,50 @@ def _filter(model, query, hints): satisfied_filters.append(filter_) return query.filter(query_term) - def exact_filter( - model, filter_, satisfied_filters, cumulative_filter_dict, hints): + def exact_filter(model, filter_, cumulative_filter_dict): """Applies an exact filter to a query. :param model: the table model in question - :param filter_: the dict that describes this filter - :param satisfied_filters: a cumulative list of satisfied filters, to - which filter_ will be added if it is - satisfied. - :param cumulative_filter_dict: a dict that describes the set of - exact filters built up so far - :param hints: contains the list of filters yet to be satisfied. - - :returns: updated cumulative dict + :param dict filter_: describes this filter + :param dict cumulative_filter_dict: describes the set of exact filters + built up so far """ key = filter_['name'] - if isinstance(getattr(model, key).property.columns[0].type, - sql.types.Boolean): + + col = getattr(model, key) + if isinstance(col.property.columns[0].type, sql.types.Boolean): cumulative_filter_dict[key] = ( utils.attr_as_boolean(filter_['value'])) else: + _WontMatch.check(filter_['value'], col) cumulative_filter_dict[key] = filter_['value'] - satisfied_filters.append(filter_) - return cumulative_filter_dict - - filter_dict = {} - satisfied_filters = [] - for filter_ in hints.filters: - if filter_['name'] not in model.attributes: - continue - if filter_['comparator'] == 'equals': - filter_dict = exact_filter( - model, filter_, satisfied_filters, filter_dict, hints) - else: - query = inexact_filter( - model, query, filter_, satisfied_filters, hints) - # Apply any exact filters we built up - if filter_dict: - query = query.filter_by(**filter_dict) + try: + filter_dict = {} + satisfied_filters = [] + for filter_ in hints.filters: + if filter_['name'] not in model.attributes: + continue + if filter_['comparator'] == 'equals': + exact_filter(model, filter_, filter_dict) + satisfied_filters.append(filter_) + else: + query = inexact_filter(model, query, filter_, + satisfied_filters) + + # Apply any exact filters we built up + if filter_dict: + query = query.filter_by(**filter_dict) + + # Remove satisfied filters, then the caller will know remaining filters + for filter_ in satisfied_filters: + hints.filters.remove(filter_) - # Remove satisfied filters, then the caller will know remaining filters - for filter_ in satisfied_filters: - hints.filters.remove(filter_) - - return query + return query + except _WontMatch: + hints.cannot_match = True + return def _limit(query, hints): @@ -378,6 +409,10 @@ def filter_limit_query(model, query, hints): # First try and satisfy any filters query = _filter(model, query, hints) + if hints.cannot_match: + # Nothing's going to match, so don't bother with the query. + return [] + # NOTE(henry-nash): Any unsatisfied filters will have been left in # the hints list for the controller to handle. We can only try and # limit here if all the filters are already satisfied since, if not, |