Skip to content
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

Raise error on trying to create unique constraints with unsupported conditions #97

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
9 changes: 8 additions & 1 deletion sql_server/pyodbc/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
Statement as DjStatement,
Table,
)
from django.db.models import Index
from django.db.models import Index, UniqueConstraint
from django.db.models.fields import AutoField, BigAutoField
from django.db.models.sql.where import AND
from django.db.transaction import TransactionManagementError
from django.utils.encoding import force_str

Expand Down Expand Up @@ -970,3 +971,9 @@ def remove_field(self, model, field):
for sql in list(self.deferred_sql):
if isinstance(sql, Statement) and sql.references_column(model._meta.db_table, field.column):
self.deferred_sql.remove(sql)

def add_constraint(self, model, constraint):
if isinstance(constraint, UniqueConstraint) and constraint.condition and constraint.condition.connector != AND:
raise NotImplementedError("The backend does not support %s conditions on unique constraint %s." %
(constraint.condition.connector, constraint.name))
super().add_constraint(model, constraint)
68 changes: 68 additions & 0 deletions testapp/migrations/0008_test_unique_constraints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Generated by Django 3.1.5 on 2021-01-18 00:05

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('testapp', '0007_test_remove_onetoone_field_part2'),
]

operations = [
migrations.CreateModel(
name='TestUnsupportableUniqueConstraint',
fields=[
(
'id',
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name='ID',
),
),
('_type', models.CharField(max_length=50)),
('status', models.CharField(max_length=50)),
],
options={
'managed': False,
},
),
migrations.CreateModel(
name='TestSupportableUniqueConstraint',
fields=[
(
'id',
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name='ID',
),
),
('_type', models.CharField(max_length=50)),
('status', models.CharField(max_length=50)),
],
),
migrations.AddConstraint(
model_name='testsupportableuniqueconstraint',
constraint=models.UniqueConstraint(
condition=models.Q(
('status', 'in_progress'),
('status', 'needs_changes'),
('status', 'published'),
),
fields=('_type',),
name='and_constraint',
),
),
migrations.AddConstraint(
model_name='testsupportableuniqueconstraint',
constraint=models.UniqueConstraint(
condition=models.Q(status__in=['in_progress', 'needs_changes']),
fields=('_type',),
name='in_constraint',
),
),
]
37 changes: 37 additions & 0 deletions testapp/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import uuid

from django.db import models
from django.db.models import Q
from django.utils import timezone


Expand Down Expand Up @@ -71,3 +72,39 @@ class TestRemoveOneToOneFieldModel(models.Model):
# thats already is removed.
# b = models.OneToOneField('self', on_delete=models.SET_NULL, null=True)
a = models.CharField(max_length=50)


class TestUnsupportableUniqueConstraint(models.Model):
class Meta:
managed = False
constraints = [
models.UniqueConstraint(
name='or_constraint',
fields=['_type'],
condition=(Q(status='in_progress') | Q(status='needs_changes')),
),
]

_type = models.CharField(max_length=50)
status = models.CharField(max_length=50)


class TestSupportableUniqueConstraint(models.Model):
class Meta:
constraints = [
models.UniqueConstraint(
name='and_constraint',
fields=['_type'],
condition=(
Q(status='in_progress') & Q(status='needs_changes') & Q(status='published')
),
),
models.UniqueConstraint(
name='in_constraint',
fields=['_type'],
condition=(Q(status__in=['in_progress', 'needs_changes'])),
),
]

_type = models.CharField(max_length=50)
status = models.CharField(max_length=50)
62 changes: 58 additions & 4 deletions testapp/tests/test_constraints.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
from django.db.utils import IntegrityError
from django.test import TestCase, skipUnlessDBFeature
from django.db import IntegrityError, connections, migrations, models
from django.db.migrations.state import ProjectState
from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature

from sql_server.pyodbc.base import DatabaseWrapper
from ..models import (
Author, Editor, Post,
TestUniqueNullableModel, TestNullableUniqueTogetherModel,
Author,
Editor,
Post,
TestUniqueNullableModel,
TestNullableUniqueTogetherModel,
)


Expand Down Expand Up @@ -52,3 +57,52 @@ def test_after_type_change(self):
TestNullableUniqueTogetherModel.objects.create(a='aaa', b='bbb', c='ccc')
with self.assertRaises(IntegrityError):
TestNullableUniqueTogetherModel.objects.create(a='aaa', b='bbb', c='ccc')


class TestUniqueConstraints(TransactionTestCase):
def test_unsupportable_unique_constraint(self):
# Only execute tests when running against SQL Server
connection = connections['default']
if isinstance(connection, DatabaseWrapper):

class TestMigration(migrations.Migration):
initial = True

operations = [
migrations.CreateModel(
name='TestUnsupportableUniqueConstraint',
fields=[
(
'id',
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name='ID',
),
),
('_type', models.CharField(max_length=50)),
('status', models.CharField(max_length=50)),
],
),
migrations.AddConstraint(
model_name='testunsupportableuniqueconstraint',
constraint=models.UniqueConstraint(
condition=models.Q(
('status', 'in_progress'),
('status', 'needs_changes'),
_connector='OR',
),
fields=('_type',),
name='or_constraint',
),
),
]

migration = TestMigration('testapp', 'test_unsupportable_unique_constraint')

with connection.schema_editor(atomic=True) as editor:
with self.assertRaisesRegex(
NotImplementedError, "does not support OR conditions"
):
return migration.apply(ProjectState(), editor)