Skip to content

Commit d9edb00

Browse files
committed
add support for comparison database functions
1 parent caf8b73 commit d9edb00

File tree

4 files changed

+49
-3
lines changed

4 files changed

+49
-3
lines changed

.github/workflows/test-python.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ jobs:
7474
bulk_create
7575
dates
7676
datetimes
77+
db_functions.comparison
7778
db_functions.datetime
7879
db_functions.math
7980
empty

django_mongodb/base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,12 @@ class DatabaseWrapper(BaseDatabaseWrapper):
4444
"IntegerField": "int",
4545
"BigIntegerField": "long",
4646
"GenericIPAddressField": "string",
47-
"NullBooleanField": "bool",
4847
"OneToOneField": "int",
48+
"PositiveBigIntegerField": "int",
4949
"PositiveIntegerField": "long",
5050
"PositiveSmallIntegerField": "int",
5151
"SlugField": "string",
52+
"SmallAutoField": "int",
5253
"SmallIntegerField": "int",
5354
"TextField": "string",
5455
"TimeField": "date",

django_mongodb/features.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33

44
class DatabaseFeatures(BaseDatabaseFeatures):
5+
greatest_least_ignores_nulls = True
6+
has_json_object_function = False
57
supports_date_lookup_using_string = False
68
supports_foreign_keys = False
79
supports_ignore_conflicts = False
@@ -29,6 +31,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
2931
"lookup.tests.LookupTests.test_count",
3032
# Lookup in order_by() not supported:
3133
# unsupported operand type(s) for %: 'function' and 'str'
34+
"db_functions.comparison.test_coalesce.CoalesceTests.test_ordering",
3235
"lookup.tests.LookupQueryingTests.test_lookup_in_order_by",
3336
# annotate() after values() doesn't raise NotSupportedError.
3437
"lookup.tests.LookupTests.test_exact_query_rhs_with_selected_columns",
@@ -50,6 +53,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
5053
# the result back to UTC.
5154
"db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_trunc_func_with_timezone",
5255
"db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_trunc_timezone_applied_before_truncation",
56+
# Coalesce() with expressions doesn't generate correct query.
57+
"db_functions.comparison.test_coalesce.CoalesceTests.test_mixed_values",
5358
}
5459

5560
django_test_skips = {
@@ -73,6 +78,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
7378
"QuerySet.update() with expression not supported.": {
7479
"annotations.tests.AliasTests.test_update_with_alias",
7580
"annotations.tests.NonAggregateAnnotationTestCase.test_update_with_annotation",
81+
"db_functions.comparison.test_least.LeastTests.test_update",
82+
"db_functions.comparison.test_greatest.GreatestTests.test_update",
7683
"model_fields.test_integerfield.PositiveIntegerFieldTests.test_negative_values",
7784
"timezones.tests.NewDatabaseTests.test_update_with_timedelta",
7885
"update.tests.AdvancedTests.test_update_annotated_queryset",
@@ -123,6 +130,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
123130
"queries.test_bulk_update.BulkUpdateTests.test_database_routing_batch_atomicity",
124131
},
125132
"Test assumes integer primary key.": {
133+
"db_functions.comparison.test_cast.CastTests.test_cast_to_integer_foreign_key",
126134
"model_fields.test_foreignkey.ForeignKeyTests.test_to_python",
127135
},
128136
# https://github.com/mongodb-labs/django-mongodb/issues/12
@@ -160,6 +168,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
160168
"lookup.tests.LookupQueryingTests.test_filter_lookup_lhs",
161169
# Subquery not supported.
162170
"annotations.tests.NonAggregateAnnotationTestCase.test_empty_queryset_annotation",
171+
"db_functions.comparison.test_coalesce.CoalesceTests.test_empty_queryset",
163172
"db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_outerref",
164173
"db_functions.datetime.test_extract_trunc.DateFunctionTests.test_trunc_subquery_with_parameters",
165174
"lookup.tests.LookupQueryingTests.test_filter_subquery_lhs",
@@ -186,8 +195,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
186195
"annotations.tests.NonAggregateAnnotationTestCase.test_custom_functions_can_ref_other_functions",
187196
# Floor not implemented.
188197
"annotations.tests.NonAggregateAnnotationTestCase.test_custom_transform_annotation",
189-
# Coalesce not implemented.
190-
"annotations.tests.AliasTests.test_alias_annotation_expression",
198+
# annotate() with expression that raises FullResultSet crashes.
191199
"annotations.tests.NonAggregateAnnotationTestCase.test_full_expression_wrapped_annotation",
192200
# BaseDatabaseOperations may require a format_for_duration_arithmetic().
193201
"annotations.tests.NonAggregateAnnotationTestCase.test_mixed_type_annotation_date_interval",
@@ -206,6 +214,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
206214
"annotations.tests.AliasTests.test_order_by_alias_aggregate",
207215
"annotations.tests.NonAggregateAnnotationTestCase.test_annotate_exists",
208216
"annotations.tests.NonAggregateAnnotationTestCase.test_annotate_with_aggregation",
217+
"db_functions.comparison.test_cast.CastTests.test_cast_from_db_datetime_to_date_group_by",
209218
},
210219
"QuerySet.dates() is not supported on MongoDB.": {
211220
"annotations.tests.AliasTests.test_dates_alias",
@@ -246,6 +255,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
246255
"annotations.tests.NonAggregateAnnotationTestCase.test_annotation_subquery_outerref_transform",
247256
"annotations.tests.NonAggregateAnnotationTestCase.test_annotation_with_m2m",
248257
"annotations.tests.NonAggregateAnnotationTestCase.test_chaining_annotation_filter_with_m2m",
258+
"db_functions.comparison.test_least.LeastTests.test_related_field",
259+
"db_functions.comparison.test_greatest.GreatestTests.test_related_field",
249260
"defer.tests.BigChildDeferTests.test_defer_baseclass_when_subclass_has_added_field",
250261
"defer.tests.BigChildDeferTests.test_defer_subclass",
251262
"defer.tests.BigChildDeferTests.test_defer_subclass_both",
@@ -331,4 +342,14 @@ class DatabaseFeatures(BaseDatabaseFeatures):
331342
"MongoDB can't annotate ($project) a function like PI().": {
332343
"db_functions.math.test_pi.PiTests.test",
333344
},
345+
"Can't cast from date to datetime without MongoDB interpreting the new value in UTC.": {
346+
"db_functions.comparison.test_cast.CastTests.test_cast_from_db_date_to_datetime",
347+
"db_functions.comparison.test_cast.CastTests.test_cast_from_db_datetime_to_time",
348+
},
349+
"Casting Python literals doesn't work.": {
350+
"db_functions.comparison.test_cast.CastTests.test_cast_from_python",
351+
"db_functions.comparison.test_cast.CastTests.test_cast_from_python_to_date",
352+
"db_functions.comparison.test_cast.CastTests.test_cast_from_python_to_datetime",
353+
"db_functions.comparison.test_cast.CastTests.test_cast_to_duration",
354+
},
334355
}

