-
Notifications
You must be signed in to change notification settings - Fork 22
Expand file tree
/
Copy pathorderable_agg.py
More file actions
133 lines (107 loc) · 4.32 KB
/
Copy pathorderable_agg.py
File metadata and controls
133 lines (107 loc) · 4.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# Taken from https://code.djangoproject.com/ticket/26067
# Note that this is fixed for Postgres, but not yet Mysql / mssql, and this is thus extendended in other projects
from django.db.models import TextField
from django.db.models.expressions import F, OrderBy
from django.db.models.aggregates import Aggregate
from django.contrib.postgres.fields import ArrayField
class OrderableAggMixin:
def __init__(self, expression, ordering=(), **extra):
if not isinstance(ordering, (list, tuple)):
ordering = [ordering]
ordering = ordering or []
# Transform minus sign prefixed strings into an OrderBy() expression.
ordering = (
(OrderBy(F(o[1:]), descending=True) if isinstance(o, str) and o[0] == '-' else o)
for o in ordering
)
super().__init__(expression, **extra)
self.ordering = self._parse_expressions(*ordering)
def resolve_expression(self, *args, **kwargs):
self.ordering = [expr.resolve_expression(*args, **kwargs) for expr in self.ordering]
return super().resolve_expression(*args, **kwargs)
def as_sql(self, compiler, connection):
if self.ordering:
self.extra['ordering'] = 'ORDER BY ' + ', '.join((
ordering_element.as_sql(compiler, connection)[0]
for ordering_element in self.ordering
))
else:
self.extra['ordering'] = ''
return super().as_sql(compiler, connection)
def get_source_expressions(self):
return self.source_expressions + self.ordering
def get_source_fields(self):
# Filter out fields contributed by the ordering expressions as
# these should not be used to determine which the return type of the
# expression.
return [
e._output_field_or_none
for e in self.get_source_expressions()[:self._get_ordering_expressions_index()]
]
def _get_ordering_expressions_index(self):
"""Return the index at which the ordering expressions start."""
source_expressions = self.get_source_expressions()
return len(source_expressions) - len(self.ordering)
class OrderableArrayAgg(OrderableAggMixin, Aggregate):
"""
Normal postgres way to do it: use array agg
"""
function = 'ARRAY_AGG'
template = '%(function)s(%(distinct)s%(expressions)s %(ordering)s)'
@property
def output_field(self):
return ArrayField(self.source_expressions[0].output_field)
def __init__(self, expression, distinct=False, **extra):
super().__init__(expression, distinct='DISTINCT ' if distinct else '', **extra)
def convert_value(self, value, expression, connection):
if not value:
return []
return value
class GroupConcat(OrderableAggMixin, Aggregate):
"""
MSSQL doesn't support array agg, but it does support group concat
"""
function = 'GROUP_CONCAT'
template = '%(function)s(%(distinct)s%(expressions)s %(ordering)s SEPARATOR \',\')'
@property
def output_field(self):
return TextField(self.source_expressions[0].output_field)
def __init__(self, expression, distinct=False, **extra):
if 'filter' in extra:
if extra['filter']: # not an empty Q()?
raise RuntimeError('Cannot filter within GroupConcat, MySQL does not support that!')
else:
del extra['filter']
super().__init__(expression, distinct='DISTINCT ' if distinct else '', **extra)
def convert_value(self, value, expression, connection):
if not value:
return []
return [int(v) for v in value.split(',')]
class StringAgg(OrderableAggMixin, Aggregate):
"""
MSSQL uses a third strategy: STring agg, which is like group_concat, but different. Also mssqls ordering strategy
is very different than the rest of the strategies
"""
function = 'STRING_AGG'
template = '%(function)s(%(distinct)s%(expressions)s, \',\') %(mssql_ordering)s'
@property
def output_field(self):
return TextField(self.source_expressions[0].output_field)
def __init__(self, expression, distinct=False, **extra):
super().__init__(expression, distinct='DISTINCT ' if distinct else '', **extra)
def as_sql(self, compiler, connection):
if self.ordering:
raw_order_by = 'ORDER BY ' + ', '.join((
ordering_element.as_sql(compiler, connection)[0]
for ordering_element in self.ordering
))
self.extra['mssql_ordering'] = f'WITHIN GROUP ( {raw_order_by} )'
else:
self.extra['mssql_ordering'] = ''
return super().as_sql(compiler, connection)
def get_source_expressions(self):
return self.source_expressions
def convert_value(self, value, expression, connection):
if not value:
return []
return [int(v) for v in value.split(',')]