Skip to content

Commit a738f41

Browse files
authored
Merge pull request #1 from ESSolutions/master
Merge updates from upstream
2 parents 5eec3e1 + 79e421a commit a738f41

24 files changed

+584
-104
lines changed

.github/workflows/main.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
pip install flake8
3030
- name: Linting
3131
run: |
32-
flake8
32+
flake8 --exclude testapp
3333
3434
build:
3535
runs-on: ${{ matrix.os }}
@@ -41,11 +41,14 @@ jobs:
4141
tox_env:
4242
- "py36-django22"
4343
- "py36-django30"
44+
- "py36-django31"
4445

4546
- "py37-django22"
4647
- "py37-django30"
48+
- "py37-django31"
4749

4850
- "py38-django30"
51+
- "py38-django31"
4952

5053
include:
5154
- python: "3.6"
@@ -54,15 +57,24 @@ jobs:
5457
- python: "3.6"
5558
tox_env: "py36-django30"
5659

60+
- python: "3.6"
61+
tox_env: "py36-django31"
62+
5763
- python: "3.7"
5864
tox_env: "py37-django22"
5965

6066
- python: "3.7"
6167
tox_env: "py37-django30"
6268

69+
- python: "3.7"
70+
tox_env: "py37-django31"
71+
6372
- python: "3.8"
6473
tox_env: "py38-django30"
6574

75+
- python: "3.8"
76+
tox_env: "py38-django31"
77+
6678

6779
steps:
6880
- uses: actions/checkout@v2

.travis.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,25 @@ matrix:
4141

4242
- { before_install: *linux_before_install, python: "3.6", os: linux, env: TOX_ENV=py36-django22 }
4343
- { before_install: *linux_before_install, python: "3.6", os: linux, env: TOX_ENV=py36-django30 }
44+
- { before_install: *linux_before_install, python: "3.6", os: linux, env: TOX_ENV=py36-django31 }
4445

4546
- { before_install: *linux_before_install, python: "3.7", os: linux, env: TOX_ENV=py37-django22 }
4647
- { before_install: *linux_before_install, python: "3.7", os: linux, env: TOX_ENV=py37-django30 }
48+
- { before_install: *linux_before_install, python: "3.7", os: linux, env: TOX_ENV=py37-django31 }
4749

4850
- { before_install: *linux_before_install, python: "3.8", os: linux, env: TOX_ENV=py38-django30 }
51+
- { before_install: *linux_before_install, python: "3.8", os: linux, env: TOX_ENV=py38-django31 }
4952

5053
- { before_install: *win_before_install, language: sh, python: "3.6", os: windows, env: TOX_ENV=py36-django22 }
5154
- { before_install: *win_before_install, language: sh, python: "3.6", os: windows, env: TOX_ENV=py36-django30 }
55+
- { before_install: *win_before_install, language: sh, python: "3.6", os: windows, env: TOX_ENV=py36-django31 }
5256

5357
- { before_install: *win_before_install, language: sh, python: "3.7", os: windows, env: TOX_ENV=py37-django22 }
5458
- { before_install: *win_before_install, language: sh, python: "3.7", os: windows, env: TOX_ENV=py37-django30 }
59+
- { before_install: *win_before_install, language: sh, python: "3.7", os: windows, env: TOX_ENV=py37-django31 }
5560

5661
- { before_install: *win_before_install, language: sh, python: "3.8", os: windows, env: TOX_ENV=py38-django30 }
62+
- { before_install: *win_before_install, language: sh, python: "3.8", os: windows, env: TOX_ENV=py38-django31 }
5763

5864

5965

README.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,12 @@ Dictionary. Current available keys are:
149149
definition present in the ``freetds.conf`` FreeTDS configuration file
150150
instead of a hostname or an IP address.
151151

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

155-
See http://www.freetds.org/userguide/dsnless.htm for more information.
157+
See https://www.freetds.org/userguide/dsnless.html for more information.
156158

157159
- unicode_results
158160

setup.py

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

