Add NULLS ordering

issue5858
review27651002
This commit is contained in:
Nicolas Évrard
2017-03-29 23:12:01 +02:00
parent 67dd854cb3
commit c3f69d1db7
9 changed files with 61 additions and 24 deletions
+1
View File
@@ -1,3 +1,4 @@
* Add NULLS ordering
* Add context domain on ir.action.act_window
* Allow None limit in action window
* Add has_window_functions on Database
+5 -4
View File
@@ -378,7 +378,9 @@ Class attributes are:
where the first element of the tuple is a field name of the model and the
second is the sort ordering as `ASC` for ascending or `DESC` for
descending.
descending. This second element may contain 'NULLS FIRST' or 'NULLS LAST'
to sort null values before or after non-null values. If neither is
specified the default behavior of the backend is used.
In case the field used for the first element is a :class:`fields.Many2One`,
it is also possible to use the dotted notation to sort on a specific field
@@ -687,14 +689,13 @@ Class methods:
sequence_ordered
================
.. method:: sequence_ordered([field_name, [field_label, [order, [null_first]]]])
.. method:: sequence_ordered([field_name, [field_label, [order]]])
Retuns a mixin_ class which defines the order of a :class:`ModelSQL` with an
:class:`trytond.model.fields.Integer` field. field_name indicates the name of
the field to be created and its default values is `sequence`. field_label
defines the label which will be used by the field and defaults to `Sequence`.
Order specifies the order direction and defaults to `ASC` and null first
if the null values should be ordered first and defaults to `True`.
Order specifies the order direction and defaults to `ASC NULLS FIRST`.
.. _mixin: http://en.wikipedia.org/wiki/Mixin
+3 -1
View File
@@ -75,7 +75,9 @@ class Database(DatabaseInterface):
_list_cache = None
_list_cache_timestamp = None
flavor = Flavor(max_limit=18446744073709551610, function_mapping=MAPPING)
flavor = Flavor(
max_limit=18446744073709551610, function_mapping=MAPPING,
null_ordering=False)
def connect(self):
return self
+2 -1
View File
@@ -221,7 +221,8 @@ class Database(DatabaseInterface):
_local = threading.local()
_conn = None
flavor = Flavor(paramstyle='qmark', function_mapping=MAPPING)
flavor = Flavor(
paramstyle='qmark', function_mapping=MAPPING, null_ordering=False)
IN_MAX = 200
def __new__(cls, name=':memory:'):
+15 -3
View File
@@ -4,7 +4,8 @@ import datetime
from itertools import islice, izip, chain, ifilter
from collections import OrderedDict
from sql import Table, Column, Literal, Desc, Asc, Expression, Null
from sql import (Table, Column, Literal, Desc, Asc, Expression, Null,
NullsFirst, NullsLast)
from sql.functions import CurrentTimestamp, Extract
from sql.conditionals import Coalesce
from sql.operators import Or, And, Operator
@@ -1149,14 +1150,25 @@ class ModelSQL(ModelStorage):
'DESC': Desc,
'ASC': Asc,
}
null_ordering_types = {
'NULLS FIRST': NullsFirst,
'NULLS LAST': NullsLast,
None: lambda _: _
}
if order is None or order is False:
order = cls._order
for oexpr, otype in order:
fname, _, extra_expr = oexpr.partition('.')
field = cls._fields[fname]
Order = order_types[otype.upper()]
otype = otype.upper()
try:
otype, null_ordering = otype.split(' ', 1)
except ValueError:
null_ordering = None
Order = order_types[otype]
NullOrdering = null_ordering_types[null_ordering]
forder = field.convert_order(oexpr, tables, cls)
order_by.extend((Order(o) for o in forder))
order_by.extend((NullOrdering(Order(o)) for o in forder))
# construct a clause for the rules :
domain = Rule.domain_get(cls.__name__, mode='read')
+1 -15
View File
@@ -1,14 +1,10 @@
# This file is part of Tryton. The COPYRIGHT file at the toplevel of this
# repository contains the full copyright notices and license terms.
from sql import Null, Column
from sql.operators import Equal, NotEqual
from sql.conditionals import Case
from trytond.model import fields
def sequence_ordered(field_name='sequence', field_label='Sequence',
order='ASC', null_first=True):
order='ASC NULLS FIRST'):
"Returns a mixin to order the model by order fields"
class SequenceOrderedMixin(object):
@@ -20,14 +16,4 @@ def sequence_ordered(field_name='sequence', field_label='Sequence',
cls._order = [(field_name, order)] + cls._order
setattr(SequenceOrderedMixin, field_name, fields.Integer(field_label))
@classmethod
def order_function(cls, tables):
table, _ = tables[None]
operator = Equal
if not null_first:
operator = NotEqual
field = Column(table, field_name)
return [Case((operator(field, Null), 0), else_=1), field]
setattr(SequenceOrderedMixin, 'order_%s' % field_name, order_function)
return SequenceOrderedMixin
+1
View File
@@ -178,6 +178,7 @@ def register():
TestHistoryLine,
FieldContextChild,
FieldContextParent,
NullOrder,
module='tests', type_='model')
Pool.register(
TestWizard,
+7
View File
@@ -13,6 +13,7 @@ __all__ = [
'Union', 'UnionUnion',
'Model4UnionTree1', 'Model4UnionTree2', 'UnionTree',
'SequenceOrderedModel',
'NullOrder',
]
@@ -161,3 +162,9 @@ class UnionTree(UnionMixin, ModelSQL):
class SequenceOrderedModel(sequence_ordered(), ModelSQL):
'Sequence Ordered Model'
__name__ = 'test.order.sequence'
class NullOrder(ModelSQL):
"Null Order"
__name__ = 'test.null_order'
integer = fields.Integer('Integer')
+26
View File
@@ -133,6 +133,32 @@ class ModelSQLTestCase(unittest.TestCase):
TargetModel.name.string, TargetModel.__doc__)
self.assertEqual(err.message, msg)
@with_transaction()
def test_null_ordering(self):
'Test NULL ordering'
pool = Pool()
NullOrder = pool.get('test.null_order')
NullOrder.create([{
'integer': 1,
}, {
'integer': 3,
}, {
'integer': None,
}])
integers = NullOrder.search([], order=[('integer', 'ASC NULLS FIRST')])
self.assertListEqual([i.integer for i in integers], [None, 1, 3])
integers = NullOrder.search(
[], order=[('integer', 'DESC NULLS FIRST')])
self.assertListEqual([i.integer for i in integers], [None, 3, 1])
integers = NullOrder.search([], order=[('integer', 'ASC NULLS LAST')])
self.assertListEqual([i.integer for i in integers], [1, 3, None])
integers = NullOrder.search([], order=[('integer', 'DESC NULLS LAST')])
self.assertListEqual([i.integer for i in integers], [3, 1, None])
def suite():
return unittest.TestLoader().loadTestsFromTestCase(ModelSQLTestCase)