diff -r 000000000000 -r 0d40e90630ef web/lib/django/db/models/sql/aggregates.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/lib/django/db/models/sql/aggregates.py Wed Jan 20 00:34:04 2010 +0100 @@ -0,0 +1,130 @@ +""" +Classes to represent the default SQL aggregate functions +""" + +class AggregateField(object): + """An internal field mockup used to identify aggregates in the + data-conversion parts of the database backend. + """ + def __init__(self, internal_type): + self.internal_type = internal_type + def get_internal_type(self): + return self.internal_type + +ordinal_aggregate_field = AggregateField('IntegerField') +computed_aggregate_field = AggregateField('FloatField') + +class Aggregate(object): + """ + Default SQL Aggregate. + """ + is_ordinal = False + is_computed = False + sql_template = '%(function)s(%(field)s)' + + def __init__(self, col, source=None, is_summary=False, **extra): + """Instantiate an SQL aggregate + + * col is a column reference describing the subject field + of the aggregate. It can be an alias, or a tuple describing + a table and column name. + * source is the underlying field or aggregate definition for + the column reference. If the aggregate is not an ordinal or + computed type, this reference is used to determine the coerced + output type of the aggregate. + * extra is a dictionary of additional data to provide for the + aggregate definition + + Also utilizes the class variables: + * sql_function, the name of the SQL function that implements the + aggregate. + * sql_template, a template string that is used to render the + aggregate into SQL. + * is_ordinal, a boolean indicating if the output of this aggregate + is an integer (e.g., a count) + * is_computed, a boolean indicating if this output of this aggregate + is a computed float (e.g., an average), regardless of the input + type. + + """ + self.col = col + self.source = source + self.is_summary = is_summary + self.extra = extra + + # Follow the chain of aggregate sources back until you find an + # actual field, or an aggregate that forces a particular output + # type. This type of this field will be used to coerce values + # retrieved from the database. + tmp = self + + while tmp and isinstance(tmp, Aggregate): + if getattr(tmp, 'is_ordinal', False): + tmp = ordinal_aggregate_field + elif getattr(tmp, 'is_computed', False): + tmp = computed_aggregate_field + else: + tmp = tmp.source + + self.field = tmp + + def relabel_aliases(self, change_map): + if isinstance(self.col, (list, tuple)): + self.col = (change_map.get(self.col[0], self.col[0]), self.col[1]) + + def as_sql(self, quote_func=None): + "Return the aggregate, rendered as SQL." + if not quote_func: + quote_func = lambda x: x + + if hasattr(self.col, 'as_sql'): + field_name = self.col.as_sql(quote_func) + elif isinstance(self.col, (list, tuple)): + field_name = '.'.join([quote_func(c) for c in self.col]) + else: + field_name = self.col + + params = { + 'function': self.sql_function, + 'field': field_name + } + params.update(self.extra) + + return self.sql_template % params + + +class Avg(Aggregate): + is_computed = True + sql_function = 'AVG' + +class Count(Aggregate): + is_ordinal = True + sql_function = 'COUNT' + sql_template = '%(function)s(%(distinct)s%(field)s)' + + def __init__(self, col, distinct=False, **extra): + super(Count, self).__init__(col, distinct=distinct and 'DISTINCT ' or '', **extra) + +class Max(Aggregate): + sql_function = 'MAX' + +class Min(Aggregate): + sql_function = 'MIN' + +class StdDev(Aggregate): + is_computed = True + + def __init__(self, col, sample=False, **extra): + super(StdDev, self).__init__(col, **extra) + self.sql_function = sample and 'STDDEV_SAMP' or 'STDDEV_POP' + +class Sum(Aggregate): + sql_function = 'SUM' + +class Variance(Aggregate): + is_computed = True + + def __init__(self, col, sample=False, **extra): + super(Variance, self).__init__(col, **extra) + self.sql_function = sample and 'VAR_SAMP' or 'VAR_POP' +