Skip to content

Commit 26e9399

Browse files
committed
Implement limit and offset for filter
These map to the corresponding SQL parameters for the SQL backends.
1 parent 02b4b28 commit 26e9399

File tree

6 files changed

+69
-14
lines changed

6 files changed

+69
-14
lines changed

CHANGES.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@ Features:
8585
- issue2551043: Add X-Roundup-issue-id email header. Add a new header
8686
to make it easier to filter notification emails without having to
8787
parse the subject line. (John Rouillard)
88-
- The database filter function now can also do an exact string search.
88+
- The database filter method now can also do an exact string search.
89+
- The database filter method now has limit and offset parameters that
90+
map to the corresponging parameters of SQL.
8991

9092
Fixed:
9193

doc/design.txt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,7 @@ Here is the interface provided by the hyperdatabase::
516516
"""
517517

518518
def filter(self, search_matches, filterspec, sort, group,
519-
retired, exact_match_spec):
519+
retired, exact_match_spec, limit, offset):
520520
"""Return a list of the ids of the active nodes in this class that
521521
match the 'filter' spec, sorted by the group spec and then the
522522
sort spec. The arguments sort, group, retired, and
@@ -544,6 +544,14 @@ Here is the interface provided by the hyperdatabase::
544544
items are returned. The default is False, i.e. only live
545545
items are returned by default.
546546

547+
The "limit" and "offset" parameters define a limit on the
548+
number of results returned and an offset before returning
549+
any results, respectively. These can be used when displaying
550+
a number of items in a pagination application or similar. A
551+
common use-case is returning the first item of a sorted
552+
search by specifying limit=1 (i.e. the maximum or minimum
553+
depending on sort order).
554+
547555
The filter must match all properties specificed. If the property
548556
value to match is a list:
549557

roundup/backends/back_sqlite.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -454,14 +454,11 @@ def load_journal(self, classname, cols, nodeid):
454454
return [[row[col] for col in cols] for row in l]
455455

456456
class sqliteClass:
457-
def filter(self, search_matches, filterspec, sort=(None,None),
458-
group=(None,None), retired=False, exact_match_spec={}):
457+
def filter(self, *args, **kw):
459458
""" If there's NO matches to a fetch, sqlite returns NULL
460459
instead of nothing
461460
"""
462-
return [f for f in rdbms_common.Class.filter(self, search_matches,
463-
filterspec, sort=sort, group=group, retired=retired,
464-
exact_match_spec=exact_match_spec) if f]
461+
return [f for f in rdbms_common.Class.filter(self, *args, **kw) if f]
465462

466463
class Class(sqliteClass, rdbms_common.Class):
467464
pass

roundup/backends/rdbms_common.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2403,7 +2403,8 @@ def collect_values(n): values.append(n.x)
24032403
return where, v, True # True to indicate original
24042404

24052405
def _filter_sql (self, search_matches, filterspec, srt=[], grp=[], retr=0,
2406-
retired=False, exact_match_spec={}):
2406+
retired=False, exact_match_spec={}, limit=None,
2407+
offset=None):
24072408
""" Compute the proptree and the SQL/ARGS for a filter.
24082409
For argument description see filter below.
24092410
We return a 3-tuple, the proptree, the sql and the sql-args
@@ -2717,15 +2718,24 @@ def _filter_sql (self, search_matches, filterspec, srt=[], grp=[], retr=0,
27172718
else:
27182719
order = ''
27192720

2721+
if limit is not None:
2722+
limit = ' LIMIT %s' % limit
2723+
else:
2724+
limit = ''
2725+
if offset is not None:
2726+
offset = ' OFFSET %s' % offset
2727+
else:
2728+
offset = ''
27202729
cols = ','.join(cols)
27212730
loj = ' '.join(loj)
2722-
sql = 'select %s from %s %s %s%s'%(cols, frum, loj, where, order)
2731+
sql = 'select %s from %s %s %s%s%s%s'%(
2732+
cols, frum, loj, where, order, limit, offset)
27232733
args = tuple(args)
27242734
__traceback_info__ = (sql, args)
27252735
return proptree, sql, args
27262736

27272737
def filter(self, search_matches, filterspec, sort=[], group=[],
2728-
retired=False, exact_match_spec={}):
2738+
retired=False, exact_match_spec={}, limit=None, offset=None):
27292739
"""Return a list of the ids of the active nodes in this class that
27302740
match the 'filter' spec, sorted by the group spec and then the
27312741
sort spec
@@ -2750,7 +2760,8 @@ def filter(self, search_matches, filterspec, sort=[], group=[],
27502760

27512761
sq = self._filter_sql (search_matches, filterspec, sort, group,
27522762
retired=retired,
2753-
exact_match_spec=exact_match_spec)
2763+
exact_match_spec=exact_match_spec,
2764+
limit=limit, offset=offset)
27542765
# nothing to match?
27552766
if sq is None:
27562767
return []
@@ -2774,7 +2785,8 @@ def filter(self, search_matches, filterspec, sort=[], group=[],
27742785
return l
27752786

27762787
def filter_iter(self, search_matches, filterspec, sort=[], group=[],
2777-
retired=False, exact_match_spec={}):
2788+
retired=False, exact_match_spec={}, limit=None,
2789+
offset=None):
27782790
"""Iterator similar to filter above with same args.
27792791
Limitation: We don't sort on multilinks.
27802792
This uses an optimisation: We put all nodes that are in the
@@ -2785,7 +2797,8 @@ def filter_iter(self, search_matches, filterspec, sort=[], group=[],
27852797
"""
27862798
sq = self._filter_sql(search_matches, filterspec, sort, group, retr=1,
27872799
retired=retired,
2788-
exact_match_spec=exact_match_spec)
2800+
exact_match_spec=exact_match_spec,
2801+
limit=limit, offset=offset)
27892802
# nothing to match?
27902803
if sq is None:
27912804
return

