Skip to content

Commit 4ebdd56

Browse files
committed
provide compatibility with Django 1.10
1 parent d2aaa85 commit 4ebdd56

File tree

8 files changed

+46
-24
lines changed

8 files changed

+46
-24
lines changed

README.rst

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

20-
- Supports Django 1.9.9
20+
- Supports Django 1.10
2121
- Supports Microsoft SQL Server 2005, 2008/2008R2, 2012, 2014, 2016 and
2222
Azure SQL Database
2323
- Supports LIMIT+OFFSET and offset w/o LIMIT emulation.
@@ -31,7 +31,7 @@ Features
3131
Dependencies
3232
------------
3333

34-
- Django 1.9.9
34+
- Django 1.10
3535
- pyodbc 3.0 or newer
3636

3737
Installation
@@ -220,7 +220,7 @@ Here is an example of the database settings:
220220
'PORT': '',
221221

222222
'OPTIONS': {
223-
'driver': 'ODBC Driver 11 for SQL Server',
223+
'driver': 'ODBC Driver 13 for SQL Server',
224224
},
225225
},
226226
}
@@ -238,12 +238,12 @@ The following features are currently not supported:
238238
Notice
239239
------
240240

241-
This version of *django-pyodbc-azure* only supports Django 1.9.
241+
This version of *django-pyodbc-azure* only supports Django 1.10.
242242
If you want to use it on older versions of Django,
243-
specify an appropriate version number (1.8.x.x for Django 1.8)
243+
specify an appropriate version number (1.9.x.x for Django 1.9)
244244
at installation like this: ::
245245

246-
pip install "django-pyodbc-azure<1.9"
246+
pip install "django-pyodbc-azure<1.10"
247247

