Skip to content

Commit b403554

Browse files
committed
provide compatibility with Django 2.0
1 parent 7f0f5fa commit b403554

File tree

8 files changed

+295
-386
lines changed

8 files changed

+295
-386
lines changed

README.rst

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,21 @@ Microsoft SQL Server and Azure SQL Database.
1717
Features
1818
--------
1919

20-
- Supports Django 1.11.9
21-
- Supports Microsoft SQL Server 2005, 2008/2008R2, 2012, 2014, 2016, 2017 and
20+
- Supports Django 2.0.1
21+
- Supports Microsoft SQL Server 2008/2008R2, 2012, 2014, 2016, 2017 and
2222
Azure SQL Database
23-
- Supports LIMIT+OFFSET and offset w/o LIMIT emulation.
24-
- Passes most of the tests of the Django test suite.
23+
- Supports LIMIT+OFFSET and offset w/o LIMIT emulation
24+
- Passes most of the tests of the Django test suite
2525
- Compatible with
26-
`Micosoft ODBC Driver for SQL Server <https://msdn.microsoft.com/library/mt654048(v=sql.1).aspx>`__,
27-
`SQL Server Native Client <https://msdn.microsoft.com/library/ms130892(v=sql.120).aspx>`__,
28-
`SQL Server <https://msdn.microsoft.com/library/aa968814(vs.85).aspx>`__
26+
`Micosoft ODBC Driver for SQL Server <https://docs.microsoft.com/en-us/sql/connect/odbc/microsoft-odbc-driver-for-sql-server>`__,
27+
`SQL Server Native Client <https://msdn.microsoft.com/en-us/library/ms131321(v=sql.120).aspx>`__,
2928
and `FreeTDS <http://www.freetds.org/>`__ ODBC drivers.
3029

3130
Dependencies
3231
------------
3332

34-
- Django 1.11.9
35-
- pyodbc 3.0 or newer
33+
- Django 2.0.1
34+
- pyodbc 4.0 or newer
3635

3736
Installation
3837
------------
@@ -124,9 +123,9 @@ Dictionary. Current available keys are:
124123

125124
- driver
126125

127-
String. ODBC Driver to use (``"ODBC Driver 11 for SQL Server"`` etc).
128-
See http://msdn.microsoft.com/en-us/library/ms130892.aspx. Default is
129-
``"SQL Server"`` on Windows and ``"FreeTDS"`` on other platforms.
126+
String. ODBC Driver to use (``"ODBC Driver 13 for SQL Server"``,
127+
``"SQL Server Native Client 11.0"``, ``"FreeTDS"`` etc).
128+
Default is ``"ODBC Driver 13 for SQL Server"``.
130129

131130
- dsn
132131

@@ -168,16 +167,6 @@ Dictionary. Current available keys are:
168167
collation of your database will be used). For Chinese language you
169168
can set it to ``"Chinese_PRC_CI_AS"``.
170169

171-
- use_legacy_datetime
172-
173-
Boolean. ``DateField``, ``TimeField`` and ``DateTimeField`` of models
174-
are mapped to SQL Server's legacy ``datetime`` type if the value is ``True``
175-
(the same behavior as the original ``django-pyodbc``). Otherwise, they
176-
are mapped to new dedicated data types (``date``, ``time``, ``datetime2``).
177-
Default value is ``False``, and note that the feature is always activated
178-
when you use SQL Server 2005, or the outdated ODBC drivers (``"FreeTDS"``
179-
with TDS protocol v7.2 or earlier/``"SQL Server"``/``"SQL Native Client"``).
180-
181170
- connection_timeout
182171

183172
Integer. Sets the timeout in seconds for the database connection process.
@@ -238,12 +227,12 @@ The following features are currently not supported:
238227
Notice
239228
------
240229

241-
This version of *django-pyodbc-azure* only supports Django 1.11.
230+
This version of *django-pyodbc-azure* only supports Django 2.0.
242231
If you want to use it on older versions of Django,
243-
specify an appropriate version number (1.10.x.x for Django 1.10)
232+
specify an appropriate version number (1.11.x.x for Django 1.11)
244233
at installation like this: ::
245234

246-
pip install "django-pyodbc-azure<1.11"
235+
pip install "django-pyodbc-azure<2.0"
247236

248237
License
249238
-------

