Skip to content

Commit

Permalink
provide compatibility with Django 1.10
Browse files Browse the repository at this point in the history
  • Loading branch information
michiya committed Aug 5, 2016
1 parent d2aaa85 commit 4ebdd56
Show file tree
Hide file tree
Showing 8 changed files with 46 additions and 24 deletions.
12 changes: 6 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Microsoft SQL Server and Azure SQL Database.
Features
--------

- Supports Django 1.9.9
- Supports Django 1.10
- Supports Microsoft SQL Server 2005, 2008/2008R2, 2012, 2014, 2016 and
Azure SQL Database
- Supports LIMIT+OFFSET and offset w/o LIMIT emulation.
Expand All @@ -31,7 +31,7 @@ Features
Dependencies
------------

- Django 1.9.9
- Django 1.10
- pyodbc 3.0 or newer

Installation
Expand Down Expand Up @@ -220,7 +220,7 @@ Here is an example of the database settings:
'PORT': '',

'OPTIONS': {
'driver': 'ODBC Driver 11 for SQL Server',
'driver': 'ODBC Driver 13 for SQL Server',
},
},
}
Expand All @@ -238,12 +238,12 @@ The following features are currently not supported:
Notice
------

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

pip install "django-pyodbc-azure<1.9"
pip install "django-pyodbc-azure<1.10"

License
-------
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

