~ubuntu-branches/ubuntu/raring/cinder/raring-updates

« back to all changes in this revision

Viewing changes to cinder/common/sqlalchemyutils.py

Tags: upstream-2013.1~g2
ImportĀ upstreamĀ versionĀ 2013.1~g2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
2
 
 
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
 
7
# All Rights Reserved.
 
8
#
 
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
 
12
#
 
13
#         http://www.apache.org/licenses/LICENSE-2.0
 
14
#
 
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
 
19
#    under the License.
 
20
 
 
21
"""Implementation of paginate query."""
 
22
 
 
23
import sqlalchemy
 
24
 
 
25
from cinder import exception
 
26
from cinder.openstack.common import log as logging
 
27
 
 
28
 
 
29
LOG = logging.getLogger(__name__)
 
30
 
 
31
 
 
32
# copied 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.
 
36
 
 
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)
 
45
 
 
46
    We also have to cope with different sort_directions.
 
47
 
 
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.
 
51
 
 
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
 
60
 
 
61
    :rtype: sqlalchemy.orm.query.Query
 
62
    :return: The query with sorting/pagination added.
 
63
    """
 
64
 
 
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?'))
 
69
 
 
70
    assert(not (sort_dir and sort_dirs))
 
71
 
 
72
    # Default the sort direction to ascending
 
73
    if sort_dirs is None and sort_dir is None:
 
74
        sort_dir = 'asc'
 
75
 
 
76
    # Ensure a per-column sort direction
 
77
    if sort_dirs is None:
 
78
        sort_dirs = [sort_dir for _sort_key in sort_keys]
 
79
 
 
80
    assert(len(sort_dirs) == len(sort_keys))
 
81
 
 
82
    # Add sorting
 
83
    for current_sort_key, current_sort_dir in zip(sort_keys, sort_dirs):
 
84
        sort_dir_func = {
 
85
            'asc': sqlalchemy.asc,
 
86
            'desc': sqlalchemy.desc,
 
87
        }[current_sort_dir]
 
88
 
 
89
        try:
 
90
            sort_key_attr = getattr(model, current_sort_key)
 
91
        except AttributeError:
 
92
            raise exception.InvalidInput(reason='Invalid sort key')
 
93
        query = query.order_by(sort_dir_func(sort_key_attr))
 
94
 
 
95
    # Add pagination
 
96
    if marker is not None:
 
97
        marker_values = []
 
98
        for sort_key in sort_keys:
 
99
            v = getattr(marker, sort_key)
 
100
            marker_values.append(v)
 
101
 
 
102
        # Build up an array of sort criteria as in the docstring
 
103
        criteria_list = []
 
104
        for i in xrange(0, len(sort_keys)):
 
105
            crit_attrs = []
 
106
            for j in xrange(0, i):
 
107
                model_attr = getattr(model, sort_keys[j])
 
108
                crit_attrs.append((model_attr == marker_values[j]))
 
109
 
 
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]))
 
115
            else:
 
116
                raise ValueError(_("Unknown sort direction, "
 
117
                                   "must be 'desc' or 'asc'"))
 
118
 
 
119
            criteria = sqlalchemy.sql.and_(*crit_attrs)
 
120
            criteria_list.append(criteria)
 
121
 
 
122
        f = sqlalchemy.sql.or_(*criteria_list)
 
123
        query = query.filter(f)
 
124
 
 
125
    if limit is not None:
 
126
        query = query.limit(limit)
 
127
 
 
128
    return query