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

Backport fixes from django-mssql-backend #9

Merged
merged 4 commits into from
Mar 19, 2021
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
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,12 @@ Dictionary. Current available keys are:
definition present in the ``freetds.conf`` FreeTDS configuration file
instead of a hostname or an IP address.

But if this option is present and it's value is ``True``, this
special behavior is turned off.
But if this option is present and its value is ``True``, this
special behavior is turned off. Instead, connections to the database
server will be established using ``HOST`` and ``PORT`` options, without
requiring ``freetds.conf`` to be configured.

See http://www.freetds.org/userguide/dsnless.htm for more information.
See https://www.freetds.org/userguide/dsnless.html for more information.

- unicode_results

Expand Down
3 changes: 2 additions & 1 deletion manage.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env python

# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

#!/usr/bin/env python
import os
import sys

Expand Down
11 changes: 9 additions & 2 deletions mssql/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
Table,
)
from django import VERSION as django_version
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 @@ -689,7 +690,7 @@ def add_field(self, model, field):
self.connection.close()

def _create_unique_sql(self, model, columns, name=None, condition=None, deferrable=None):
if (deferrable and not self.connection.features.supports_deferrable_unique_constraints):
if (deferrable and not getattr(self.connection.features, 'supports_deferrable_unique_constraints', False)):
return None

def create_unique_name(*args, **kwargs):
Expand Down Expand Up @@ -955,3 +956,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/0010_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', '0009_test_drop_table_with_foreign_key_reference_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
Expand Up @@ -4,6 +4,7 @@
import uuid

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


Expand Down Expand Up @@ -74,3 +75,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)
61 changes: 58 additions & 3 deletions testapp/tests/test_constraints.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

from django.db import connections, migrations, models
from django.db.migrations.state import ProjectState
from django.db.utils import IntegrityError
from django.test import TestCase, skipUnlessDBFeature
from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature

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


Expand Down Expand Up @@ -55,3 +61,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)