setup.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
setup(
2121
name='django-pyodbc-azure',
22-
version='1.11.9.0',
22+
version='2.0.1.0',
2323
description='Django backend for Microsoft SQL Server and Azure SQL Database using pyodbc',
2424
long_description=open('README.rst').read(),
2525
author='Michiya Takahashi',
@@ -28,8 +28,8 @@
2828
license='BSD',
2929
packages=['sql_server', 'sql_server.pyodbc'],
3030
install_requires=[
31-
'Django>=1.11.9,<2.0',
32-
'pyodbc>=3.0',
31+
'Django>=2.0.1,<2.1',
32+
'pyodbc>=4.0',
3333
],
3434
classifiers=CLASSIFIERS,
3535
keywords='azure django',

sql_server/pyodbc/base.py

Lines changed: 36 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,27 @@
77

88
from django.core.exceptions import ImproperlyConfigured
99
from django import VERSION
10-
if VERSION[:3] < (1,11,9) or VERSION[:2] >= (2,0):
10+
11+
if VERSION[:3] < (2,0,1) or VERSION[:2] >= (2,1):
1112
raise ImproperlyConfigured("Django %d.%d.%d is not supported." % VERSION[:3])
1213

1314
try:
1415
import pyodbc as Database
1516
except ImportError as e:
1617
raise ImproperlyConfigured("Error loading pyodbc module: %s" % e)
1718

18-
pyodbc_ver = tuple(map(int, Database.version.split('.')[:2]))
19-
if pyodbc_ver < (3, 0):
20-
raise ImproperlyConfigured("pyodbc 3.0 or newer is required; you have %s" % Database.version)
19+
from django.utils.version import get_version_tuple
20+
21+
pyodbc_ver = get_version_tuple(Database.version)
22+
if pyodbc_ver < (4,0):
23+
raise ImproperlyConfigured("pyodbc 4.0 or newer is required; you have %s" % Database.version)
2124

2225
from django.conf import settings
26+
from django.db import NotSupportedError
2327
from django.db.backends.base.base import BaseDatabaseWrapper
2428
from django.db.backends.base.validation import BaseDatabaseValidation
2529
from django.utils.encoding import smart_str
2630
from django.utils.functional import cached_property
27-
from django.utils.six import binary_type, text_type
2831
from django.utils.timezone import utc
2932

3033
if hasattr(settings, 'DATABASE_CONNECTION_POOLING'):
@@ -64,6 +67,7 @@ def encode_value(v):
6467

6568
class DatabaseWrapper(BaseDatabaseWrapper):
6669
vendor = 'microsoft'
70+
display_name = 'SQL Server'
6771
# This dictionary maps Field objects to their associated MS SQL column
6872
# types, as strings. Column-type strings can contain format strings; they'll
6973
# be interpolated against the values of Field.__dict__ before being output.
@@ -75,7 +79,6 @@ class DatabaseWrapper(BaseDatabaseWrapper):
7579
'BinaryField': 'varbinary(max)',
7680
'BooleanField': 'bit',
7781
'CharField': 'nvarchar(%(max_length)s)',
78-
'CommaSeparatedIntegerField': 'nvarchar(%(max_length)s)',
7982
'DateField': 'date',
8083
'DateTimeField': 'datetime2',
8184
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
@@ -149,7 +152,6 @@ class DatabaseWrapper(BaseDatabaseWrapper):
149152
'08S02',
150153
)
151154
_sql_server_versions = {
152-
9: 2005,
153155
10: 2008,
154156
11: 2012,
155157
12: 2014,
@@ -171,7 +173,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
171173
)
172174

173175
def __init__(self, *args, **kwargs):
174-
super(DatabaseWrapper, self).__init__(*args, **kwargs)
176+
super().__init__(*args, **kwargs)
175177

176178
opts = self.settings_dict["OPTIONS"]
177179

@@ -190,9 +192,6 @@ def __init__(self, *args, **kwargs):
190192
else:
191193
self.driver_charset = opts.get('driver_charset', None)
192194

193-
# data type compatibility to databases created by old django-pyodbc
194-
self.use_legacy_datetime = opts.get('use_legacy_datetime', False)
195-
196195
# interval to wait for recovery from network error
197196
interval = opts.get('connection_recovery_interval_msec', 0.0)
198197
self.connection_recovery_interval_msec = float(interval) / 1000
@@ -236,17 +235,14 @@ def get_new_connection(self, conn_params):
236235
password = conn_params.get('PASSWORD', None)
237236
port = conn_params.get('PORT', None)
238237

