Skip to content

Fix better support for django 3.1.2 #82

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

Merged
merged 7 commits into from
Oct 22, 2020
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
25 changes: 13 additions & 12 deletions sql_server/pyodbc/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,28 @@
except ImportError as e:
raise ImproperlyConfigured("Error loading pyodbc module: %s" % e)

from django.utils.version import get_version_tuple # noqa
from django.utils.version import get_version_tuple # noqa

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

from django.conf import settings # noqa
from django.db import NotSupportedError # noqa
from django.db.backends.base.base import BaseDatabaseWrapper # noqa
from django.utils.encoding import smart_str # noqa
from django.utils.functional import cached_property # noqa
from django.conf import settings # noqa
from django.db import NotSupportedError # noqa
from django.db.backends.base.base import BaseDatabaseWrapper # noqa
from django.utils.encoding import smart_str # noqa
from django.utils.functional import cached_property # noqa

if hasattr(settings, 'DATABASE_CONNECTION_POOLING'):
if not settings.DATABASE_CONNECTION_POOLING:
Database.pooling = False

from .client import DatabaseClient # noqa
from .creation import DatabaseCreation # noqa
from .features import DatabaseFeatures # noqa
from .introspection import DatabaseIntrospection # noqa
from .operations import DatabaseOperations # noqa
from .schema import DatabaseSchemaEditor # noqa
from .client import DatabaseClient # noqa
from .creation import DatabaseCreation # noqa
from .features import DatabaseFeatures # noqa
from .introspection import DatabaseIntrospection # noqa
from .operations import DatabaseOperations # noqa
from .schema import DatabaseSchemaEditor # noqa

EDITION_AZURE_SQL_DB = 5

