Skip to content

Commit a2b9e3e

Browse files
author
Ralf Schlatterbeck
committed
Null-value sorting fixes:
- Fixed sorting of NULL values for the PostgreSQL backend. Generic implementation of additional sort-key for NULL values, can be reused by other (future) rdbms backends. - Added a test for NULL value sorting and removed an override for the Interval test for the PostgreSQL backend which had a special case for NULL values
1 parent 04af3bd commit a2b9e3e

File tree

4 files changed

+47
-21
lines changed

4 files changed

+47
-21
lines changed

roundup/backends/back_postgresql.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#$Id: back_postgresql.py,v 1.32 2006-08-11 00:44:00 richard Exp $
1+
#$Id: back_postgresql.py,v 1.33 2006-08-23 12:57:10 schlatterbeck Exp $
22
#
33
# Copyright (c) 2003 Martynas Sklyzmantas, Andrey Lebedev <[email protected]>
44
#
@@ -223,11 +223,13 @@ def clear(self):
223223
self.cursor.execute('DROP SEQUENCE _%s_ids'%cn)
224224
self.cursor.execute('CREATE SEQUENCE _%s_ids'%cn)
225225

226+
class PostgresqlClass:
227+
order_by_null_values = '(%s is not NULL)'
226228

227-
class Class(rdbms_common.Class):
229+
class Class(PostgresqlClass, rdbms_common.Class):
228230
pass
229-
class IssueClass(rdbms_common.IssueClass):
231+
class IssueClass(PostgresqlClass, rdbms_common.IssueClass):
230232
pass
231-
class FileClass(rdbms_common.FileClass):
233+
class FileClass(PostgresqlClass, rdbms_common.FileClass):
232234
pass
233235

roundup/backends/rdbms_common.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
#$Id: rdbms_common.py,v 1.176 2006-08-22 19:27:36 schlatterbeck Exp $
18+
#$Id: rdbms_common.py,v 1.177 2006-08-23 12:57:10 schlatterbeck Exp $
1919
''' Relational database (SQL) backend common code.
2020
2121
Basics:
@@ -2024,6 +2024,14 @@ def _subselect(self, classname, multilink_table):
20242024
return '_%s.id not in (select nodeid from %s)'%(classname,
20252025
multilink_table)
20262026

2027+
# Some DBs order NULL values last. Set this variable in the backend
2028+
# for prepending an order by clause for each attribute that causes
2029+
# correct sort order for NULLs. Examples:
2030+
# order_by_null_values = '(%s is not NULL)'
2031+
# order_by_null_values = 'notnull(%s)'
2032+
# The format parameter is replaced with the attribute.
2033+
order_by_null_values = None
2034+
20272035
def filter(self, search_matches, filterspec, sort=[], group=[]):
20282036
'''Return a list of the ids of the active nodes in this class that
20292037
match the 'filter' spec, sorted by the group spec and then the
@@ -2242,9 +2250,13 @@ def filter(self, search_matches, filterspec, sort=[], group=[]):
22422250
# Don't select top-level id twice
22432251
if p.name != 'id' or p.parent != proptree:
22442252
ordercols.append(oc)
2245-
if p.sort_direction == '-':
2246-
oc += ' desc'
2247-
orderby.append(oc)
2253+
desc = ['', ' desc'][p.sort_direction == '-']
2254+
# Some SQL dbs sort NULL values last -- we want them first.
2255+
if (self.order_by_null_values and p.name != 'id'):
2256+
nv = self.order_by_null_values % oc
2257+
ordercols.append(nv)
2258+
orderby.append(nv + desc)
2259+
orderby.append(oc + desc)
22482260

22492261
props = self.getprops()
22502262

test/db_test_base.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
# $Id: db_test_base.py,v 1.76 2006-08-22 19:33:02 schlatterbeck Exp $
18+
# $Id: db_test_base.py,v 1.77 2006-08-23 12:57:10 schlatterbeck Exp $
1919