django_mongodb/functions.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from django.db import NotSupportedError
22
from django.db.models.expressions import Func
3+
from django.db.models.functions.comparison import Cast, Coalesce, Greatest, Least, NullIf
34
from django.db.models.functions.datetime import (
45
Extract,
56
ExtractDay,
@@ -21,7 +22,10 @@
2122

2223
MONGO_OPERATORS = {
2324
Ceil: "ceil",
25+
Coalesce: "ifNull",
2426
Degrees: "radiansToDegrees",
27+
Greatest: "max",
28+
Least: "min",
2529
Power: "pow",
2630
Radians: "degreesToRadians",
2731
Random: "rand",
@@ -41,6 +45,17 @@
4145
}
4246

4347

48+
def cast(self, compiler, connection):
49+
output_type = connection.data_types[self.output_field.get_internal_type()]
50+
lhs_mql = process_lhs(self, compiler, connection)[0]
51+
if max_length := self.output_field.max_length:
52+
lhs_mql = {"$substrCP": [lhs_mql, 0, max_length]}
53+
lhs_mql = {"$convert": {"input": lhs_mql, "to": output_type}}
54+
if decimal_places := getattr(self.output_field, "decimal_places", None):
55+
lhs_mql = {"$trunc": [lhs_mql, decimal_places]}
56+
return lhs_mql
57+
58+
4459
def cot(self, compiler, connection):
4560
lhs_mql = process_lhs(self, compiler, connection)
4661
return {"$divide": [1, {"$tan": lhs_mql}]}
@@ -69,6 +84,12 @@ def log(self, compiler, connection):
6984
return func(clone, compiler, connection)
7085

7186

87+
def null_if(self, compiler, connection):
88+
"""Return None if expr1==expr2 else expr1."""
89+
expr1, expr2 = (expr.as_mql(compiler, connection) for expr in self.get_source_expressions())
90+
return {"$cond": {"if": {"$eq": [expr1, expr2]}, "then": None, "else": expr1}}
91+
92+
7293
def round_(self, compiler, connection):
7394
# Round needs its own function because it's a special case that inherits
7495
# from Transform but has two arguments.
@@ -84,9 +105,11 @@ def trunc(self, compiler, connection):
84105

85106

86107
def register_functions():
108+
Cast.as_mql = cast
87109
Cot.as_mql = cot
88110
Extract.as_mql = extract
89111
Func.as_mql = func
90112
Log.as_mql = log
113+
NullIf.as_mql = null_if
91114
Round.as_mql = round_
92115
TruncBase.as_mql = trunc

0 commit comments

Comments
 (0)