Skip to content

Limit SmallerIntegerFields to 32 bit values #359

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions django_mongodb_backend/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,13 +256,15 @@ def explain_query_prefix(self, format=None, **options):
return validated_options

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

Expand Down
30 changes: 30 additions & 0 deletions docs/source/ref/models/fields.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,36 @@
=====================
Model field reference
=====================

Supported model fields
======================

All of Django's :doc:`model fields <django:ref/models/fields>` are
supported, except:

- :class:`~django.db.models.AutoField` (including
:class:`~django.db.models.BigAutoField` and
:class:`~django.db.models.SmallAutoField`)
- :class:`~django.db.models.CompositePrimaryKey`
- :class:`~django.db.models.GeneratedField`

A few notes about some of the other fields:

- :class:`~django.db.models.DateTimeField` is limited to millisecond precision
(rather than microsecond like most other databases), and correspondingly,
:class:`~django.db.models.DurationField` stores milliseconds rather than
microseconds.
- :class:`~django.db.models.SmallIntegerField` and
:class:`~django.db.models.PositiveSmallIntegerField` support 32 bit values
(ranges ``(-2147483648, 2147483647)`` and ``(0, 2147483647)``, respectively),
validated by forms and model validation. Be careful because MongoDB doesn't
prohibit inserting values outside of the supported range and unique
constraints don't work for values outside of the 32-bit range of the BSON
``int`` type.

MongoDB-specific model fields
=============================

.. module:: django_mongodb_backend.fields

Some MongoDB-specific fields are available in ``django_mongodb_backend.fields``.
Expand Down
3 changes: 3 additions & 0 deletions docs/source/releases/5.2.x.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ New features
Backwards incompatible changes
------------------------------

- :class:`django.db.models.SmallIntegerField` and
:class:`django.db.models.PositiveSmallIntegerField` are now limited to 32 bit
values in forms and model validation.
- Removed support for database caching as the MongoDB security team considers the cache
backend's ``pickle`` encoding of cached values a vulnerability. If an attacker
compromises the database, they could run arbitrary commands on the application
Expand Down
5 changes: 5 additions & 0 deletions tests/model_fields_/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
from django_mongodb_backend.models import EmbeddedModel


class UniqueIntegers(models.Model):
small = models.SmallIntegerField(unique=True, blank=True, null=True)
positive_small = models.PositiveSmallIntegerField(unique=True, blank=True, null=True)


# ObjectIdField
class ObjectIdModel(models.Model):
field = ObjectIdField()
Expand Down
75 changes: 75 additions & 0 deletions tests/model_fields_/test_integerfield.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from django.core.exceptions import ValidationError
from django.db import IntegrityError
from django.test import TestCase

from .models import UniqueIntegers


class SmallIntegerFieldTests(TestCase):
max_value = 2**31 - 1
min_value = -(2**31)

def test_unique_max_value(self):
"""
SmallIntegerField.db_type() is "int" which means unique constraints
are only enforced up to 32-bit values.
"""
UniqueIntegers.objects.create(small=self.max_value + 1)
UniqueIntegers.objects.create(small=self.max_value + 1) # no IntegrityError
UniqueIntegers.objects.create(small=self.max_value)
with self.assertRaises(IntegrityError):
UniqueIntegers.objects.create(small=self.max_value)

def test_unique_min_value(self):
"""
SmallIntegerField.db_type() is "int" which means unique constraints
are only enforced down to negative 32-bit values.
"""
UniqueIntegers.objects.create(small=self.min_value - 1)
UniqueIntegers.objects.create(small=self.min_value - 1) # no IntegrityError
UniqueIntegers.objects.create(small=self.min_value)
with self.assertRaises(IntegrityError):
UniqueIntegers.objects.create(small=self.min_value)

def test_validate_max_value(self):
UniqueIntegers(small=self.max_value).full_clean() # no error
msg = "{'small': ['Ensure this value is less than or equal to 2147483647.']"
with self.assertRaisesMessage(ValidationError, msg):
UniqueIntegers(small=self.max_value + 1).full_clean()

def test_validate_min_value(self):
UniqueIntegers(small=self.min_value).full_clean() # no error
msg = "{'small': ['Ensure this value is greater than or equal to -2147483648.']"
with self.assertRaisesMessage(ValidationError, msg):
UniqueIntegers(small=self.min_value - 1).full_clean()


class PositiveSmallIntegerFieldTests(TestCase):
max_value = 2**31 - 1
min_value = 0

def test_unique_max_value(self):
"""
SmallIntegerField.db_type() is "int" which means unique constraints
are only enforced up to 32-bit values.
"""
UniqueIntegers.objects.create(positive_small=self.max_value + 1)
UniqueIntegers.objects.create(positive_small=self.max_value + 1) # no IntegrityError
UniqueIntegers.objects.create(positive_small=self.max_value)
with self.assertRaises(IntegrityError):
UniqueIntegers.objects.create(positive_small=self.max_value)

# test_unique_min_value isn't needed since PositiveSmallIntegerField has a
# limit of zero (enforced only in forms and model validation).

def test_validate_max_value(self):
UniqueIntegers(positive_small=self.max_value).full_clean() # no error
msg = "{'positive_small': ['Ensure this value is less than or equal to 2147483647.']"
with self.assertRaisesMessage(ValidationError, msg):
UniqueIntegers(positive_small=self.max_value + 1).full_clean()

def test_validate_min_value(self):
UniqueIntegers(positive_small=self.min_value).full_clean() # no error
msg = "{'positive_small': ['Ensure this value is greater than or equal to 0.']"
with self.assertRaisesMessage(ValidationError, msg):
UniqueIntegers(positive_small=self.min_value - 1).full_clean()