1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
# Copyright 2010 United States Government as represented by the
4
# Administrator of the National Aeronautics and Space Administration.
5
# Copyright 2010-2011 OpenStack LLC.
6
# Copyright 2012 Justin Santa Barbara
9
# Licensed under the Apache License, Version 2.0 (the "License"); you may
10
# not use this file except in compliance with the License. You may obtain
11
# a copy of the License at
13
# http://www.apache.org/licenses/LICENSE-2.0
15
# Unless required by applicable law or agreed to in writing, software
16
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
17
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
18
# License for the specific language governing permissions and limitations
21
"""Implementation of paginate query."""
25
from nova import exception
26
from nova.openstack.common import log as logging
29
LOG = logging.getLogger(__name__)
32
# copy from glance/db/sqlalchemy/api.py
33
def paginate_query(query, model, limit, sort_keys, marker=None,
34
sort_dir=None, sort_dirs=None):
35
"""Returns a query with sorting / pagination criteria added.
37
Pagination works by requiring a unique sort_key, specified by sort_keys.
38
(If sort_keys is not unique, then we risk looping through values.)
39
We use the last row in the previous page as the 'marker' for pagination.
40
So we must return values that follow the passed marker in the order.
41
With a single-valued sort_key, this would be easy: sort_key > X.
42
With a compound-values sort_key, (k1, k2, k3) we must do this to repeat
43
the lexicographical ordering:
44
(k1 > X1) or (k1 == X1 && k2 > X2) or (k1 == X1 && k2 == X2 && k3 > X3)
46
We also have to cope with different sort_directions.
48
Typically, the id of the last row is used as the client-facing pagination
49
marker, then the actual marker object must be fetched from the db and
50
passed in to us as marker.
52
:param query: the query object to which we should add paging/sorting
53
:param model: the ORM model class
54
:param limit: maximum number of items to return
55
:param sort_keys: array of attributes by which results should be sorted
56
:param marker: the last item of the previous page; we returns the next
57
results after this value.
58
:param sort_dir: direction in which results should be sorted (asc, desc)
59
:param sort_dirs: per-column array of sort_dirs, corresponding to sort_keys
61
:rtype: sqlalchemy.orm.query.Query
62
:return: The query with sorting/pagination added.
65
if 'id' not in sort_keys:
66
# TODO(justinsb): If this ever gives a false-positive, check
67
# the actual primary key, rather than assuming its id
68
LOG.warn(_('Id not in sort_keys; is sort_keys unique?'))
70
assert(not (sort_dir and sort_dirs))
72
# Default the sort direction to ascending
73
if sort_dirs is None and sort_dir is None:
76
# Ensure a per-column sort direction
78
sort_dirs = [sort_dir for _sort_key in sort_keys]
80
assert(len(sort_dirs) == len(sort_keys))
83
for current_sort_key, current_sort_dir in zip(sort_keys, sort_dirs):
85
'asc': sqlalchemy.asc,
86
'desc': sqlalchemy.desc,
90
sort_key_attr = getattr(model, current_sort_key)
91
except AttributeError:
92
raise exception.InvalidSortKey()
93
query = query.order_by(sort_dir_func(sort_key_attr))
96
if marker is not None:
98
for sort_key in sort_keys:
99
v = getattr(marker, sort_key)
100
marker_values.append(v)
102
# Build up an array of sort criteria as in the docstring
104
for i in xrange(0, len(sort_keys)):
106
for j in xrange(0, i):
107
model_attr = getattr(model, sort_keys[j])
108
crit_attrs.append((model_attr == marker_values[j]))
110
model_attr = getattr(model, sort_keys[i])
111
if sort_dirs[i] == 'desc':
112
crit_attrs.append((model_attr < marker_values[i]))
113
elif sort_dirs[i] == 'asc':
114
crit_attrs.append((model_attr > marker_values[i]))
116
raise ValueError(_("Unknown sort direction, "
117
"must be 'desc' or 'asc'"))
119
criteria = sqlalchemy.sql.and_(*crit_attrs)
120
criteria_list.append(criteria)
122
f = sqlalchemy.sql.or_(*criteria_list)
123
query = query.filter(f)
125
if limit is not None:
126
query = query.limit(limit)