setup(
name='django-pyodbc-azure',
version='1.9.9.0',
version='1.10.0.0',
description='Django backend for Microsoft SQL Server and Azure SQL Database using pyodbc',
long_description=open('README.rst').read(),
author='Michiya Takahashi',
Expand All @@ -27,7 +27,7 @@
license='BSD',
packages=['sql_server', 'sql_server.pyodbc'],
install_requires=[
'Django>=1.9.9,<1.10',
'Django>=1.10,<1.11',
'pyodbc>=3.0',
],
classifiers=CLASSIFIERS,
Expand Down
3 changes: 2 additions & 1 deletion sql_server/pyodbc/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

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

try:
Expand Down Expand Up @@ -70,6 +70,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
# If a column type is set to None, it won't be included in the output.
data_types = {
'AutoField': 'int IDENTITY (1, 1)',
'BigAutoField': 'bigint IDENTITY (1, 1)',
'BigIntegerField': 'bigint',
'BinaryField': 'varbinary(max)',
'BooleanField': 'bit',
Expand Down
16 changes: 8 additions & 8 deletions sql_server/pyodbc/compiler.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from itertools import chain

from django.db.models.aggregates import Avg, Count, StdDev, Variance
from django.db.models.expressions import Ref, Value
from django.db.models.functions import ConcatPair, Greatest, Least, Length, Substr
Expand All @@ -13,8 +15,7 @@ def _as_sql_agv(self, compiler, connection):
def _as_sql_concatpair(self, compiler, connection):
if connection.sql_server_version < 2012:
node = self.coalesce()
node.arg_joiner = ' + '
return node.as_sql(compiler, connection, template='%(expressions)s')
return node.as_sql(compiler, connection, arg_joiner=' + ', template='%(expressions)s')
else:
return self.as_sql(compiler, connection)

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

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

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

if can_bulk:
result.append(self.connection.ops.bulk_insert_sql(fields, placeholder_rows))
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 @@ -17,6 +17,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
has_select_for_update = True
has_select_for_update_nowait = True
has_zoneinfo_database = pytz is not None
ignores_quoted_identifier_case = True
requires_literal_defaults = True
requires_sqlparse_for_splitting = False
supports_1000_query_parameters = False
Expand All @@ -27,6 +28,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_sequence_reset = False
supports_subqueries_in_group_by = False
supports_tablespaces = True
supports_temporal_subtraction = True
supports_timezones = False
supports_transactions = True
uses_savepoints = True
12 changes: 10 additions & 2 deletions sql_server/pyodbc/introspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
FieldInfo = namedtuple('FieldInfo', FieldInfo._fields + ('default',))

SQL_AUTOFIELD = -777555
SQL_BIGAUTOFIELD = -777444


class DatabaseIntrospection(BaseDatabaseIntrospection):
# Map type codes to Django Field types.
data_types_reverse = {
SQL_AUTOFIELD: 'AutoField',
SQL_BIGAUTOFIELD: 'BigAutoField',
Database.SQL_BIGINT: 'BigIntegerField',
#Database.SQL_BINARY: ,
Database.SQL_BIT: 'BooleanField',
Expand Down Expand Up @@ -87,16 +89,22 @@ def get_table_description(self, cursor, table_name, identity_check=True):
If set to True, the function will check each of the table's fields for the
IDENTITY property (the IDENTITY property is the MSSQL equivalent to an AutoField).
When a field is found with an IDENTITY property, it is given a custom field number
When an integer field is found with an IDENTITY property, it is given a custom field number
of SQL_AUTOFIELD, which maps to the 'AutoField' value in the DATA_TYPES_REVERSE dict.
When a bigint field is found with an IDENTITY property, it is given a custom field number
of SQL_BIGAUTOFIELD, which maps to the 'BigAutoField' value in the DATA_TYPES_REVERSE dict.
"""

# map pyodbc's cursor.columns to db-api cursor description
columns = [[c[3], c[4], None, c[6], c[6], c[8], c[10], c[12]] for c in cursor.columns(table=table_name)]
items = []
for column in columns:
if identity_check and self._is_auto_field(cursor, table_name, column[0]):
column[1] = SQL_AUTOFIELD
if column[1] == Database.SQL_BIGINT:
column[1] = SQL_BIGAUTOFIELD
else:
column[1] = SQL_AUTOFIELD
if column[1] == Database.SQL_WVARCHAR and column[3] < 4000:
column[1] = Database.SQL_WCHAR
items.append(FieldInfo(*column))
Expand Down
11 changes: 11 additions & 0 deletions sql_server/pyodbc/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,17 @@ def start_transaction_sql(self):
"""
return "BEGIN TRANSACTION"

def subtract_temporals(self, internal_type, lhs, rhs):
lhs_sql, lhs_params = lhs
rhs_sql, rhs_params = rhs
if internal_type == 'DateField':
sql = "CAST(DATEDIFF(day, %(rhs)s, %(lhs)s) AS bigint) * 86400 * 1000000"
params = rhs_params + lhs_params
else:
sql = "CAST(DATEDIFF(second, %(rhs)s, %(lhs)s) AS bigint) * 1000000 + DATEPART(microsecond, %(lhs)s) - DATEPART(microsecond, %(rhs)s)"
params = rhs_params + lhs_params * 2 + rhs_params
return sql % {'lhs':lhs_sql, 'rhs':rhs_sql}, params

def tablespace_sql(self, tablespace, inline=False):
"""
Returns the SQL that will be appended to tables or rows to define
Expand Down
10 changes: 5 additions & 5 deletions sql_server/pyodbc/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,6 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
sql_alter_column_null = "ALTER COLUMN %(column)s %(type)s NULL"
sql_alter_column_type = "ALTER COLUMN %(column)s %(type)s"
sql_create_column = "ALTER TABLE %(table)s ADD %(column)s %(definition)s"
sql_create_fk = "ALTER TABLE %(table)s ADD CONSTRAINT %(name)s " \
"FOREIGN KEY (%(column)s) " \
"REFERENCES %(to_table)s (%(to_column)s)"
sql_delete_column = "ALTER TABLE %(table)s DROP COLUMN %(column)s"
sql_delete_index = "DROP INDEX %(name)s ON %(table)s"
sql_delete_table = "DROP TABLE %(table)s"
Expand Down Expand Up @@ -171,6 +168,7 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
actions.append((
self.sql_alter_column_default % {
"column": self.quote_name(new_field.column),
"type": new_type,
"default": self.prepare_default(new_default),
},
[],
Expand All @@ -179,6 +177,7 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
actions.append((
self.sql_alter_column_default % {
"column": self.quote_name(new_field.column),
"type": new_type,
"default": "%s",
},
[new_default],
Expand Down Expand Up @@ -362,6 +361,7 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
"table": self.quote_name(model._meta.db_table),
"changes": self.sql_alter_column_no_default % {
"name": self.quote_name(next(iter(row))),
"type": new_type,
}
}
self.execute(sql)
Expand Down Expand Up @@ -482,7 +482,7 @@ def create_model(self, model):
definition,
))
# Autoincrement SQL (for backends with post table definition variant)
if field.get_internal_type() == "AutoField":
if field.get_internal_type() in ("AutoField", "BigAutoField"):
autoinc_sql = self.connection.ops.autoinc_sql(model._meta.db_table, field.column)
if autoinc_sql:
self.deferred_sql.extend(autoinc_sql)
Expand Down Expand Up @@ -542,7 +542,7 @@ def execute(self, sql, params=[], has_result=False):
"""
result = None
# Log the command we're running, then run it
logger.debug("%s; (params %r)" % (sql, params))
logger.debug("%s; (params %r)", sql, params, extra={'params': params, 'sql': sql})
if self.collect_sql:
ending = "" if sql.endswith(";") else ";"
if params is not None:
Expand Down

0 comments on commit 4ebdd56

Please sign in to comment.