roundup/hyperdb.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1399,7 +1399,7 @@ def _sortattr(self, sort=[], group=[]):
13991399
return sortattr
14001400

14011401
def filter(self, search_matches, filterspec, sort=[], group=[],
1402-
retired=False, exact_match_spec={}):
1402+
retired=False, exact_match_spec={}, limit=None, offset=None):
14031403
"""Return a list of the ids of the active nodes in this class that
14041404
match the 'filter' spec, sorted by the group spec and then the
14051405
sort spec.
@@ -1426,6 +1426,13 @@ def filter(self, search_matches, filterspec, sort=[], group=[],
14261426
are returned. The default is False, i.e. only live items are
14271427
returned by default.
14281428
1429+
The "limit" and "offset" parameters define a limit on the number
1430+
of results returned and an offset before returning any results,
1431+
respectively. These can be used when displaying a number of
1432+
items in a pagination application or similar. A common use-case
1433+
is returning the first item of a sorted search by specifying
1434+
limit=1 (i.e. the maximum or minimum depending on sort order).
1435+
14291436
The filter must match all properties specificed. If the property
14301437
value to match is a list:
14311438
@@ -1453,8 +1460,17 @@ def filter(self, search_matches, filterspec, sort=[], group=[],
14531460
sortattr = self._sortattr(sort = sort, group = group)
14541461
proptree = self._proptree(exact_match_spec, filterspec, sortattr)
14551462
proptree.search(search_matches, retired=retired)
1463+
if offset is not None or limit is not None:
1464+
items = proptree.sort()
1465+
if limit and offset:
1466+
return items[offset:offset+limit]
1467+
elif offset is not None:
1468+
return items[offset:]
1469+
else:
1470+
return items[:limit]
14561471
return proptree.sort()
14571472

1473+
14581474
# non-optimized filter_iter, a backend may chose to implement a
14591475
# better version that provides a real iterator that pre-fills the
14601476
# cache for each id returned. Note that the filter_iter doesn't

test/db_test_base.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2055,6 +2055,25 @@ def testFilteringTransitiveLinkUser(self):
20552055
ae(f(None, {'supervisor.supervisor': '3', 'supervisor': '4'},
20562056
('+','username')), ['6', '7'])
20572057

2058+
def testFilteringTransitiveLinkUserLimit(self):
2059+
ae, filter, filter_iter = self.filteringSetupTransitiveSearch('user')
2060+
for f in filter, filter_iter:
2061+
ae(f(None, {'supervisor.username': 'ceo'}, ('+','username'),
2062+
limit=1), ['4'])
2063+
ae(f(None, {'supervisor.supervisor.username': 'ceo'},
2064+
('+','username'), limit=4), ['6', '7', '8', '9'])
2065+
ae(f(None, {'supervisor.supervisor': '3'}, ('+','username'),
2066+
limit=2, offset=2), ['8', '9'])
2067+
ae(f(None, {'supervisor.supervisor.id': '3'}, ('+','username'),
2068+
limit=3, offset=1), ['7', '8', '9'])
2069+
ae(f(None, {'supervisor.username': 'grouplead2'}, ('+','username'),
2070+
limit=2, offset=2), ['10'])
2071+
ae(f(None, {'supervisor.username': 'grouplead2',
2072+
'supervisor.supervisor.username': 'ceo'}, ('+','username'),
2073+
limit=4, offset=3), [])
2074+
ae(f(None, {'supervisor.supervisor': '3', 'supervisor': '4'},
2075+
('+','username'), limit=1, offset=5), [])
2076+
20582077
def testFilteringTransitiveLinkSort(self):
20592078
ae, filter, filter_iter = self.filteringSetupTransitiveSearch()
20602079
ae, ufilter, ufilter_iter = self.iterSetup('user')

0 commit comments

Comments
 (0)