Skip to content

Commit 9d7abdf

Browse files
committed
Merge branch 'dev'
2 parents a88c246 + 2b7f9b5 commit 9d7abdf

File tree

4 files changed

+51
-19
lines changed

4 files changed

+51
-19
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,20 @@ Dictionary. Current available keys are:
183183
Integer. Sets the timeout in seconds for the database query.
184184
Default value is ``0`` which disables the timeout.
185185

186+
- [setencoding](https://github.com/mkleehammer/pyodbc/wiki/Connection#setencoding) and [setdecoding](https://github.com/mkleehammer/pyodbc/wiki/Connection#setdecoding)
187+
188+
```python
189+
# Example
190+
"OPTIONS": {
191+
"setdecoding": [
192+
{"sqltype": pyodbc.SQL_CHAR, "encoding": 'utf-8'},
193+
{"sqltype": pyodbc.SQL_WCHAR, "encoding": 'utf-8'}],
194+
"setencoding": [
195+
{"encoding": "utf-8"}],
196+
...
197+
},
198+
```
199+
186200
### Backend-specific settings
187201

188202
The following project-level settings also control the behavior of the backend:

mssql/base.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,8 @@ def get_new_connection(self, conn_params):
318318
retries = options.get('connection_retries', 5)
319319
backoff_time = options.get('connection_retry_backoff_time', 5)
320320
query_timeout = options.get('query_timeout', 0)
321+
setencoding = options.get('setencoding', None)
322+
setdecoding = options.get('setdecoding', None)
321323

322324
conn = None
323325
retry_count = 0
@@ -341,6 +343,12 @@ def get_new_connection(self, conn_params):
341343
raise
342344

343345
conn.timeout = query_timeout
346+
if setencoding:
347+
for entry in setencoding:
348+
conn.setencoding(**entry)
349+
if setdecoding:
350+
for entry in setdecoding:
351+
conn.setdecoding(**entry)
344352
return conn
345353

346354
def init_connection_state(self):

mssql/schema.py

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
)
1919
from django import VERSION as django_version
2020
from django.db.models import Index, UniqueConstraint
21-
from django.db.models.fields import AutoField, BigAutoField, TextField
21+
from django.db.models.fields import AutoField, BigAutoField
2222
from django.db.models.sql.where import AND
2323
from django.db.transaction import TransactionManagementError
2424
from django.utils.encoding import force_str
@@ -27,6 +27,7 @@
2727
from django.db.models.sql import Query
2828
from django.db.backends.ddl_references import Expressions
2929

30+
3031
class Statement(DjStatement):
3132
def __hash__(self):
3233
return hash((self.template, str(self.parts['name'])))
@@ -42,6 +43,7 @@ def rename_column_references(self, table, old_column, new_column):
4243
if condition:
4344
self.parts['condition'] = condition.replace(f'[{old_column}]', f'[{new_column}]')
4445

46+
4547
class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
4648

