Skip to content

Commit

Permalink
provide Django 2.1 compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
michiya committed Aug 20, 2018
1 parent 317b97e commit c1d7fb8
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 122 deletions.
10 changes: 5 additions & 5 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 2.0.8
- Supports Django 2.1
- Supports Microsoft SQL Server 2008/2008R2, 2012, 2014, 2016, 2017 and
Azure SQL Database
- Passes most of the tests of the Django test suite
Expand All @@ -29,7 +29,7 @@ Features
Dependencies
------------

- Django 2.0.8
- Django 2.1
- pyodbc 3.0 or newer

Installation
Expand Down Expand Up @@ -241,9 +241,9 @@ The following features are currently not supported:
Notice
------

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

pip install "django-pyodbc-azure<2.0"
pip install "django-pyodbc-azure<2.1"
5 changes: 2 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
'Framework :: Django',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
Expand All @@ -18,7 +17,7 @@

setup(
name='django-pyodbc-azure',
version='2.0.8.0',
version='2.1.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 +26,7 @@
license='BSD',
packages=['sql_server', 'sql_server.pyodbc'],
install_requires=[
'Django>=2.0.8,<2.1',
'Django>=2.1.0,<2.2',
'pyodbc>=3.0',
],
classifiers=CLASSIFIERS,
Expand Down
2 changes: 1 addition & 1 deletion sql_server/pyodbc/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from django.core.exceptions import ImproperlyConfigured
from django import VERSION

if VERSION[:3] < (2,0,8) or VERSION[:2] >= (2,1):
if VERSION[:3] < (2,1,0) or VERSION[:2] >= (2,2):
raise ImproperlyConfigured("Django %d.%d.%d is not supported." % VERSION[:3])

try:
Expand Down
97 changes: 76 additions & 21 deletions sql_server/pyodbc/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

from django.db.models.aggregates import Avg, Count, StdDev, Variance
from django.db.models.expressions import Exists, OrderBy, Ref, Value
from django.db.models.functions import ConcatPair, Greatest, Least, Length, StrIndex, Substr
from django.db.models.functions import (
Chr, ConcatPair, Greatest, Least, Length, LPad, Repeat, RPad, StrIndex, Substr, Trim
)
from django.db.models.sql import compiler
from django.db.transaction import TransactionManagementError
from django.db.utils import DatabaseError, NotSupportedError
Expand All @@ -12,6 +14,9 @@
def _as_sql_agv(self, compiler, connection):
return self.as_sql(compiler, connection, template='%(function)s(CONVERT(float, %(field)s))')

def _as_sql_chr(self, compiler, connection):
return self.as_sql(compiler, connection, function='NCHAR')

def _as_sql_concatpair(self, compiler, connection):
if connection.sql_server_version < 2012:
node = self.coalesce()
Expand Down Expand Up @@ -39,6 +44,23 @@ def _as_sql_least(self, compiler, connection):
def _as_sql_length(self, compiler, connection):
return self.as_sql(compiler, connection, function='LEN')

def _as_sql_lpad(self, compiler, connection):
i = iter(self.get_source_expressions())
expression, expression_arg = compiler.compile(next(i))
length, length_arg = compiler.compile(next(i))
fill_text, fill_text_arg = compiler.compile(next(i))
params = []
params.extend(fill_text_arg)
params.extend(length_arg)
params.extend(length_arg)
params.extend(expression_arg)
params.extend(length_arg)
params.extend(expression_arg)
params.extend(expression_arg)
template = ('LEFT(REPLICATE(%(fill_text)s, %(length)s), CASE WHEN %(length)s > LEN(%(expression)s) '
'THEN %(length)s - LEN(%(expression)s) ELSE 0 END) + %(expression)s')
return template % {'expression':expression, 'length':length, 'fill_text':fill_text }, params

def _as_sql_exists(self, compiler, connection, template=None, **extra_context):
# MS SQL doesn't allow EXISTS() in the SELECT list, so wrap it with a
# CASE WHEN expression. Change the template since the When expression
Expand All @@ -55,6 +77,22 @@ def _as_sql_order_by(self, compiler, connection):
template = 'CASE WHEN %(expression)s IS NULL THEN 0 ELSE 1 END, %(expression)s %(ordering)s'
return self.as_sql(compiler, connection, template=template)

def _as_sql_repeat(self, compiler, connection):
return self.as_sql(compiler, connection, function='REPLICATE')

def _as_sql_rpad(self, compiler, connection):
i = iter(self.get_source_expressions())
expression, expression_arg = compiler.compile(next(i))
length, length_arg = compiler.compile(next(i))
fill_text, fill_text_arg = compiler.compile(next(i))
params = []
params.extend(expression_arg)
params.extend(fill_text_arg)
params.extend(length_arg)
params.extend(length_arg)
template='LEFT(%(expression)s + REPLICATE(%(fill_text)s, %(length)s), %(length)s)'
return template % {'expression':expression, 'length':length, 'fill_text':fill_text }, params

def _as_sql_stddev(self, compiler, connection):
function = 'STDEV'
if self.function == 'STDDEV_POP':
Expand All @@ -72,6 +110,9 @@ def _as_sql_substr(self, compiler, connection):
self.get_source_expressions().append(Value(2**31-1))
return self.as_sql(compiler, connection)

def _as_sql_trim(self, compiler, connection):
return self.as_sql(compiler, connection, template='LTRIM(RTRIM(%(expressions)s))')

def _as_sql_variance(self, compiler, connection):
function = 'VAR'
if self.function == 'VAR_POP':
Expand Down Expand Up @@ -120,6 +161,8 @@ def as_sql(self, with_limits=True, with_col_aliases=False):
try:
extra_select, order_by, group_by = self.pre_sql_setup()
for_update_part = None
# Is a LIMIT/OFFSET clause needed?
with_limit_offset = with_limits and (self.query.high_mark is not None or self.query.low_mark)
combinator = self.query.combinator
features = self.connection.features

Expand All @@ -138,7 +181,7 @@ def as_sql(self, with_limits=True, with_col_aliases=False):
raise NotSupportedError('{} is not supported on this database backend.'.format(combinator))
result, params = self.get_combinator_sql(combinator, self.query.combinator_all)
else:
distinct_fields = self.get_distinct()
distinct_fields, distinct_params = self.get_distinct()
# This must come after 'select', 'ordering', and 'distinct' -- see
# docstring of get_from_clause() for details.
from_, f_params = self.get_from_clause()
Expand All @@ -148,7 +191,12 @@ def as_sql(self, with_limits=True, with_col_aliases=False):
result = ['SELECT']

if self.query.distinct:
result.append(self.connection.ops.distinct_sql(distinct_fields))
distinct_result, distinct_params = self.connection.ops.distinct_sql(
distinct_fields,
distinct_params,
)
result += distinct_result
params += distinct_params

# SQL Server requires the keword for limitting at the begenning
if do_limit and not do_offset:
Expand Down Expand Up @@ -191,13 +239,11 @@ def as_sql(self, with_limits=True, with_col_aliases=False):
elif not order_by:
order_by.append(((None, ('%s ASC' % offsetting_order_by, [], None))))

result.append(', '.join(out_cols))

if self.query.select_for_update and self.connection.features.has_select_for_update:
if self.connection.get_autocommit():
raise TransactionManagementError('select_for_update cannot be used outside of a transaction.')

if with_limits and not self.connection.features.supports_select_for_update_with_limit:
if with_limit_offset and not self.connection.features.supports_select_for_update_with_limit:
raise NotSupportedError(
'LIMIT/OFFSET is not supported with '
'select_for_update on this database backend.'
Expand All @@ -223,8 +269,7 @@ def as_sql(self, with_limits=True, with_col_aliases=False):
if for_update_part and self.connection.features.for_update_after_from:
from_.insert(1, for_update_part)

result.append('FROM')
result.extend(from_)
result += [', '.join(out_cols), 'FROM', *from_]
params.extend(f_params)

if where:
Expand All @@ -237,16 +282,20 @@ def as_sql(self, with_limits=True, with_col_aliases=False):
params.extend(g_params)
if grouping:
if distinct_fields:
raise NotImplementedError(
"annotate() + distinct(fields) is not implemented.")
if not order_by:
order_by = self.connection.ops.force_no_ordering()
raise NotImplementedError('annotate() + distinct(fields) is not implemented.')
order_by = order_by or self.connection.ops.force_no_ordering()
result.append('GROUP BY %s' % ', '.join(grouping))

if having:
result.append('HAVING %s' % having)
params.extend(h_params)

if self.query.explain_query:
result.insert(0, self.connection.ops.explain_query_prefix(
self.query.explain_format,
**self.query.explain_options
))

if order_by:
ordering = []
for _, (o_sql, o_params, _) in order_by:
Expand All @@ -269,9 +318,7 @@ def as_sql(self, with_limits=True, with_col_aliases=False):
if not self.query.subquery:
result.append('ORDER BY X.rn')
else:
result.append('OFFSET %d ROWS' % low_mark)
if do_limit:
result.append('FETCH FIRST %d ROWS ONLY' % (high_mark - low_mark))
result.append(self.connection.ops.limit_offset_sql(self.query.low_mark, self.query.high_mark))

if self.query.subquery and extra_select:
# If the query is used as a subquery, the extra selects would
Expand Down Expand Up @@ -313,6 +360,8 @@ def _as_microsoft(self, node):
as_microsoft = None
if isinstance(node, Avg):
as_microsoft = _as_sql_agv
elif isinstance(node, Chr):
as_microsoft = _as_sql_chr
elif isinstance(node, ConcatPair):
as_microsoft = _as_sql_concatpair
elif isinstance(node, Count):
Expand All @@ -323,16 +372,24 @@ def _as_microsoft(self, node):
as_microsoft = _as_sql_least
elif isinstance(node, Length):
as_microsoft = _as_sql_length
elif isinstance(node, RPad):
as_microsoft = _as_sql_rpad
elif isinstance(node, LPad):
as_microsoft = _as_sql_lpad
elif isinstance(node, Exists):
as_microsoft = _as_sql_exists
elif isinstance(node, OrderBy):
as_microsoft = _as_sql_order_by
elif isinstance(node, Repeat):
as_microsoft = _as_sql_repeat
elif isinstance(node, StdDev):
as_microsoft = _as_sql_stddev
elif isinstance(node, StrIndex):
as_microsoft = _as_sql_strindex
elif isinstance(node, Substr):
as_microsoft = _as_sql_substr
elif isinstance(node, Trim):
as_microsoft = _as_sql_trim
elif isinstance(node, Variance):
as_microsoft = _as_sql_variance
if as_microsoft:
Expand All @@ -349,11 +406,9 @@ def as_sql(self):
qn = self.connection.ops.quote_name
opts = self.query.get_meta()
result = ['INSERT INTO %s' % qn(opts.db_table)]
fields = self.query.fields or [opts.pk]

has_fields = bool(self.query.fields)

if has_fields:
fields = self.query.fields
if self.query.fields:
result.append('(%s)' % ', '.join(qn(f.column) for f in fields))
values_format = 'VALUES (%s)'
value_rows = [
Expand All @@ -370,7 +425,7 @@ def as_sql(self):
# queries and generate their own placeholders. Doing that isn't
# necessary and it should be possible to use placeholders and
# expressions in bulk inserts too.
can_bulk = (not self.return_id and self.connection.features.has_bulk_insert) and has_fields
can_bulk = (not self.return_id and self.connection.features.has_bulk_insert) and self.query.fields

placeholder_rows, param_rows = self.assemble_as_sql(fields, value_rows)

Expand All @@ -390,7 +445,7 @@ def as_sql(self):
for p, vals in zip(placeholder_rows, param_rows)
]

if has_fields:
if self.query.fields:
if opts.auto_field is not None:
# db_column is None if not explicitly specified by model field
auto_field_column = opts.auto_field.db_column or opts.auto_field.column
Expand Down
2 changes: 1 addition & 1 deletion sql_server/pyodbc/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


class DatabaseFeatures(BaseDatabaseFeatures):
allow_sliced_subqueries = False
allow_sliced_subqueries_with_in = False
can_introspect_autofield = True
can_introspect_small_integer_field = True
can_return_id_from_insert = True
Expand Down

0 comments on commit c1d7fb8

Please sign in to comment.