Skip to content

Commit

Permalink
Merge pull request #96 from microsoft/dev
Browse files Browse the repository at this point in the history
Prepare for 1.1.2 release
  • Loading branch information
absci authored Jan 31, 2022
2 parents 9d7abdf + 7ef1108 commit 39ee995
Show file tree
Hide file tree
Showing 18 changed files with 420 additions and 237 deletions.
6 changes: 4 additions & 2 deletions mssql/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ def bulk_update_with_default(self, objs, fields, batch_size=None, default=0):
if any(f.primary_key for f in fields):
raise ValueError('bulk_update() cannot be used with primary key fields.')
if not objs:
return
return 0
# PK is used twice in the resulting update query, once in the filter
# and once in the WHEN. Each field will also have one CAST.
max_batch_size = connections[self.db].ops.bulk_batch_size(['pk', 'pk'] + fields, objs)
Expand Down Expand Up @@ -266,9 +266,11 @@ def bulk_update_with_default(self, objs, fields, batch_size=None, default=0):
case_statement = Cast(case_statement, output_field=field)
update_kwargs[field.attname] = case_statement
updates.append(([obj.pk for obj in batch_objs], update_kwargs))
rows_updated = 0
with transaction.atomic(using=self.db, savepoint=False):
for pks, update_kwargs in updates:
self.filter(pk__in=pks).update(**update_kwargs)
rows_updated += self.filter(pk__in=pks).update(**update_kwargs)
return rows_updated

ATan2.as_microsoft = sqlserver_atan2
In.split_parameter_list_as_sql = split_parameter_list_as_sql
Expand Down
30 changes: 14 additions & 16 deletions mssql/introspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
SQL_BIGAUTOFIELD = -777444

def get_schema_name():
return getattr(settings, 'SCHEMA_TO_INSPECT', 'dbo')
return getattr(settings, 'SCHEMA_TO_INSPECT', 'SCHEMA_NAME()')