248248
License
249249
-------

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
setup(
2020
name='django-pyodbc-azure',
21-
version='1.9.9.0',
21+
version='1.10.0.0',
2222
description='Django backend for Microsoft SQL Server and Azure SQL Database using pyodbc',
2323
long_description=open('README.rst').read(),
2424
author='Michiya Takahashi',
@@ -27,7 +27,7 @@
2727
license='BSD',
2828
packages=['sql_server', 'sql_server.pyodbc'],
2929
install_requires=[
30-
'Django>=1.9.9,<1.10',
30+
'Django>=1.10,<1.11',
3131
'pyodbc>=3.0',
3232
],
3333
classifiers=CLASSIFIERS,

sql_server/pyodbc/base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

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

1313
try:
@@ -70,6 +70,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
7070
# If a column type is set to None, it won't be included in the output.
7171
data_types = {
7272
'AutoField': 'int IDENTITY (1, 1)',
73+
'BigAutoField': 'bigint IDENTITY (1, 1)',
7374
'BigIntegerField': 'bigint',
7475
'BinaryField': 'varbinary(max)',
7576
'BooleanField': 'bit',

sql_server/pyodbc/compiler.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from itertools import chain
2+
13
from django.db.models.aggregates import Avg, Count, StdDev, Variance
24
from django.db.models.expressions import Ref, Value
35
from django.db.models.functions import ConcatPair, Greatest, Least, Length, Substr
@@ -13,8 +15,7 @@ def _as_sql_agv(self, compiler, connection):
1315
def _as_sql_concatpair(self, compiler, connection):
1416
if connection.sql_server_version < 2012:
1517
node = self.coalesce()
16-
node.arg_joiner = ' + '
17-
return node.as_sql(compiler, connection, template='%(expressions)s')
18+
return node.as_sql(compiler, connection, arg_joiner=' + ', template='%(expressions)s')
1819
else:
1920
return self.as_sql(compiler, connection)
2021

@@ -25,17 +26,15 @@ def _as_sql_greatest(self, compiler, connection):
2526
# SQL Server does not provide GREATEST function,
2627
# so we emulate it with a table value constructor
2728
# https://msdn.microsoft.com/en-us/library/dd776382.aspx
28-
self.arg_joiner = '), ('
2929
template='(SELECT MAX(value) FROM (VALUES (%(expressions)s)) AS _%(function)s(value))'
30-
return self.as_sql(compiler, connection, template=template)
30+
return self.as_sql(compiler, connection, arg_joiner='), (', template=template)
3131

3232
def _as_sql_least(self, compiler, connection):
3333
# SQL Server does not provide LEAST function,
3434
# so we emulate it with a table value constructor
3535
# https://msdn.microsoft.com/en-us/library/dd776382.aspx
36-
self.arg_joiner = '), ('
3736
template='(SELECT MIN(value) FROM (VALUES (%(expressions)s)) AS _%(function)s(value))'
38-
return self.as_sql(compiler, connection, template=template)
37+
return self.as_sql(compiler, connection, arg_joiner='), (', template=template)
3938

4039
def _as_sql_length(self, compiler, connection):
4140
return self.as_sql(compiler, connection, function='LEN')
@@ -275,8 +274,9 @@ def as_sql(self):
275274
if self.return_id and self.connection.features.can_return_id_from_insert:
276275
result.insert(0, 'SET NOCOUNT ON')
277276
result.append((values_format + ';') % ', '.join(placeholder_rows[0]))
278-
result.append('SELECT CAST(SCOPE_IDENTITY() AS int)')
279-
return [(" ".join(result), tuple(param_rows[0]))]
277+
params = [param_rows[0]]
278+
result.append('SELECT CAST(SCOPE_IDENTITY() AS bigint)')
279+
return [(" ".join(result), tuple(chain.from_iterable(params)))]
280280

281281
if can_bulk:
282282
result.append(self.connection.ops.bulk_insert_sql(fields, placeholder_rows))

sql_server/pyodbc/features.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
1717
has_select_for_update = True
1818
has_select_for_update_nowait = True
1919
has_zoneinfo_database = pytz is not None
20+
ignores_quoted_identifier_case = True
2021
requires_literal_defaults = True
2122
requires_sqlparse_for_splitting = False
2223
supports_1000_query_parameters = False
@@ -27,6 +28,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
2728
supports_sequence_reset = False
2829
supports_subqueries_in_group_by = False
2930
supports_tablespaces = True
31+
supports_temporal_subtraction = True
3032
supports_timezones = False
3133
supports_transactions = True
3234
uses_savepoints = True

sql_server/pyodbc/introspection.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99
FieldInfo = namedtuple('FieldInfo', FieldInfo._fields + ('default',))
1010

1111
SQL_AUTOFIELD = -777555
12+
SQL_BIGAUTOFIELD = -777444
1213

1314

1415
class DatabaseIntrospection(BaseDatabaseIntrospection):
1516
# Map type codes to Django Field types.
1617
data_types_reverse = {
1718
SQL_AUTOFIELD: 'AutoField',
19+
SQL_BIGAUTOFIELD: 'BigAutoField',
1820
Database.SQL_BIGINT: 'BigIntegerField',
1921
#Database.SQL_BINARY: ,
2022
Database.SQL_BIT: 'BooleanField',
@@ -87,16 +89,22 @@ def get_table_description(self, cursor, table_name, identity_check=True):
8789
If set to True, the function will check each of the table's fields for the
8890
IDENTITY property (the IDENTITY property is the MSSQL equivalent to an AutoField).
8991
90-
When a field is found with an IDENTITY property, it is given a custom field number
92+
When an integer field is found with an IDENTITY property, it is given a custom field number
9193
of SQL_AUTOFIELD, which maps to the 'AutoField' value in the DATA_TYPES_REVERSE dict.
94+
95+
When a bigint field is found with an IDENTITY property, it is given a custom field number
96+
of SQL_BIGAUTOFIELD, which maps to the 'BigAutoField' value in the DATA_TYPES_REVERSE dict.
9297
"""
9398

9499
# map pyodbc's cursor.columns to db-api cursor description
95100
columns = [[c[3], c[4], None, c[6], c[6], c[8], c[10], c[12]] for c in cursor.columns(table=table_name)]
96101
items = []
97102
for column in columns:
98103
if identity_check and self._is_auto_field(cursor, table_name, column[0]):
99-
column[1] = SQL_AUTOFIELD
104+
if column[1] == Database.SQL_BIGINT:
105+
column[1] = SQL_BIGAUTOFIELD
106+
else:
107+
column[1] = SQL_AUTOFIELD
100108
if column[1] == Database.SQL_WVARCHAR and column[3] < 4000:
101109
column[1] = Database.SQL_WCHAR
102110
items.append(FieldInfo(*column))

sql_server/pyodbc/operations.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,17 @@ def start_transaction_sql(self):
394394
"""
395395
return "BEGIN TRANSACTION"
396396

397+
def subtract_temporals(self, internal_type, lhs, rhs):
398+
lhs_sql, lhs_params = lhs
399+
rhs_sql, rhs_params = rhs
400+
if internal_type == 'DateField':
401+
sql = "CAST(DATEDIFF(day, %(rhs)s, %(lhs)s) AS bigint) * 86400 * 1000000"
402+
params = rhs_params + lhs_params
403+
else:
404+
sql = "CAST(DATEDIFF(second, %(rhs)s, %(lhs)s) AS bigint) * 1000000 + DATEPART(microsecond, %(lhs)s) - DATEPART(microsecond, %(rhs)s)"
405+
params = rhs_params + lhs_params * 2 + rhs_params
406+
return sql % {'lhs':lhs_sql, 'rhs':rhs_sql}, params
407+
397408
def tablespace_sql(self, tablespace, inline=False):
398409
"""
399410
Returns the SQL that will be appended to tables or rows to define

sql_server/pyodbc/schema.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,6 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
4343
sql_alter_column_null = "ALTER COLUMN %(column)s %(type)s NULL"
4444
sql_alter_column_type = "ALTER COLUMN %(column)s %(type)s"
4545
sql_create_column = "ALTER TABLE %(table)s ADD %(column)s %(definition)s"
46-
sql_create_fk = "ALTER TABLE %(table)s ADD CONSTRAINT %(name)s " \
47-
"FOREIGN KEY (%(column)s) " \
48-
"REFERENCES %(to_table)s (%(to_column)s)"
4946
sql_delete_column = "ALTER TABLE %(table)s DROP COLUMN %(column)s"
5047
sql_delete_index = "DROP INDEX %(name)s ON %(table)s"
5148
sql_delete_table = "DROP TABLE %(table)s"
@@ -171,6 +168,7 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
171168
actions.append((
172169
self.sql_alter_column_default % {
173170
"column": self.quote_name(new_field.column),
171+
"type": new_type,
174172
"default": self.prepare_default(new_default),
175173
},
176174
[],
@@ -179,6 +177,7 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
179177
actions.append((
180178
self.sql_alter_column_default % {
181179
"column": self.quote_name(new_field.column),
180+
"type": new_type,
182181
"default": "%s",
183182
},
184183
[new_default],
@@ -362,6 +361,7 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
362361
"table": self.quote_name(model._meta.db_table),
363362
"changes": self.sql_alter_column_no_default % {
364363
"name": self.quote_name(next(iter(row))),
364+
"type": new_type,
365365
}
366366
}
367367
self.execute(sql)
@@ -482,7 +482,7 @@ def create_model(self, model):
482482
definition,
483483
))
484484
# Autoincrement SQL (for backends with post table definition variant)
485-
if field.get_internal_type() == "AutoField":
485+
if field.get_internal_type() in ("AutoField", "BigAutoField"):
486486
autoinc_sql = self.connection.ops.autoinc_sql(model._meta.db_table, field.column)
487487
if autoinc_sql:
488488
self.deferred_sql.extend(autoinc_sql)
@@ -542,7 +542,7 @@ def execute(self, sql, params=[], has_result=False):
542542
"""
543543
result = None
544544
# Log the command we're running, then run it
545-
logger.debug("%s; (params %r)" % (sql, params))
545+
logger.debug("%s; (params %r)", sql, params, extra={'params': params, 'sql': sql})
546546
if self.collect_sql:
547547
ending = "" if sql.endswith(";") else ";"
548548
if params is not None:

0 commit comments

Comments
 (0)