Skip to content

Commit c93d014

Browse files
committed
Limit SmallerIntegerFields to 32-bit values
1 parent 37c9928 commit c93d014

File tree

5 files changed

+59
-21
lines changed

5 files changed

+59
-21
lines changed

django_mongodb_backend/operations.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -256,13 +256,15 @@ def explain_query_prefix(self, format=None, **options):
256256
return validated_options
257257

258258
def integer_field_range(self, internal_type):
259-
# MongODB doesn't enforce any integer constraints, but it supports
260-
# integers up to 64 bits.
261-
if internal_type in {
262-
"PositiveBigIntegerField",
263-
"PositiveIntegerField",
264-
"PositiveSmallIntegerField",
265-
}:
259+
# MongoDB doesn't enforce any integer constraints, but the
260+
# SmallIntegerFields use "int" for unique constraints which is limited
261+
# to 32 bits.
262+
if internal_type == "PositiveSmallIntegerField":
263+
return (0, 2147483647)
264+
if internal_type == "SmallIntegerField":
265+
return (-2147483648, 2147483647)
266+
# Other fields use "long" which supports up to 64 bits.
267+
if internal_type in {"PositiveBigIntegerField", "PositiveIntegerField"}:
266268
return (0, 9223372036854775807)
267269
return (-9223372036854775808, 9223372036854775807)
268270

docs/source/ref/models/fields.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ A few notes about some of the other fields:
2020
(rather than microsecond like most other databases), and correspondingly,
2121
:class:`~django.db.models.DurationField` stores milliseconds rather than
2222
microseconds.
23+
- :class:`~django.db.models.SmallIntegerField` and
24+
:class:`~django.db.models.PositiveSmallIntegerField` support 32 bit values
25+
(ranges ``(-2147483648, 2147483647)`` and ``(0, 2147483647)``, respectively),
26+
validated only by forms and model validation. Be careful because MongoDB
27+
doesn't prohibit inserting values outside of the supported range and unique
28+
constraints don't work for values outside of the 32-bit range of the BSON
29+
``int`` type.
2330

2431
MongoDB-specific model fields
2532
=============================

docs/source/releases/5.2.x.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ New features
2121
Backwards incompatible changes
2222
------------------------------
2323

24+
- :class:`django.db.models.SmallIntegerField` and
25+
:class:`django.db.models.PositiveSmallIntegerField` are now limited to 32 bit
26+
values in forms and model validation.
2427
- Removed support for database caching as the MongoDB security team considers the cache
2528
backend's ``pickle`` encoding of cached values a vulnerability. If an attacker
2629
compromises the database, they could run arbitrary commands on the application

tests/model_fields_/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from django_mongodb_backend.models import EmbeddedModel
1414

1515

16-
class Integers(models.Model):
16+
class UniqueIntegers(models.Model):
1717
small = models.SmallIntegerField(unique=True, blank=True, null=True)
1818
positive_small = models.PositiveSmallIntegerField(unique=True, blank=True, null=True)
1919

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
from django.core.exceptions import ValidationError
12
from django.db import IntegrityError
23
from django.test import TestCase
34

4-
from .models import Integers
5+
from .models import UniqueIntegers
56

67

78
class SmallIntegerFieldTests(TestCase):
@@ -13,37 +14,62 @@ def test_unique_max_value(self):
1314
SmallIntegerField.db_type() is "int" which means unique constraints
1415
are only enforced up to 32-bit values.
1516
"""
16-
Integers.objects.create(small=self.max_value + 1)
17-
Integers.objects.create(small=self.max_value + 1) # no IntegrityError
18-
Integers.objects.create(small=self.max_value)
17+
UniqueIntegers.objects.create(small=self.max_value + 1)
18+
UniqueIntegers.objects.create(small=self.max_value + 1) # no IntegrityError
19+
UniqueIntegers.objects.create(small=self.max_value)
1920
with self.assertRaises(IntegrityError):
20-
Integers.objects.create(small=self.max_value)
21+
UniqueIntegers.objects.create(small=self.max_value)
2122

2223
def test_unique_min_value(self):
2324
"""
2425
SmallIntegerField.db_type() is "int" which means unique constraints
2526
are only enforced down to negative 32-bit values.
2627
"""
27-
Integers.objects.create(small=self.min_value - 1)
28-
Integers.objects.create(small=self.min_value - 1) # no IntegrityError
29-
Integers.objects.create(small=self.min_value)
28+
UniqueIntegers.objects.create(small=self.min_value - 1)
29+
UniqueIntegers.objects.create(small=self.min_value - 1) # no IntegrityError
30+
UniqueIntegers.objects.create(small=self.min_value)
3031
with self.assertRaises(IntegrityError):
31-
Integers.objects.create(small=self.min_value)
32+
UniqueIntegers.objects.create(small=self.min_value)
33+
34+
def test_validate_max_value(self):
35+
UniqueIntegers(small=self.max_value).full_clean() # no error
36+
msg = "{'small': ['Ensure this value is less than or equal to 2147483647.']"
37+
with self.assertRaisesMessage(ValidationError, msg):
38+
UniqueIntegers(small=self.max_value + 1).full_clean()
39+
40+
def test_validate_min_value(self):
41+
UniqueIntegers(small=self.min_value).full_clean() # no error
42+
msg = "{'small': ['Ensure this value is greater than or equal to -2147483648.']"
43+
with self.assertRaisesMessage(ValidationError, msg):
44+
UniqueIntegers(small=self.min_value - 1).full_clean()
3245

3346

3447
class PositiveSmallIntegerFieldTests(TestCase):
3548
max_value = 2**31 - 1
49+
min_value = 0
3650

3751
def test_unique_max_value(self):
3852
"""
3953
SmallIntegerField.db_type() is "int" which means unique constraints
4054
are only enforced up to 32-bit values.
4155
"""
42-
Integers.objects.create(positive_small=self.max_value + 1)
43-
Integers.objects.create(positive_small=self.max_value + 1) # no IntegrityError
44-
Integers.objects.create(positive_small=self.max_value)
56+
UniqueIntegers.objects.create(positive_small=self.max_value + 1)
57+
UniqueIntegers.objects.create(positive_small=self.max_value + 1) # no IntegrityError
58+
UniqueIntegers.objects.create(positive_small=self.max_value)
4559
with self.assertRaises(IntegrityError):
46-
Integers.objects.create(positive_small=self.max_value)
60+
UniqueIntegers.objects.create(positive_small=self.max_value)
4761

4862
# test_unique_min_value isn't needed since PositiveSmallIntegerField has a
4963
# limit of zero (enforced only in forms and model validation).
64+
65+
def test_validate_max_value(self):
66+
UniqueIntegers(positive_small=self.max_value).full_clean() # no error
67+
msg = "{'positive_small': ['Ensure this value is less than or equal to 2147483647.']"
68+
with self.assertRaisesMessage(ValidationError, msg):
69+
UniqueIntegers(positive_small=self.max_value + 1).full_clean()
70+
71+
def test_validate_min_value(self):
72+
UniqueIntegers(positive_small=self.min_value).full_clean() # no error
73+
msg = "{'positive_small': ['Ensure this value is greater than or equal to 0.']"
74+
with self.assertRaisesMessage(ValidationError, msg):
75+
UniqueIntegers(positive_small=self.min_value - 1).full_clean()

0 commit comments

Comments
 (0)