summaryrefslogtreecommitdiff
path: root/django/db/models/functions/math.py
diff options
context:
space:
mode:
authorJunyi Jiao <jiaojunyi90@gmail.com>2018-07-05 11:02:12 -0400
committerTim Graham <timograham@gmail.com>2018-07-05 11:02:12 -0400
commita0b19a0f5b1731cf575546175034da53f5af5367 (patch)
tree912f6367180636e4c9e1aecc4045830046a70ea6 /django/db/models/functions/math.py
parent48aeca44d885929106e71fe78379fe50850af001 (diff)
downloaddjango-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.py174
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'