Expand Down Expand Up @@ -95,6 +95,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
'TextField': 'nvarchar(max)',
'TimeField': 'time',
'UUIDField': 'char(32)',
'JSONField': 'nvarchar(max)',
}
data_type_check_constraints = {
'PositiveIntegerField': '[%(column)s] >= 0',
Expand Down
2 changes: 2 additions & 0 deletions sql_server/pyodbc/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_timezones = False
supports_transactions = True
uses_savepoints = True
supports_order_by_nulls_modifier = False
supports_order_by_is_nulls = False

@cached_property
def has_bulk_insert(self):
Expand Down
29 changes: 29 additions & 0 deletions sql_server/pyodbc/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,34 @@ class TryCast(Cast):
function = 'TRY_CAST'


def sqlserver_as_sql(self, compiler, connection, template=None, **extra_context):
template = template or self.template
if connection.features.supports_order_by_nulls_modifier:
if self.nulls_last:
template = '%s NULLS LAST' % template
elif self.nulls_first:
template = '%s NULLS FIRST' % template
else:
if self.nulls_last and not (
self.descending and connection.features.order_by_nulls_first
) and connection.features.supports_order_by_is_nulls:
template = '%%(expression)s IS NULL, %s' % template
elif self.nulls_first and not (
not self.descending and connection.features.order_by_nulls_first
) and connection.features.supports_order_by_is_nulls:
template = '%%(expression)s IS NOT NULL, %s' % template
connection.ops.check_expression_support(self)
expression_sql, params = compiler.compile(self.expression)
placeholders = {
'expression': expression_sql,
'ordering': 'DESC' if self.descending else 'ASC',
**extra_context,
}
template = template or self.template
params *= template.count('%(expression)s')
return (template % placeholders).rstrip(), params


def sqlserver_atan2(self, compiler, connection, **extra_context):
return self.as_sql(compiler, connection, function='ATN2', **extra_context)

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

OrderBy.as_microsoft = sqlserver_orderby
OrderBy.as_sql = sqlserver_as_sql
84 changes: 45 additions & 39 deletions sql_server/pyodbc/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ def savepoint_rollback_sql(self, sid):
"""
return "ROLLBACK TRANSACTION %s" % sid

def sql_flush(self, style, tables, sequences, allow_cascade=False):
def sql_flush(self, style, tables, *, reset_sequences=False, allow_cascade=False):
"""
Returns a list of SQL statements required to remove all data from
the given database tables (without actually removing the tables
Expand All @@ -329,14 +329,21 @@ def sql_flush(self, style, tables, sequences, allow_cascade=False):
The `allow_cascade` argument determines whether truncation may cascade
to tables with foreign keys pointing the tables being truncated.
"""
if tables:
# Cannot use TRUNCATE on tables that are referenced by a FOREIGN KEY
# So must use the much slower DELETE
from django.db import connections
cursor = connections[self.connection.alias].cursor()
# Try to minimize the risks of the braindeaded inconsistency in
# DBCC CHEKIDENT(table, RESEED, n) behavior.
seqs = []
if not tables:
return []

# Cannot use TRUNCATE on tables that are referenced by a FOREIGN KEY
# So must use the much slower DELETE
from django.db import connections
cursor = connections[self.connection.alias].cursor()
# Try to minimize the risks of the braindeaded inconsistency in
# DBCC CHEKIDENT(table, RESEED, n) behavior.
seqs = []
if reset_sequences:
sequences = [
sequence
for sequence in self.connection.introspection.sequence_list()
]
for seq in sequences:
cursor.execute("SELECT COUNT(*) FROM %s" % self.quote_name(seq["table"]))
rowcnt = cursor.fetchone()[0]
Expand All @@ -347,37 +354,36 @@ def sql_flush(self, style, tables, sequences, allow_cascade=False):
elem['start_id'] = 1
elem.update(seq)
seqs.append(elem)
COLUMNS = "TABLE_NAME, CONSTRAINT_NAME"
WHERE = "CONSTRAINT_TYPE not in ('PRIMARY KEY','UNIQUE')"
cursor.execute(
"SELECT {} FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE {}".format(COLUMNS, WHERE))
fks = cursor.fetchall()
sql_list = ['ALTER TABLE %s NOCHECK CONSTRAINT %s;' %
(self.quote_name(fk[0]), self.quote_name(fk[1])) for fk in fks]
sql_list.extend(['%s %s %s;' % (style.SQL_KEYWORD('DELETE'), style.SQL_KEYWORD('FROM'),
style.SQL_FIELD(self.quote_name(table))) for table in tables])

if self.connection.to_azure_sql_db and self.connection.sql_server_version < 2014:
warnings.warn("Resetting identity columns is not supported "
"on this versios of Azure SQL Database.",
RuntimeWarning)
else:
# Then reset the counters on each table.
sql_list.extend(['%s %s (%s, %s, %s) %s %s;' % (
style.SQL_KEYWORD('DBCC'),
style.SQL_KEYWORD('CHECKIDENT'),
style.SQL_FIELD(self.quote_name(seq["table"])),
style.SQL_KEYWORD('RESEED'),
style.SQL_FIELD('%d' % seq['start_id']),
style.SQL_KEYWORD('WITH'),
style.SQL_KEYWORD('NO_INFOMSGS'),
) for seq in seqs])

sql_list.extend(['ALTER TABLE %s CHECK CONSTRAINT %s;' %
(self.quote_name(fk[0]), self.quote_name(fk[1])) for fk in fks])
return sql_list

COLUMNS = "TABLE_NAME, CONSTRAINT_NAME"
WHERE = "CONSTRAINT_TYPE not in ('PRIMARY KEY','UNIQUE')"
cursor.execute(
"SELECT {} FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE {}".format(COLUMNS, WHERE))
fks = cursor.fetchall()
sql_list = ['ALTER TABLE %s NOCHECK CONSTRAINT %s;' %
(self.quote_name(fk[0]), self.quote_name(fk[1])) for fk in fks]
sql_list.extend(['%s %s %s;' % (style.SQL_KEYWORD('DELETE'), style.SQL_KEYWORD('FROM'),
style.SQL_FIELD(self.quote_name(table))) for table in tables])

if self.connection.to_azure_sql_db and self.connection.sql_server_version < 2014:
warnings.warn("Resetting identity columns is not supported "
"on this versios of Azure SQL Database.",
RuntimeWarning)
else:
return []
# Then reset the counters on each table.
sql_list.extend(['%s %s (%s, %s, %s) %s %s;' % (
style.SQL_KEYWORD('DBCC'),
style.SQL_KEYWORD('CHECKIDENT'),
style.SQL_FIELD(self.quote_name(seq["table"])),
style.SQL_KEYWORD('RESEED'),
style.SQL_FIELD('%d' % seq['start_id']),
style.SQL_KEYWORD('WITH'),
style.SQL_KEYWORD('NO_INFOMSGS'),
) for seq in seqs])

sql_list.extend(['ALTER TABLE %s CHECK CONSTRAINT %s;' %
(self.quote_name(fk[0]), self.quote_name(fk[1])) for fk in fks])
return sql_list

def start_transaction_sql(self):
"""
Expand Down