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