1717
setup(
1818
name='django-mssql-backend',
19-
version='2.8.0',
19+
version='2.8.1',
2020
description='Django backend for Microsoft SQL Server',
2121
long_description=open('README.rst').read(),
2222
author='ES Solutions AB',

sql_server/pyodbc/base.py

Lines changed: 14 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

@@ -89,12 +89,14 @@ class DatabaseWrapper(BaseDatabaseWrapper):
8989
'OneToOneField': 'int',
9090
'PositiveIntegerField': 'int',
9191
'PositiveSmallIntegerField': 'smallint',
92+
'PositiveBigIntegerField': 'bigint',
9293
'SlugField': 'nvarchar(%(max_length)s)',
9394
'SmallAutoField': 'smallint IDENTITY (1, 1)',
9495
'SmallIntegerField': 'smallint',
9596
'TextField': 'nvarchar(max)',
9697
'TimeField': 'time',
9798
'UUIDField': 'char(32)',
99+
'JSONField': 'nvarchar(max)',
98100
}
99101
data_type_check_constraints = {
100102
'PositiveIntegerField': '[%(column)s] >= 0',

sql_server/pyodbc/creation.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import binascii
22
import os
33

4+
import django
45
from django.db.backends.base.creation import BaseDatabaseCreation
56

67

78
class DatabaseCreation(BaseDatabaseCreation):
9+
@property
10+
def cursor(self):
11+
if django.VERSION >= (3, 1):
12+
return self.connection._nodb_cursor
13+
14+
return self.connection._nodb_connection.cursor
815

916
def _destroy_test_db(self, test_database_name, verbosity):
1017
"""
@@ -14,7 +21,7 @@ def _destroy_test_db(self, test_database_name, verbosity):
1421
# ourselves. Connect to the previous database (not the test database)
1522
# to do so, because it's not allowed to delete a database while being
1623
# connected to it.
17-
with self.connection._nodb_connection.cursor() as cursor:
24+
with self.cursor() as cursor:
1825
to_azure_sql_db = self.connection.to_azure_sql_db
1926
if not to_azure_sql_db:
2027
cursor.execute("ALTER DATABASE %s SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
@@ -36,7 +43,7 @@ def enable_clr(self):
3643
This function will not fail if current user doesn't have
3744
permissions to enable clr, and clr is already enabled
3845
"""
39-
with self._nodb_connection.cursor() as cursor:
46+
with self.cursor() as cursor:
4047
# check whether clr is enabled
4148
cursor.execute('''
4249
SELECT value FROM sys.configurations
@@ -86,7 +93,7 @@ def install_regex_clr(self, database_name):
8693

8794
self.enable_clr()
8895

89-
with self._nodb_connection.cursor() as cursor:
96+
with self.cursor() as cursor:
9097
for s in sql:
9198
cursor.execute(s)
9299

sql_server/pyodbc/features.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44

55
class DatabaseFeatures(BaseDatabaseFeatures):
6+
can_introspect_json_field = False
7+
has_native_json_field = False
68
has_native_uuid_field = False
79
allow_sliced_subqueries_with_in = False
810
can_introspect_autofield = True
@@ -22,6 +24,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
2224
requires_literal_defaults = True
2325
requires_sqlparse_for_splitting = False
2426
supports_boolean_expr_in_select_clause = False
27+
supports_deferrable_unique_constraints = False
2528
supports_ignore_conflicts = False
2629
supports_index_on_text_field = False
2730
supports_paramstyle_pyformat = False
@@ -33,6 +36,9 @@ class DatabaseFeatures(BaseDatabaseFeatures):
3336
supports_timezones = False
3437
supports_transactions = True
3538
uses_savepoints = True
39+
supports_order_by_nulls_modifier = False
40+
supports_order_by_is_nulls = False
41+
order_by_nulls_first = True
3642

3743
@cached_property
3844
def has_bulk_insert(self):
@@ -53,3 +59,11 @@ def supports_partial_indexes(self):
5359
@cached_property
5460
def supports_functions_in_partial_indexes(self):
5561
return self.connection.sql_server_version > 2005
62+
63+
@cached_property
64+
def introspected_field_types(self):
65+
return {
66+
**super().introspected_field_types,
67+
'GenericIPAddressField': 'CharField',
68+
'PositiveBigIntegerField': 'BigIntegerField'
69+
}

sql_server/pyodbc/functions.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from django import VERSION
22
from django.db.models import BooleanField
33
from django.db.models.functions import Cast
4-
from django.db.models.functions.math import ATan2, Log, Ln, Round
4+
from django.db.models.functions.math import ATan2, Log, Ln, Mod, Round
55
from django.db.models.expressions import Case, Exists, OrderBy, When
66
from django.db.models.lookups import Lookup
77

@@ -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

@@ -26,6 +54,10 @@ def sqlserver_ln(self, compiler, connection, **extra_context):
2654
return self.as_sql(compiler, connection, function='LOG', **extra_context)
2755

2856

57+
def sqlserver_mod(self, compiler, connection, **extra_context):
58+
return self.as_sql(compiler, connection, template='%(expressions)s', arg_joiner='%%', **extra_context)
59+
60+
2961
def sqlserver_round(self, compiler, connection, **extra_context):
3062
return self.as_sql(compiler, connection, template='%(function)s(%(expressions)s, 0)', **extra_context)
3163

@@ -77,6 +109,7 @@ def sqlserver_orderby(self, compiler, connection):
77109
ATan2.as_microsoft = sqlserver_atan2
78110
Log.as_microsoft = sqlserver_log
79111
Ln.as_microsoft = sqlserver_ln
112+
Mod.as_microsoft = sqlserver_mod
80113
Round.as_microsoft = sqlserver_round
81114

82115
if DJANGO3:
@@ -85,3 +118,4 @@ def sqlserver_orderby(self, compiler, connection):
85118
Exists.as_microsoft = sqlserver_exists
86119

87120
OrderBy.as_microsoft = sqlserver_orderby
121+
OrderBy.as_sql = sqlserver_as_sql

sql_server/pyodbc/introspection.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import pyodbc as Database
2+
from collections import namedtuple
23

34
from django.db.backends.base.introspection import (
4-
BaseDatabaseIntrospection, FieldInfo, TableInfo,
5+
BaseDatabaseIntrospection, TableInfo,
56
)
67
from django.db.models.indexes import Index
78

89
SQL_AUTOFIELD = -777555
910
SQL_BIGAUTOFIELD = -777444
1011

12+
FieldInfo = namedtuple('FieldInfo', 'name type_code display_size internal_size precision scale null_ok default')
13+
1114

1215
class DatabaseIntrospection(BaseDatabaseIntrospection):
1316
# Map type codes to Django Field types.

0 commit comments

Comments
 (0)