2020
import unittest, os, shutil, errno, imp, sys, time, pprint, sets
2121

@@ -1216,8 +1216,8 @@ def testFilteringTransitiveLinkSort(self):
12161216
ae, filt = self.filteringSetupTransitiveSearch()
12171217
ufilt = self.db.user.filter
12181218
# Need to make ceo his own (and first two users') supervisor,
1219-
# otherwise sorting for postgreSQL will fail: postgreSQL seems
1220-
# to sort NULLs last.
1219+
# otherwise we will depend on sorting order of NULL values.
1220+
# Leave that to a separate test.
12211221
self.db.user.set('1', supervisor = '3')
12221222
self.db.user.set('2', supervisor = '3')
12231223
self.db.user.set('3', supervisor = '3')
@@ -1253,6 +1253,27 @@ def testFilteringTransitiveLinkSort(self):
12531253
('-','assignedto.supervisor'), ('+','assignedto'), ('+','status')]),
12541254
['4', '5', '7', '6', '8', '1', '2', '3'])
12551255

1256+
def testFilteringTransitiveLinkSortNull(self):
1257+
"""Check sorting of NULL values"""
1258+
ae, filt = self.filteringSetupTransitiveSearch()
1259+
ufilt = self.db.user.filter
1260+
ae(ufilt(None, {}, [('+','supervisor.supervisor.supervisor'),
1261+
('+','supervisor.supervisor'), ('+','supervisor'),
1262+
('+','username')]),
1263+
['1', '3', '2', '4', '5', '6', '7', '8', '9', '10'])
1264+
ae(ufilt(None, {}, [('+','supervisor.supervisor.supervisor'),
1265+
('-','supervisor.supervisor'), ('-','supervisor'),
1266+
('+','username')]),
1267+
['8', '9', '10', '6', '7', '4', '5', '1', '3', '2'])
1268+
ae(filt(None, {}, [('+','assignedto.supervisor.supervisor.supervisor'),
1269+
('+','assignedto.supervisor.supervisor'),
1270+
('+','assignedto.supervisor'), ('+','assignedto')]),
1271+
['1', '2', '3', '4', '5', '6', '7', '8'])
1272+
ae(filt(None, {}, [('+','assignedto.supervisor.supervisor.supervisor'),
1273+
('+','assignedto.supervisor.supervisor'),
1274+
('-','assignedto.supervisor'), ('+','assignedto')]),
1275+
['4', '5', '6', '7', '8', '1', '2', '3'])
1276+
12561277
def testFilteringTransitiveLinkIssue(self):
12571278
ae, filt = self.filteringSetupTransitiveSearch()
12581279
ae(filt(None, {'assignedto.supervisor.username': 'grouplead1'},

test/test_postgresql.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
# $Id: test_postgresql.py,v 1.12 2004-11-03 01:34:21 richard Exp $
18+
# $Id: test_postgresql.py,v 1.13 2006-08-23 12:57:10 schlatterbeck Exp $
1919

2020
import unittest
2121

@@ -48,15 +48,6 @@ def tearDown(self):
4848
DBTest.tearDown(self)
4949
postgresqlOpener.tearDown(self)
5050

51-
def testFilteringIntervalSort(self):
52-
# PostgreSQL sorts NULLs differently to other databases (others
53-
# treat it as lower than real values, PG treats it as higher)
54-
ae, filt = self.filteringSetup()
55-
# ascending should sort None, 1:10, 1d
56-
ae(filt(None, {}, ('+','foo'), (None,None)), ['4', '1', '2', '3'])
57-
# descending should sort 1d, 1:10, None
58-
ae(filt(None, {}, ('-','foo'), (None,None)), ['3', '2', '1', '4'])
59-
6051
class postgresqlROTest(postgresqlOpener, ROTest):
6152
def setUp(self):
6253
postgresqlOpener.setUp(self)

0 commit comments

Comments
 (0)