class DatabaseIntrospection(BaseDatabaseIntrospection):
# Map type codes to Django Field types.
Expand Down Expand Up @@ -66,7 +66,7 @@ def get_table_list(self, cursor):
"""
Returns a list of table and view names in the current database.
"""
sql = 'SELECT TABLE_NAME, TABLE_TYPE FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ' + "'" + get_schema_name() + "'"
sql = f'SELECT TABLE_NAME, TABLE_TYPE FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = {get_schema_name()}'
cursor.execute(sql)
types = {'BASE TABLE': 't', 'VIEW': 'v'}
return [TableInfo(row[0], types.get(row[1]))
Expand Down Expand Up @@ -129,10 +129,10 @@ def get_table_description(self, cursor, table_name, identity_check=True):
return items

def get_sequences(self, cursor, table_name, table_fields=()):
cursor.execute("""
cursor.execute(f"""
SELECT c.name FROM sys.columns c
INNER JOIN sys.tables t ON c.object_id = t.object_id
WHERE t.schema_id = SCHEMA_ID(""" + "'" + get_schema_name() + "'" + """) AND t.name = %s AND c.is_identity = 1""",
WHERE t.schema_id = SCHEMA_ID({get_schema_name()}) AND t.name = %s AND c.is_identity = 1""",
[table_name])
# SQL Server allows only one identity column per table
# https://docs.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql-identity-property
Expand All @@ -148,7 +148,7 @@ def get_relations(self, cursor, table_name):
# CONSTRAINT_TABLE_USAGE: http://msdn2.microsoft.com/en-us/library/ms179883.aspx
# REFERENTIAL_CONSTRAINTS: http://msdn2.microsoft.com/en-us/library/ms179987.aspx
# TABLE_CONSTRAINTS: http://msdn2.microsoft.com/en-us/library/ms181757.aspx
sql = """
sql = f"""
SELECT e.COLUMN_NAME AS column_name,
c.TABLE_NAME AS referenced_table_name,
d.COLUMN_NAME AS referenced_column_name
Expand All @@ -161,7 +161,7 @@ def get_relations(self, cursor, table_name):
ON c.CONSTRAINT_NAME = d.CONSTRAINT_NAME AND c.CONSTRAINT_SCHEMA = d.CONSTRAINT_SCHEMA
INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS e
ON a.CONSTRAINT_NAME = e.CONSTRAINT_NAME AND a.TABLE_SCHEMA = e.TABLE_SCHEMA
WHERE a.TABLE_SCHEMA = """ + "'" + get_schema_name() + "'" + """ AND a.TABLE_NAME = %s AND a.CONSTRAINT_TYPE = 'FOREIGN KEY'"""
WHERE a.TABLE_SCHEMA = {get_schema_name()} AND a.TABLE_NAME = %s AND a.CONSTRAINT_TYPE = 'FOREIGN KEY'"""
cursor.execute(sql, (table_name,))
return dict([[item[0], (item[2], item[1])] for item in cursor.fetchall()])

Expand All @@ -171,14 +171,14 @@ def get_key_columns(self, cursor, table_name):
key columns in given table.
"""
key_columns = []
cursor.execute("""
cursor.execute(f"""
SELECT c.name AS column_name, rt.name AS referenced_table_name, rc.name AS referenced_column_name
FROM sys.foreign_key_columns fk
INNER JOIN sys.tables t ON t.object_id = fk.parent_object_id
INNER JOIN sys.columns c ON c.object_id = t.object_id AND c.column_id = fk.parent_column_id
INNER JOIN sys.tables rt ON rt.object_id = fk.referenced_object_id
INNER JOIN sys.columns rc ON rc.object_id = rt.object_id AND rc.column_id = fk.referenced_column_id
WHERE t.schema_id = SCHEMA_ID(""" + "'" + get_schema_name() + "'" + """) AND t.name = %s""", [table_name])
WHERE t.schema_id = SCHEMA_ID({get_schema_name()}) AND t.name = %s""", [table_name])
key_columns.extend([tuple(row) for row in cursor.fetchall()])
return key_columns

Expand All @@ -201,7 +201,7 @@ def get_constraints(self, cursor, table_name):
constraints = {}
# Loop over the key table, collecting things as constraints
# This will get PKs, FKs, and uniques, but not CHECK
cursor.execute("""
cursor.execute(f"""
SELECT
kc.constraint_name,
kc.column_name,
Expand Down Expand Up @@ -241,9 +241,7 @@ def get_constraints(self, cursor, table_name):
kc.table_name = fk.table_name AND
kc.column_name = fk.column_name
WHERE
kc.table_schema = """
+ "'" + get_schema_name() + "'" +
""" AND
kc.table_schema = {get_schema_name()} AND
kc.table_name = %s
ORDER BY
kc.constraint_name ASC,
Expand All @@ -263,7 +261,7 @@ def get_constraints(self, cursor, table_name):
# Record the details
constraints[constraint]['columns'].append(column)
# Now get CHECK constraint columns
cursor.execute("""
cursor.execute(f"""
SELECT kc.constraint_name, kc.column_name
FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS kc
JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS c ON
Expand All @@ -272,7 +270,7 @@ def get_constraints(self, cursor, table_name):
kc.constraint_name = c.constraint_name
WHERE
c.constraint_type = 'CHECK' AND
kc.table_schema = """ + "'" + get_schema_name() + "'" + """ AND
kc.table_schema = {get_schema_name()} AND
kc.table_name = %s
""", [table_name])
for constraint, column in cursor.fetchall():
Expand All @@ -289,7 +287,7 @@ def get_constraints(self, cursor, table_name):
# Record the details
constraints[constraint]['columns'].append(column)
# Now get indexes
cursor.execute("""
cursor.execute(f"""
SELECT
i.name AS index_name,
i.is_unique,
Expand All @@ -311,7 +309,7 @@ def get_constraints(self, cursor, table_name):
ic.object_id = c.object_id AND
ic.column_id = c.column_id
WHERE
t.schema_id = SCHEMA_ID(""" + "'" + get_schema_name() + "'" + """) AND
t.schema_id = SCHEMA_ID({get_schema_name()}) AND
t.name = %s
ORDER BY
i.index_id ASC,
Expand Down
2 changes: 1 addition & 1 deletion mssql/management/commands/inspectdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ def add_arguments(self, parser):

def handle(self, *args, **options):
if options["schema"]:
settings.SCHEMA_TO_INSPECT = options["schema"]
settings.SCHEMA_TO_INSPECT = "'" + options["schema"] + "'"
return super().handle(*args, **options)
28 changes: 16 additions & 12 deletions mssql/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,14 +225,6 @@ def _model_indexes_sql(self, model):
output.append(index.create_sql(model, self))
return output

def _alter_many_to_many(self, model, old_field, new_field, strict):
"""Alter M2Ms to repoint their to= endpoints."""

for idx in self._constraint_names(old_field.remote_field.through, index=True, unique=True):
self.execute(self.sql_delete_index % {'name': idx, 'table': old_field.remote_field.through._meta.db_table})

return super()._alter_many_to_many(model, old_field, new_field, strict)

def _db_table_constraint_names(self, db_table, column_names=None, unique=None,
primary_key=None, index=None, foreign_key=None,
check=None, type_=None, exclude=None):
Expand Down Expand Up @@ -442,9 +434,17 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
# Drop unique constraint, SQL Server requires explicit deletion
self._delete_unique_constraints(model, old_field, new_field, strict)
# Drop indexes, SQL Server requires explicit deletion
self._delete_indexes(model, old_field, new_field)
if not new_field.get_internal_type() in ("JSONField", "TextField") and not (old_field.db_index and new_field.db_index):
post_actions.append((self._create_index_sql(model, [new_field]), ()))
indexes_dropped = self._delete_indexes(model, old_field, new_field)
if (
new_field.get_internal_type() not in ("JSONField", "TextField") and
(old_field.db_index or not new_field.db_index) and
new_field.db_index or
(indexes_dropped and sorted(indexes_dropped) == sorted(
[index.name for index in model._meta.indexes]))
):
create_index_sql_statement = self._create_index_sql(model, [new_field])
if create_index_sql_statement.__str__() not in [sql.__str__() for sql in self.deferred_sql]:
post_actions.append((create_index_sql_statement, ()))
# Only if we have a default and there is a change from NULL to NOT NULL
four_way_default_alteration = (
new_field.has_default() and
Expand Down Expand Up @@ -566,7 +566,9 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
if index_columns:
for columns in index_columns:
create_index_sql_statement = self._create_index_sql(model, columns)
if create_index_sql_statement.__str__() not in [sql.__str__() for sql in self.deferred_sql]:
if (create_index_sql_statement.__str__()
not in [sql.__str__() for sql in self.deferred_sql] + [statement[0].__str__() for statement in post_actions]
):
self.execute(create_index_sql_statement)

# Type alteration on primary key? Then we need to alter the column
Expand Down Expand Up @@ -653,6 +655,7 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,

def _delete_indexes(self, model, old_field, new_field):
index_columns = []
index_names = []
if old_field.db_index and new_field.db_index:
index_columns.append([old_field.column])
elif old_field.null != new_field.null:
Expand All @@ -671,6 +674,7 @@ def _delete_indexes(self, model, old_field, new_field):
index_names = self._constraint_names(model, columns, index=True)
for index_name in index_names:
self.execute(self._delete_constraint_sql(self.sql_delete_index, model, index_name))
return index_names

def _delete_unique_constraints(self, model, old_field, new_field, strict=False):
unique_columns = []
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

setup(
name='mssql-django',
version='1.1.1',
version='1.1.2',
description='Django backend for Microsoft SQL Server',
long_description=long_description,
long_description_content_type='text/markdown',
Expand Down
2 changes: 1 addition & 1 deletion testapp/migrations/0002_test_unique_nullable_part1.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class Migration(migrations.Migration):
]

operations = [
# Issue #38 test prep
# Prep test for issue https://github.com/ESSolutions/django-mssql-backend/issues/38
# Create with a field that is unique *and* nullable so it is implemented with a filtered unique index.
migrations.CreateModel(
name='TestUniqueNullableModel',
Expand Down
2 changes: 1 addition & 1 deletion testapp/migrations/0003_test_unique_nullable_part2.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class Migration(migrations.Migration):
]

operations = [
# Issue #38 test
# Run test for issue https://github.com/ESSolutions/django-mssql-backend/issues/38
# Now remove the null=True to check this transition is correctly handled.
migrations.AlterField(
model_name='testuniquenullablemodel',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class Migration(migrations.Migration):
('testapp', '0003_test_unique_nullable_part2'),
]

# Issue #45 test prep
# Prep test for issue https://github.com/ESSolutions/django-mssql-backend/issues/45
operations = [
# for case 1:
migrations.AddField(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
class Migration(migrations.Migration):

dependencies = [
('testapp', '0004_test_issue45_unique_type_change_part1'),
('testapp', '0004_test_unique_type_change_part1'),
]

# Issue #45 test
# Run test for issue https://github.com/ESSolutions/django-mssql-backend/issues/45
operations = [
# Case 1: changing max_length changes the column type - the filtered UNIQUE INDEX which implements
# the nullable unique constraint, should be correctly reinstated after this change of column type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
class Migration(migrations.Migration):

dependencies = [
('testapp', '0005_test_issue45_unique_type_change_part2'),
('testapp', '0005_test_unique_type_change_part2'),
]

operations = [
Expand Down
21 changes: 21 additions & 0 deletions testapp/migrations/0012_test_indexes_retained_part1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('testapp', '0011_test_unique_constraints'),
]

# Prep test for issue https://github.com/ESSolutions/django-mssql-backend/issues/58
operations = [
migrations.CreateModel(
name='TestIndexesRetained',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('a', models.IntegerField(db_index=True)),
('b', models.IntegerField(db_index=True)),
('c', models.IntegerField(db_index=True)),
],
),
]
27 changes: 27 additions & 0 deletions testapp/migrations/0013_test_indexes_retained_part2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('testapp', '0012_test_indexes_retained_part1'),
]

# Run test for issue https://github.com/ESSolutions/django-mssql-backend/issues/58
# where the following operations should leave indexes intact
operations = [
migrations.AlterField(
model_name='testindexesretained',
name='a',
field=models.IntegerField(db_index=True, null=True),
),
migrations.RenameField(
model_name='testindexesretained',
old_name='b',
new_name='b_renamed',
),
migrations.RenameModel(
old_name='TestIndexesRetained',
new_name='TestIndexesRetainedRenamed',
),
]
26 changes: 26 additions & 0 deletions testapp/migrations/0014_test_rename_m2mfield_part1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('testapp', '0013_test_indexes_retained_part2'),
]

operations = [
# Prep test for issue https://github.com/microsoft/mssql-django/issues/86
migrations.CreateModel(
name='M2MOtherModel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=10)),
],
),
migrations.CreateModel(
name='TestRenameManyToManyFieldModel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('others', models.ManyToManyField(to='testapp.M2MOtherModel')),
],
),
]
19 changes: 19 additions & 0 deletions testapp/migrations/0015_test_rename_m2mfield_part2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('testapp', '0014_test_rename_m2mfield_part1'),
]

operations = [
# Run test for issue https://github.com/microsoft/mssql-django/issues/86
# Must be in a separate migration so that the unique index was created
# (deferred after the previous migration) before we do the rename.
migrations.RenameField(
model_name='testrenamemanytomanyfieldmodel',
old_name='others',
new_name='others_renamed',
),
]
Loading

0 comments on commit 39ee995

Please sign in to comment.