239-
default_driver = 'SQL Server' if os.name == 'nt' else 'FreeTDS'
240238
options = conn_params.get('OPTIONS', {})
241-
driver = options.get('driver', default_driver)
239+
driver = options.get('driver', 'ODBC Driver 13 for SQL Server')
242240
dsn = options.get('dsn', None)
243241

244242
# Microsoft driver names assumed here are:
245-
# * SQL Server
246-
# * SQL Native Client
247243
# * SQL Server Native Client 10.0/11.0
248244
# * ODBC Driver 11/13 for SQL Server
249-
ms_drivers = re.compile('.*SQL (Server$|(Server )?Native Client)')
245+
ms_drivers = re.compile('^ODBC Driver .* for SQL Server$|^SQL Server Native Client')
250246

251247
# available ODBC connection string keywords:
252248
# (Microsoft drivers for Windows)
@@ -284,9 +280,7 @@ def get_new_connection(self, conn_params):
284280

285281
cstr_parts['DATABASE'] = database
286282

287-
if ms_drivers.match(driver) and not driver == 'SQL Server':
288-
self.supports_mars = True
289-
if self.supports_mars:
283+
if ms_drivers.match(driver) and os.name == 'nt':
290284
cstr_parts['MARS_Connection'] = 'yes'
291285

292286
connstr = encode_connection_string(cstr_parts)
@@ -327,37 +321,26 @@ def get_new_connection(self, conn_params):
327321

328322
def init_connection_state(self):
329323
drv_name = self.connection.getinfo(Database.SQL_DRIVER_NAME).upper()
330-
driver_is_freetds = drv_name.startswith('LIBTDSODBC')
331-
driver_is_sqlsrv32 = drv_name == 'SQLSRV32.DLL'
332324

333-
if driver_is_freetds:
334-
self.supports_mars = False
325+
if drv_name.startswith('LIBTDSODBC'):
335326
try:
336327
drv_ver = self.connection.getinfo(Database.SQL_DRIVER_VER)
337-
ver = tuple(map(int, drv_ver.split('.')[:2]))
328+
ver = get_version_tuple(drv_ver.split('.')[:2])
338329
if ver < (0, 95):
339-
# FreeTDS can't execute some sql queries like CREATE DATABASE etc.
340-
# in multi-statement, so we need to commit the above SQL sentence(s)
341-
# to avoid this
342-
self.connection.commit()
330+
raise ImproperlyConfigured(
331+
"FreeTDS 0.95 or newer is required.")
343332
except:
344333
# unknown driver version
345334
pass
346-
elif driver_is_sqlsrv32:
347-
self.supports_mars = False
348335

349-
ms_drv_names = re.compile('^(LIB)?(SQLN?CLI|MSODBCSQL)')
336+
ms_drv_names = re.compile('^(LIB)?(SQLNCLI|MSODBCSQL)')
350337

351-
if driver_is_sqlsrv32 or ms_drv_names.match(drv_name):
338+
if ms_drv_names.match(drv_name):
352339
self.driver_charset = None
353-
354-
# http://msdn.microsoft.com/en-us/library/ms131686.aspx
355-
if self.supports_mars and ms_drv_names.match(drv_name):
340+
# http://msdn.microsoft.com/en-us/library/ms131686.aspx
341+
self.supports_mars = True
356342
self.features.can_use_chunked_reads = True
357343

358-
if self.sql_server_version < 2008:
359-
self.features.has_bulk_insert = False
360-
361344
settings_dict = self.settings_dict
362345
cursor = self.create_cursor()
363346

@@ -370,17 +353,10 @@ def init_connection_state(self):
370353
cursor.execute('SET DATEFORMAT ymd; SET DATEFIRST %s' % datefirst)
371354

372355
# http://blogs.msdn.com/b/sqlnativeclient/archive/2008/02/27/microsoft-sql-server-native-client-and-microsoft-sql-server-2008-native-client.aspx
373-
try:
374-
val = cursor.execute('SELECT SYSDATETIME()').fetchone()[0]
375-
if isinstance(val, text_type):
376-
# the driver doesn't support the modern datetime types
377-
self.use_legacy_datetime = True
378-
except:
379-
# the server doesn't support the modern datetime types
380-
self.use_legacy_datetime = True
381-
if self.use_legacy_datetime:
382-
self._use_legacy_datetime()
383-
self.features.supports_microsecond_precision = False
356+
val = cursor.execute('SELECT SYSDATETIME()').fetchone()[0]
357+
if isinstance(val, str):
358+
raise ImproperlyConfigured(
359+
"The database driver doesn't support modern datatime types.")
384360

