diff options
| author | Junyi Jiao <jiaojunyi90@gmail.com> | 2018-07-05 11:02:12 -0400 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2018-07-05 11:02:12 -0400 |
| commit | a0b19a0f5b1731cf575546175034da53f5af5367 (patch) | |
| tree | 912f6367180636e4c9e1aecc4045830046a70ea6 /django/db/models/functions/math.py | |
| parent | 48aeca44d885929106e71fe78379fe50850af001 (diff) | |
| download | django-a0b19a0f5b1731cf575546175034da53f5af5367.tar.gz | |
Refs #28643 -- Added math database functions.
Thanks Nick Pope for much review.
Diffstat (limited to 'django/db/models/functions/math.py')
| -rw-r--r-- | django/db/models/functions/math.py | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/django/db/models/functions/math.py b/django/db/models/functions/math.py new file mode 100644 index 0000000000..6341e101a6 --- /dev/null +++ b/django/db/models/functions/math.py @@ -0,0 +1,174 @@ +import math +import sys + +from django.db.models import ( + DecimalField, FloatField, Func, IntegerField, Transform, +) +from django.db.models.functions import Cast + + +class DecimalInputMixin: + + def as_postgresql(self, compiler, connection): + # Cast FloatField to DecimalField as PostgreSQL doesn't support the + # following function signatures: + # - LOG(double, double) + # - MOD(double, double) + output_field = DecimalField(decimal_places=sys.float_info.dig, max_digits=1000) + clone = self.copy() + clone.set_source_expressions([ + Cast(expression, output_field) if isinstance(expression.output_field, FloatField) + else expression for expression in self.get_source_expressions() + ]) + return clone.as_sql(compiler, connection) + + +class OutputFieldMixin: + + def _resolve_output_field(self): + has_decimals = any(isinstance(s.output_field, DecimalField) for s in self.get_source_expressions()) + return DecimalField() if has_decimals else FloatField() + + +class Abs(Transform): + function = 'ABS' + lookup_name = 'abs' + + +class ACos(OutputFieldMixin, Transform): + function = 'ACOS' + lookup_name = 'acos' + + +class ASin(OutputFieldMixin, Transform): + function = 'ASIN' + lookup_name = 'asin' + + +class ATan(OutputFieldMixin, Transform): + function = 'ATAN' + lookup_name = 'atan' + + +class ATan2(OutputFieldMixin, Func): + function = 'ATAN2' + arity = 2 + + def as_sqlite(self, compiler, connection): + if not getattr(connection.ops, 'spatialite', False) or connection.ops.spatial_version < (4, 3, 0): + return self.as_sql(compiler, connection) + # This function is usually ATan2(y, x), returning the inverse tangent + # of y / x, but it's ATan2(x, y) on SpatiaLite 4.3+. + # Cast integers to float to avoid inconsistent/buggy behavior if the + # arguments are mixed between integer and float or decimal. + # https://www.gaia-gis.it/fossil/libspatialite/tktview?name=0f72cca3a2 + clone = self.copy() + clone.set_source_expressions([ + Cast(expression, FloatField()) if isinstance(expression.output_field, IntegerField) + else expression for expression in self.get_source_expressions()[::-1] + ]) + return clone.as_sql(compiler, connection) + + +class Ceil(Transform): + function = 'CEILING' + lookup_name = 'ceil' + + def as_oracle(self, compiler, connection): + return super().as_sql(compiler, connection, function='CEIL') + + +class Cos(OutputFieldMixin, Transform): + function = 'COS' + lookup_name = 'cos' + + +class Cot(OutputFieldMixin, Transform): + function = 'COT' + lookup_name = 'cot' + + def as_oracle(self, compiler, connection): + return super().as_sql(compiler, connection, template='(1 / TAN(%(expressions)s))') + + +class Degrees(OutputFieldMixin, Transform): + function = 'DEGREES' + lookup_name = 'degrees' + + def as_oracle(self, compiler, connection): + return super().as_sql(compiler, connection, template='((%%(expressions)s) * 180 / %s)' % math.pi) + + +class Exp(OutputFieldMixin, Transform): + function = 'EXP' + lookup_name = 'exp' + + +class Floor(Transform): + function = 'FLOOR' + lookup_name = 'floor' + + +class Ln(OutputFieldMixin, Transform): + function = 'LN' + lookup_name = 'ln' + + +class Log(DecimalInputMixin, OutputFieldMixin, Func): + function = 'LOG' + arity = 2 + + def as_sqlite(self, compiler, connection): + if not getattr(connection.ops, 'spatialite', False): + return self.as_sql(compiler, connection) + # This function is usually Log(b, x) returning the logarithm of x to + # the base b, but on SpatiaLite it's Log(x, b). + clone = self.copy() + clone.set_source_expressions(self.get_source_expressions()[::-1]) + return clone.as_sql(compiler, connection) + + +class Mod(DecimalInputMixin, OutputFieldMixin, Func): + function = 'MOD' + arity = 2 + + +class Pi(OutputFieldMixin, Func): + function = 'PI' + arity = 0 + + def as_oracle(self, compiler, connection): + return super().as_sql(compiler, connection, template=str(math.pi)) + + +class Power(OutputFieldMixin, Func): + function = 'POWER' + arity = 2 + + +class Radians(OutputFieldMixin, Transform): + function = 'RADIANS' + lookup_name = 'radians' + + def as_oracle(self, compiler, connection): + return super().as_sql(compiler, connection, template='((%%(expressions)s) * %s / 180)' % math.pi) + + +class Round(Transform): + function = 'ROUND' + lookup_name = 'round' + + +class Sin(OutputFieldMixin, Transform): + function = 'SIN' + lookup_name = 'sin' + + +class Sqrt(OutputFieldMixin, Transform): + function = 'SQRT' + lookup_name = 'sqrt' + + +class Tan(OutputFieldMixin, Transform): + function = 'TAN' + lookup_name = 'tan' |
