Skip to content

Commit 7982506

Browse files
authored
Fix better support for django 3.1.2 (#82)
- Support for JSONField - Updates parameter structure for sql_flush - reset_sequences - Add an mssql specific feature flag - supports_order_by_is_nulls
1 parent 05f3fe1 commit 7982506

File tree

4 files changed

+89
-51
lines changed

4 files changed

+89
-51
lines changed

sql_server/pyodbc/base.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,28 @@
1212
except ImportError as e:
1313
raise ImproperlyConfigured("Error loading pyodbc module: %s" % e)
1414

15-
from django.utils.version import get_version_tuple # noqa
15+
from django.utils.version import get_version_tuple # noqa
1616

1717
pyodbc_ver = get_version_tuple(Database.version)
1818
if pyodbc_ver < (3, 0):
1919
raise ImproperlyConfigured("pyodbc 3.0 or newer is required; you have %s" % Database.version)
2020

21-
from django.conf import settings # noqa
22-
from django.db import NotSupportedError # noqa
23-
from django.db.backends.base.base import BaseDatabaseWrapper # noqa
24-
from django.utils.encoding import smart_str # noqa
25-
from django.utils.functional import cached_property # noqa
21+
from django.conf import settings # noqa
22+
from django.db import NotSupportedError # noqa
23+
from django.db.backends.base.base import BaseDatabaseWrapper # noqa
24+
from django.utils.encoding import smart_str # noqa
25+
from django.utils.functional import cached_property # noqa
2626

2727
if hasattr(settings, 'DATABASE_CONNECTION_POOLING'):
2828
if not settings.DATABASE_CONNECTION_POOLING:
2929
Database.pooling = False
3030

31-
from .client import DatabaseClient # noqa
32-
from .creation import DatabaseCreation # noqa
33-
from .features import DatabaseFeatures # noqa
34-
from .introspection import DatabaseIntrospection # noqa
35-
from .operations import DatabaseOperations # noqa
36-
from .schema import DatabaseSchemaEditor # noqa
31+
from .client import DatabaseClient # noqa
32+
from .creation import DatabaseCreation # noqa
33+
from .features import DatabaseFeatures # noqa
34+
from .introspection import DatabaseIntrospection # noqa
35+
from .operations import DatabaseOperations # noqa
36+
from .schema import DatabaseSchemaEditor # noqa
3737

3838
EDITION_AZURE_SQL_DB = 5
3939

@@ -95,6 +95,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
9595
'TextField': 'nvarchar(max)',
9696
'TimeField': 'time',
9797
'UUIDField': 'char(32)',
98+
'JSONField': 'nvarchar(max)',
9899
}
99100
data_type_check_constraints = {
100101
'PositiveIntegerField': '[%(column)s] >= 0',

sql_server/pyodbc/features.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
3434
supports_timezones = False
3535
supports_transactions = True
3636
uses_savepoints = True
37+
supports_order_by_nulls_modifier = False
38+
supports_order_by_is_nulls = False
3739

3840
@cached_property
3941
def has_bulk_insert(self):

sql_server/pyodbc/functions.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,34 @@ class TryCast(Cast):
1212
function = 'TRY_CAST'
1313

1414

15+
def sqlserver_as_sql(self, compiler, connection, template=None, **extra_context):
16+
template = template or self.template
17+
if connection.features.supports_order_by_nulls_modifier:
18+
if self.nulls_last:
19+
template = '%s NULLS LAST' % template
20+
elif self.nulls_first:
21+
template = '%s NULLS FIRST' % template
22+
else:
23+
if self.nulls_last and not (
24+
self.descending and connection.features.order_by_nulls_first
25+
) and connection.features.supports_order_by_is_nulls:
26+
template = '%%(expression)s IS NULL, %s' % template
27+
elif self.nulls_first and not (
28+
not self.descending and connection.features.order_by_nulls_first
29+
) and connection.features.supports_order_by_is_nulls:
30+
template = '%%(expression)s IS NOT NULL, %s' % template
31+
connection.ops.check_expression_support(self)
32+
expression_sql, params = compiler.compile(self.expression)
33+
placeholders = {
34+
'expression': expression_sql,
35+
'ordering': 'DESC' if self.descending else 'ASC',
36+
**extra_context,
37+
}
38+
template = template or self.template
39+
params *= template.count('%(expression)s')
40+
return (template % placeholders).rstrip(), params
41+
42+
1543
def sqlserver_atan2(self, compiler, connection, **extra_context):
1644
return self.as_sql(compiler, connection, function='ATN2', **extra_context)
1745

@@ -85,3 +113,4 @@ def sqlserver_orderby(self, compiler, connection):
85113
Exists.as_microsoft = sqlserver_exists
86114

87115
OrderBy.as_microsoft = sqlserver_orderby
116+
OrderBy.as_sql = sqlserver_as_sql

sql_server/pyodbc/operations.py

Lines changed: 45 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ def savepoint_rollback_sql(self, sid):
314314
"""
315315
return "ROLLBACK TRANSACTION %s" % sid
316316

317-
def sql_flush(self, style, tables, sequences, allow_cascade=False):
317+
def sql_flush(self, style, tables, *, reset_sequences=False, allow_cascade=False):
318318
"""
319319
Returns a list of SQL statements required to remove all data from
320320
the given database tables (without actually removing the tables
@@ -329,14 +329,21 @@ def sql_flush(self, style, tables, sequences, allow_cascade=False):
329329
The `allow_cascade` argument determines whether truncation may cascade
330330
to tables with foreign keys pointing the tables being truncated.
331331
"""
332-
if tables:
333-
# Cannot use TRUNCATE on tables that are referenced by a FOREIGN KEY
334-
# So must use the much slower DELETE
335-
from django.db import connections
336-
cursor = connections[self.connection.alias].cursor()
337-
# Try to minimize the risks of the braindeaded inconsistency in
338-
# DBCC CHEKIDENT(table, RESEED, n) behavior.
339-
seqs = []
332+
if not tables:
333+
return []
334+
335+
# Cannot use TRUNCATE on tables that are referenced by a FOREIGN KEY
336+
# So must use the much slower DELETE
337+
from django.db import connections
338+
cursor = connections[self.connection.alias].cursor()
339+
# Try to minimize the risks of the braindeaded inconsistency in
340+
# DBCC CHEKIDENT(table, RESEED, n) behavior.
341+
seqs = []
342+
if reset_sequences:
343+
sequences = [
344+
sequence
345+
for sequence in self.connection.introspection.sequence_list()
346+
]
340347
for seq in sequences:
341348
cursor.execute("SELECT COUNT(*) FROM %s" % self.quote_name(seq["table"]))
342349
rowcnt = cursor.fetchone()[0]
@@ -347,37 +354,36 @@ def sql_flush(self, style, tables, sequences, allow_cascade=False):
347354
elem['start_id'] = 1
348355
elem.update(seq)
349356
seqs.append(elem)
350-
COLUMNS = "TABLE_NAME, CONSTRAINT_NAME"
351-
WHERE = "CONSTRAINT_TYPE not in ('PRIMARY KEY','UNIQUE')"
352-
cursor.execute(
353-
"SELECT {} FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE {}".format(COLUMNS, WHERE))
354-
fks = cursor.fetchall()
355-
sql_list = ['ALTER TABLE %s NOCHECK CONSTRAINT %s;' %
356-
(self.quote_name(fk[0]), self.quote_name(fk[1])) for fk in fks]
357-
sql_list.extend(['%s %s %s;' % (style.SQL_KEYWORD('DELETE'), style.SQL_KEYWORD('FROM'),
358-
style.SQL_FIELD(self.quote_name(table))) for table in tables])
359-
360-
if self.connection.to_azure_sql_db and self.connection.sql_server_version < 2014:
361-
warnings.warn("Resetting identity columns is not supported "
362-
"on this versios of Azure SQL Database.",
363-
RuntimeWarning)
364-
else:
365-
# Then reset the counters on each table.
366-
sql_list.extend(['%s %s (%s, %s, %s) %s %s;' % (
367-
style.SQL_KEYWORD('DBCC'),
368-
style.SQL_KEYWORD('CHECKIDENT'),
369-
style.SQL_FIELD(self.quote_name(seq["table"])),
370-
style.SQL_KEYWORD('RESEED'),
371-
style.SQL_FIELD('%d' % seq['start_id']),
372-
style.SQL_KEYWORD('WITH'),
373-
style.SQL_KEYWORD('NO_INFOMSGS'),
374-
) for seq in seqs])
375-
376-
sql_list.extend(['ALTER TABLE %s CHECK CONSTRAINT %s;' %
377-
(self.quote_name(fk[0]), self.quote_name(fk[1])) for fk in fks])
378-
return sql_list
357+
358+
COLUMNS = "TABLE_NAME, CONSTRAINT_NAME"
359+
WHERE = "CONSTRAINT_TYPE not in ('PRIMARY KEY','UNIQUE')"
360+
cursor.execute(
361+
"SELECT {} FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE {}".format(COLUMNS, WHERE))
362+
fks = cursor.fetchall()
363+
sql_list = ['ALTER TABLE %s NOCHECK CONSTRAINT %s;' %
364+
(self.quote_name(fk[0]), self.quote_name(fk[1])) for fk in fks]
365+
sql_list.extend(['%s %s %s;' % (style.SQL_KEYWORD('DELETE'), style.SQL_KEYWORD('FROM'),
366+
style.SQL_FIELD(self.quote_name(table))) for table in tables])
367+
368+
if self.connection.to_azure_sql_db and self.connection.sql_server_version < 2014:
369+
warnings.warn("Resetting identity columns is not supported "
370+
"on this versios of Azure SQL Database.",
371+
RuntimeWarning)
379372
else:
380-
return []
373+
# Then reset the counters on each table.
374+
sql_list.extend(['%s %s (%s, %s, %s) %s %s;' % (
375+
style.SQL_KEYWORD('DBCC'),
376+
style.SQL_KEYWORD('CHECKIDENT'),
377+
style.SQL_FIELD(self.quote_name(seq["table"])),
378+
style.SQL_KEYWORD('RESEED'),
379+
style.SQL_FIELD('%d' % seq['start_id']),
380+
style.SQL_KEYWORD('WITH'),
381+
style.SQL_KEYWORD('NO_INFOMSGS'),
382+
) for seq in seqs])
383+
384+
sql_list.extend(['ALTER TABLE %s CHECK CONSTRAINT %s;' %
385+
(self.quote_name(fk[0]), self.quote_name(fk[1])) for fk in fks])
386+
return sql_list
381387

382388
def start_transaction_sql(self):
383389
"""

0 commit comments

Comments
 (0)