|
1 """ |
|
2 Classes to represent the default SQL aggregate functions |
|
3 """ |
|
4 |
|
5 class AggregateField(object): |
|
6 """An internal field mockup used to identify aggregates in the |
|
7 data-conversion parts of the database backend. |
|
8 """ |
|
9 def __init__(self, internal_type): |
|
10 self.internal_type = internal_type |
|
11 def get_internal_type(self): |
|
12 return self.internal_type |
|
13 |
|
14 ordinal_aggregate_field = AggregateField('IntegerField') |
|
15 computed_aggregate_field = AggregateField('FloatField') |
|
16 |
|
17 class Aggregate(object): |
|
18 """ |
|
19 Default SQL Aggregate. |
|
20 """ |
|
21 is_ordinal = False |
|
22 is_computed = False |
|
23 sql_template = '%(function)s(%(field)s)' |
|
24 |
|
25 def __init__(self, col, source=None, is_summary=False, **extra): |
|
26 """Instantiate an SQL aggregate |
|
27 |
|
28 * col is a column reference describing the subject field |
|
29 of the aggregate. It can be an alias, or a tuple describing |
|
30 a table and column name. |
|
31 * source is the underlying field or aggregate definition for |
|
32 the column reference. If the aggregate is not an ordinal or |
|
33 computed type, this reference is used to determine the coerced |
|
34 output type of the aggregate. |
|
35 * extra is a dictionary of additional data to provide for the |
|
36 aggregate definition |
|
37 |
|
38 Also utilizes the class variables: |
|
39 * sql_function, the name of the SQL function that implements the |
|
40 aggregate. |
|
41 * sql_template, a template string that is used to render the |
|
42 aggregate into SQL. |
|
43 * is_ordinal, a boolean indicating if the output of this aggregate |
|
44 is an integer (e.g., a count) |
|
45 * is_computed, a boolean indicating if this output of this aggregate |
|
46 is a computed float (e.g., an average), regardless of the input |
|
47 type. |
|
48 |
|
49 """ |
|
50 self.col = col |
|
51 self.source = source |
|
52 self.is_summary = is_summary |
|
53 self.extra = extra |
|
54 |
|
55 # Follow the chain of aggregate sources back until you find an |
|
56 # actual field, or an aggregate that forces a particular output |
|
57 # type. This type of this field will be used to coerce values |
|
58 # retrieved from the database. |
|
59 tmp = self |
|
60 |
|
61 while tmp and isinstance(tmp, Aggregate): |
|
62 if getattr(tmp, 'is_ordinal', False): |
|
63 tmp = ordinal_aggregate_field |
|
64 elif getattr(tmp, 'is_computed', False): |
|
65 tmp = computed_aggregate_field |
|
66 else: |
|
67 tmp = tmp.source |
|
68 |
|
69 self.field = tmp |
|
70 |
|
71 def relabel_aliases(self, change_map): |
|
72 if isinstance(self.col, (list, tuple)): |
|
73 self.col = (change_map.get(self.col[0], self.col[0]), self.col[1]) |
|
74 |
|
75 def as_sql(self, quote_func=None): |
|
76 "Return the aggregate, rendered as SQL." |
|
77 if not quote_func: |
|
78 quote_func = lambda x: x |
|
79 |
|
80 if hasattr(self.col, 'as_sql'): |
|
81 field_name = self.col.as_sql(quote_func) |
|
82 elif isinstance(self.col, (list, tuple)): |
|
83 field_name = '.'.join([quote_func(c) for c in self.col]) |
|
84 else: |
|
85 field_name = self.col |
|
86 |
|
87 params = { |
|
88 'function': self.sql_function, |
|
89 'field': field_name |
|
90 } |
|
91 params.update(self.extra) |
|
92 |
|
93 return self.sql_template % params |
|
94 |
|
95 |
|
96 class Avg(Aggregate): |
|
97 is_computed = True |
|
98 sql_function = 'AVG' |
|
99 |
|
100 class Count(Aggregate): |
|
101 is_ordinal = True |
|
102 sql_function = 'COUNT' |
|
103 sql_template = '%(function)s(%(distinct)s%(field)s)' |
|
104 |
|
105 def __init__(self, col, distinct=False, **extra): |
|
106 super(Count, self).__init__(col, distinct=distinct and 'DISTINCT ' or '', **extra) |
|
107 |
|
108 class Max(Aggregate): |
|
109 sql_function = 'MAX' |
|
110 |
|
111 class Min(Aggregate): |
|
112 sql_function = 'MIN' |
|
113 |
|
114 class StdDev(Aggregate): |
|
115 is_computed = True |
|
116 |
|
117 def __init__(self, col, sample=False, **extra): |
|
118 super(StdDev, self).__init__(col, **extra) |
|
119 self.sql_function = sample and 'STDDEV_SAMP' or 'STDDEV_POP' |
|
120 |
|
121 class Sum(Aggregate): |
|
122 sql_function = 'SUM' |
|
123 |
|
124 class Variance(Aggregate): |
|
125 is_computed = True |
|
126 |
|
127 def __init__(self, col, sample=False, **extra): |
|
128 super(Variance, self).__init__(col, **extra) |
|
129 self.sql_function = sample and 'VAR_SAMP' or 'VAR_POP' |
|
130 |