385361
def is_usable(self):
386362
try:
@@ -401,7 +377,7 @@ def sql_server_version(self):
401377
ver = cursor.fetchone()[0]
402378
ver = int(ver.split('.')[0])
403379
if not ver in self._sql_server_versions:
404-
raise NotImplementedError('SQL Server v%d is not supported.' % ver)
380+
raise NotSupportedError('SQL Server v%d is not supported.' % ver)
405381
return self._sql_server_versions[ver]
406382

407383
@cached_property
@@ -446,7 +422,7 @@ def _savepoint_commit(self, sid):
446422

447423
def _savepoint_rollback(self, sid):
448424
with self.cursor() as cursor:
449-
# FreeTDS v0.95 requires TRANCOUNT that is greater than 0
425+
# FreeTDS requires TRANCOUNT that is greater than 0
450426
cursor.execute('SELECT @@TRANCOUNT')
451427
trancount = cursor.fetchone()[0]
452428
if trancount > 0:
@@ -456,15 +432,11 @@ def _set_autocommit(self, autocommit):
456432
with self.wrap_database_errors:
457433
allowed = not autocommit
458434
if not allowed:
459-
# FreeTDS v0.95 requires TRANCOUNT that is greater than 0
435+
# FreeTDS requires TRANCOUNT that is greater than 0
460436
allowed = self._get_trancount() > 0
461437
if allowed:
462438
self.connection.autocommit = autocommit
463439

464-
def _use_legacy_datetime(self):
465-
for field in ('DateField', 'DateTimeField', 'TimeField'):
466-
self.data_types[field] = 'datetime'
467-
468440
def check_constraints(self, table_names=None):
469441
self._execute_foreach('ALTER TABLE %s WITH CHECK CHECK CONSTRAINT ALL',
470442
table_names)
@@ -502,7 +474,7 @@ def close(self):
502474
self.cursor.close()
503475

504476
def format_sql(self, sql, params):
505-
if self.driver_charset and isinstance(sql, text_type):
477+
if self.driver_charset and isinstance(sql, str):
506478
# FreeTDS (and other ODBC drivers?) doesn't support Unicode
507479
# yet, so we need to encode the SQL clause itself in utf-8
508480
sql = smart_str(sql, self.driver_charset)
@@ -517,15 +489,15 @@ def format_params(self, params):
517489
fp = []
518490
if params is not None:
519491
for p in params:
520-
if isinstance(p, text_type):
492+
if isinstance(p, str):
521493
if self.driver_charset:
522494
# FreeTDS (and other ODBC drivers?) doesn't support Unicode
523495
# yet, so we need to encode parameters in utf-8
524496
fp.append(smart_str(p, self.driver_charset))
525497
else:
526498
fp.append(p)
527499

528-
elif isinstance(p, binary_type):
500+
elif isinstance(p, bytes):
529501
fp.append(p)
530502

531503
elif isinstance(p, type(True)):
@@ -568,24 +540,25 @@ def format_rows(self, rows):
568540
def format_row(self, row):
569541
"""
570542
Decode data coming from the database if needed and convert rows to tuples
571-
(pyodbc Rows are not sliceable).
543+
(pyodbc Rows are not hashable).
572544
"""
573545
if self.driver_charset:
574546
for i in range(len(row)):
575547
f = row[i]
576548
# FreeTDS (and other ODBC drivers?) doesn't support Unicode
577549
# yet, so we need to decode utf-8 data coming from the DB
578-
if isinstance(f, binary_type):
550+
if isinstance(f, bytes):
579551
row[i] = f.decode(self.driver_charset)
580-
return row
552+
return tuple(row)
581553

582554
def fetchone(self):
583555
row = self.cursor.fetchone()
584556
if row is not None:
585557
row = self.format_row(row)
586558
# Any remaining rows in the current set must be discarded
587559
# before changing autocommit mode when you use FreeTDS
588-
self.cursor.nextset()
560+
if not self.connection.supports_mars:
561+
self.cursor.nextset()
589562
return row
590563

591564
def fetchmany(self, chunk):

0 commit comments

Comments
 (0)