1import math
2
3from django.db.models.expressions import Func, Value
4from django.db.models.fields import FloatField, IntegerField
5from django.db.models.functions import Cast
6from django.db.models.functions.mixins import (
7 FixDecimalInputMixin,
8 NumericOutputFieldMixin,
9)
10from django.db.models.lookups import Transform
11
12
13class Abs(Transform):
14 function = "ABS"
15 lookup_name = "abs"
16
17
18class ACos(NumericOutputFieldMixin, Transform):
19 function = "ACOS"
20 lookup_name = "acos"
21
22
23class ASin(NumericOutputFieldMixin, Transform):
24 function = "ASIN"
25 lookup_name = "asin"
26
27
28class ATan(NumericOutputFieldMixin, Transform):
29 function = "ATAN"
30 lookup_name = "atan"
31
32
33class ATan2(NumericOutputFieldMixin, Func):
34 function = "ATAN2"
35 arity = 2
36
37 def as_sqlite(self, compiler, connection, **extra_context):
38 if not getattr(
39 connection.ops, "spatialite", False
40 ) or connection.ops.spatial_version >= (5, 0, 0):
41 return self.as_sql(compiler, connection)
42 # This function is usually ATan2(y, x), returning the inverse tangent
43 # of y / x, but it's ATan2(x, y) on SpatiaLite < 5.0.0.
44 # Cast integers to float to avoid inconsistent/buggy behavior if the
45 # arguments are mixed between integer and float or decimal.
46 # https://www.gaia-gis.it/fossil/libspatialite/tktview?name=0f72cca3a2
47 clone = self.copy()
48 clone.set_source_expressions(
49 [
50 (
51 Cast(expression, FloatField())
52 if isinstance(expression.output_field, IntegerField)
53 else expression
54 )
55 for expression in self.get_source_expressions()[::-1]
56 ]
57 )
58 return clone.as_sql(compiler, connection, **extra_context)
59
60
61class Ceil(Transform):
62 function = "CEILING"
63 lookup_name = "ceil"
64
65 def as_oracle(self, compiler, connection, **extra_context):
66 return super().as_sql(compiler, connection, function="CEIL", **extra_context)
67
68
69class Cos(NumericOutputFieldMixin, Transform):
70 function = "COS"
71 lookup_name = "cos"
72
73
74class Cot(NumericOutputFieldMixin, Transform):
75 function = "COT"
76 lookup_name = "cot"
77
78 def as_oracle(self, compiler, connection, **extra_context):
79 return super().as_sql(
80 compiler, connection, template="(1 / TAN(%(expressions)s))", **extra_context
81 )
82
83
84class Degrees(NumericOutputFieldMixin, Transform):
85 function = "DEGREES"
86 lookup_name = "degrees"
87
88 def as_oracle(self, compiler, connection, **extra_context):
89 return super().as_sql(
90 compiler,
91 connection,
92 template="((%%(expressions)s) * 180 / %s)" % math.pi,
93 **extra_context,
94 )
95
96
97class Exp(NumericOutputFieldMixin, Transform):
98 function = "EXP"
99 lookup_name = "exp"
100
101
102class Floor(Transform):
103 function = "FLOOR"
104 lookup_name = "floor"
105
106
107class Ln(NumericOutputFieldMixin, Transform):
108 function = "LN"
109 lookup_name = "ln"
110
111
112class Log(FixDecimalInputMixin, NumericOutputFieldMixin, Func):
113 function = "LOG"
114 arity = 2
115
116 def as_sqlite(self, compiler, connection, **extra_context):
117 if not getattr(connection.ops, "spatialite", False):
118 return self.as_sql(compiler, connection)
119 # This function is usually Log(b, x) returning the logarithm of x to
120 # the base b, but on SpatiaLite it's Log(x, b).
121 clone = self.copy()
122 clone.set_source_expressions(self.get_source_expressions()[::-1])
123 return clone.as_sql(compiler, connection, **extra_context)
124
125
126class Mod(FixDecimalInputMixin, NumericOutputFieldMixin, Func):
127 function = "MOD"
128 arity = 2
129
130
131class Pi(NumericOutputFieldMixin, Func):
132 function = "PI"
133 arity = 0
134
135 def as_oracle(self, compiler, connection, **extra_context):
136 return super().as_sql(
137 compiler, connection, template=str(math.pi), **extra_context
138 )
139
140
141class Power(NumericOutputFieldMixin, Func):
142 function = "POWER"
143 arity = 2
144
145
146class Radians(NumericOutputFieldMixin, Transform):
147 function = "RADIANS"
148 lookup_name = "radians"
149
150 def as_oracle(self, compiler, connection, **extra_context):
151 return super().as_sql(
152 compiler,
153 connection,
154 template="((%%(expressions)s) * %s / 180)" % math.pi,
155 **extra_context,
156 )
157
158
159class Random(NumericOutputFieldMixin, Func):
160 function = "RANDOM"
161 arity = 0
162
163 def as_mysql(self, compiler, connection, **extra_context):
164 return super().as_sql(compiler, connection, function="RAND", **extra_context)
165
166 def as_oracle(self, compiler, connection, **extra_context):
167 return super().as_sql(
168 compiler, connection, function="DBMS_RANDOM.VALUE", **extra_context
169 )
170
171 def as_sqlite(self, compiler, connection, **extra_context):
172 return super().as_sql(compiler, connection, function="RAND", **extra_context)
173
174 def get_group_by_cols(self):
175 return []
176
177
178class Round(FixDecimalInputMixin, Transform):
179 function = "ROUND"
180 lookup_name = "round"
181 arity = None # Override Transform's arity=1 to enable passing precision.
182
183 def __init__(self, expression, precision=0, **extra):
184 super().__init__(expression, precision, **extra)
185
186 def as_sqlite(self, compiler, connection, **extra_context):
187 precision = self.get_source_expressions()[1]
188 if isinstance(precision, Value) and precision.value < 0:
189 raise ValueError("SQLite does not support negative precision.")
190 return super().as_sqlite(compiler, connection, **extra_context)
191
192 def _resolve_output_field(self):
193 source = self.get_source_expressions()[0]
194 return source.output_field
195
196
197class Sign(Transform):
198 function = "SIGN"
199 lookup_name = "sign"
200
201
202class Sin(NumericOutputFieldMixin, Transform):
203 function = "SIN"
204 lookup_name = "sin"
205
206
207class Sqrt(NumericOutputFieldMixin, Transform):
208 function = "SQRT"
209 lookup_name = "sqrt"
210
211
212class Tan(NumericOutputFieldMixin, Transform):
213 function = "TAN"
214 lookup_name = "tan"