4749
_sql_check_constraint = " CONSTRAINT %(name)s CHECK (%(check)s)"
@@ -389,7 +391,8 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
389391
columns_to_recreate_index = ', '.join(['%s' % self.quote_name(column[0]) for column in result])
390392
filter_definition = result[0][1]
391393
sql_restore_index += f'CREATE UNIQUE INDEX {index_name} ON {model._meta.db_table} ({columns_to_recreate_index}) WHERE {filter_definition};'
392-
self.execute(self._db_table_delete_constraint_sql(self.sql_delete_index, model._meta.db_table, index_name))
394+
self.execute(self._db_table_delete_constraint_sql(
395+
self.sql_delete_index, model._meta.db_table, index_name))
393396
self.execute(self._rename_field_sql(model._meta.db_table, old_field, new_field, new_type))
394397
# Restore indexes for altered table
395398
if(sql_restore_index):
@@ -440,7 +443,7 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
440443
self._delete_unique_constraints(model, old_field, new_field, strict)
441444
# Drop indexes, SQL Server requires explicit deletion
442445
self._delete_indexes(model, old_field, new_field)
443-
if not isinstance(new_field, TextField):
446+
if not new_field.get_internal_type() in ("JSONField", "TextField") and not (old_field.db_index and new_field.db_index):
444447
post_actions.append((self._create_index_sql(model, [new_field]), ()))
445448
# Only if we have a default and there is a change from NULL to NOT NULL
446449
four_way_default_alteration = (
@@ -562,7 +565,10 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
562565
index_columns.append(columns)
563566
if index_columns:
564567
for columns in index_columns:
565-
self.execute(self._create_index_sql(model, columns, suffix='_idx'))
568+
create_index_sql_statement = self._create_index_sql(model, columns)
569+
if create_index_sql_statement.__str__() not in [sql.__str__() for sql in self.deferred_sql]:
570+
self.execute(create_index_sql_statement)
571+
566572
# Type alteration on primary key? Then we need to alter the column
567573
# referring to us.
568574
rels_to_update = []
@@ -592,7 +598,8 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
592598
# Drop related_model indexes, so it can be altered
593599
index_names = self._db_table_constraint_names(old_rel.related_model._meta.db_table, index=True)
594600
for index_name in index_names:
595-
self.execute(self._db_table_delete_constraint_sql(self.sql_delete_index, old_rel.related_model._meta.db_table, index_name))
601+
self.execute(self._db_table_delete_constraint_sql(
602+
self.sql_delete_index, old_rel.related_model._meta.db_table, index_name))
596603
self.execute(
597604
self.sql_alter_column % {
598605
"table": self.quote_name(new_rel.related_model._meta.db_table),
@@ -646,10 +653,10 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
646653

647654
def _delete_indexes(self, model, old_field, new_field):
648655
index_columns = []
649-
if old_field.null != new_field.null:
650-
index_columns.append([old_field.column])
651656
if old_field.db_index and new_field.db_index:
652657
index_columns.append([old_field.column])
658+
elif old_field.null != new_field.null:
659+
index_columns.append([old_field.column])
653660
for fields in model._meta.index_together:
654661
columns = [model._meta.get_field(field).column for field in fields]
655662
if old_field.column in columns:
@@ -754,12 +761,13 @@ def add_field(self, model, field):
754761
self.connection.close()
755762

756763
if django_version >= (4, 0):
757-
def _create_unique_sql(self, model, fields , name=None, condition=None, deferrable=None, include=None, opclasses=None, expressions=None):
758-
if (deferrable and not getattr(self.connection.features, 'supports_deferrable_unique_constraints', False)
759-
or
764+
def _create_unique_sql(self, model, fields,
765+
name=None, condition=None, deferrable=None,
766+
include=None, opclasses=None, expressions=None):
767+
if (deferrable and not getattr(self.connection.features, 'supports_deferrable_unique_constraints', False) or
760768
(condition and not self.connection.features.supports_partial_indexes) or
761769
(include and not self.connection.features.supports_covering_indexes) or
762-
(expressions and not self.connection.features.supports_expression_indexes)):
770+
(expressions and not self.connection.features.supports_expression_indexes)):
763771
return None
764772

765773
def create_unique_name(*args, **kwargs):
@@ -803,12 +811,13 @@ def create_unique_name(*args, **kwargs):
803811
include=include,
804812
)
805813
else:
806-
def _create_unique_sql(self, model, columns , name=None, condition=None, deferrable=None, include=None, opclasses=None, expressions=None):
807-
if (deferrable and not getattr(self.connection.features, 'supports_deferrable_unique_constraints', False)
808-
or
814+
def _create_unique_sql(self, model, columns,
815+
name=None, condition=None, deferrable=None,
816+
include=None, opclasses=None, expressions=None):
817+
if (deferrable and not getattr(self.connection.features, 'supports_deferrable_unique_constraints', False) or
809818
(condition and not self.connection.features.supports_partial_indexes) or
810819
(include and not self.connection.features.supports_covering_indexes) or
811-
(expressions and not self.connection.features.supports_expression_indexes)):
820+
(expressions and not self.connection.features.supports_expression_indexes)):
812821
return None
813822

814823
def create_unique_name(*args, **kwargs):
@@ -823,7 +832,7 @@ def create_unique_name(*args, **kwargs):
823832
statement_args = {
824833
"deferrable": self._deferrable_constraint_sql(deferrable)
825834
} if django_version >= (3, 1) else {}
826-
include = self._index_include_sql(model, include) if django_version >=(3, 2) else ''
835+
include = self._index_include_sql(model, include) if django_version >= (3, 2) else ''
827836

828837
if condition:
829838
return Statement(
@@ -973,7 +982,8 @@ def _delete_unique_sql(
973982
if condition or include or opclasses:
974983
sql = self.sql_delete_index
975984
with self.connection.cursor() as cursor:
976-
cursor.execute("SELECT 1 FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE WHERE CONSTRAINT_NAME = '%s'" % name)
985+
cursor.execute(
986+
"SELECT 1 FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE WHERE CONSTRAINT_NAME = '%s'" % name)
977987
row = cursor.fetchone()
978988
if row:
979989
sql = self.sql_delete_unique
@@ -1113,6 +1123,6 @@ def _create_index_name(self, table_name, column_names, suffix=""):
11131123
index_name = super()._create_index_name(table_name, column_names, suffix)
11141124
# Check if the db_table specified a user-defined schema
11151125
if('].[' in index_name):
1116-
new_index_name = index_name.replace('[','').replace(']','').replace('.', '_')
1126+
new_index_name = index_name.replace('[', '').replace(']', '').replace('.', '_')
11171127
return new_index_name
11181128
return index_name

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
setup(
3030
name='mssql-django',
31-
version='1.1',
31+
version='1.1.1',
3232
description='Django backend for Microsoft SQL Server',
3333
long_description=long_description,
3434
long_description_content_type='text/markdown',

0 commit comments

Comments
 (0)