Skip to content

Commit c1d7fb8

Browse files
committed
provide Django 2.1 compatibility
1 parent 317b97e commit c1d7fb8

File tree

8 files changed

+106
-122
lines changed

8 files changed

+106
-122
lines changed

README.rst

+5-5
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 2.0.8
20+
- Supports Django 2.1
2121
- Supports Microsoft SQL Server 2008/2008R2, 2012, 2014, 2016, 2017 and
2222
Azure SQL Database
2323
- Passes most of the tests of the Django test suite
@@ -29,7 +29,7 @@ Features
2929
Dependencies
3030
------------
3131

32-
- Django 2.0.8
32+
- Django 2.1
3333
- pyodbc 3.0 or newer
3434

3535
Installation
@@ -241,9 +241,9 @@ The following features are currently not supported:
241241
Notice
242242
------
243243

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

249-
pip install "django-pyodbc-azure<2.0"
249+
pip install "django-pyodbc-azure<2.1"

setup.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
'Framework :: Django',
1010
'Programming Language :: Python',
1111
'Programming Language :: Python :: 3',
12-
'Programming Language :: Python :: 3.4',
1312
'Programming Language :: Python :: 3.5',
1413
'Programming Language :: Python :: 3.6',
1514
'Programming Language :: Python :: 3.7',
@@ -18,7 +17,7 @@
1817

1918
setup(
2019
name='django-pyodbc-azure',
21-
version='2.0.8.0',
20+
version='2.1.0.0',
2221
description='Django backend for Microsoft SQL Server and Azure SQL Database using pyodbc',
2322
long_description=open('README.rst').read(),
2423
author='Michiya Takahashi',
@@ -27,7 +26,7 @@
2726
license='BSD',
2827
packages=['sql_server', 'sql_server.pyodbc'],
2928
install_requires=[
30-
'Django>=2.0.8,<2.1',
29+
'Django>=2.1.0,<2.2',
3130
'pyodbc>=3.0',
3231
],
3332
classifiers=CLASSIFIERS,

sql_server/pyodbc/base.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from django.core.exceptions import ImproperlyConfigured
99
from django import VERSION
1010

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

1414
try:

sql_server/pyodbc/compiler.py

+76-21
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

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

17+
def _as_sql_chr(self, compiler, connection):
18+
return self.as_sql(compiler, connection, function='NCHAR')
19+
1520
def _as_sql_concatpair(self, compiler, connection):
1621
if connection.sql_server_version < 2012:
1722
node = self.coalesce()
@@ -39,6 +44,23 @@ def _as_sql_least(self, compiler, connection):
3944
def _as_sql_length(self, compiler, connection):
4045
return self.as_sql(compiler, connection, function='LEN')
4146

47+
def _as_sql_lpad(self, compiler, connection):
48+
i = iter(self.get_source_expressions())
49+
expression, expression_arg = compiler.compile(next(i))
50+
length, length_arg = compiler.compile(next(i))
51+
fill_text, fill_text_arg = compiler.compile(next(i))
52+
params = []
53+
params.extend(fill_text_arg)
54+
params.extend(length_arg)
55+
params.extend(length_arg)
56+
params.extend(expression_arg)
57+
params.extend(length_arg)
58+
params.extend(expression_arg)
59+
params.extend(expression_arg)
60+
template = ('LEFT(REPLICATE(%(fill_text)s, %(length)s), CASE WHEN %(length)s > LEN(%(expression)s) '
61+
'THEN %(length)s - LEN(%(expression)s) ELSE 0 END) + %(expression)s')
62+
return template % {'expression':expression, 'length':length, 'fill_text':fill_text }, params
63+
4264
def _as_sql_exists(self, compiler, connection, template=None, **extra_context):
4365
# MS SQL doesn't allow EXISTS() in the SELECT list, so wrap it with a
4466
# CASE WHEN expression. Change the template since the When expression
@@ -55,6 +77,22 @@ def _as_sql_order_by(self, compiler, connection):
5577
template = 'CASE WHEN %(expression)s IS NULL THEN 0 ELSE 1 END, %(expression)s %(ordering)s'
5678
return self.as_sql(compiler, connection, template=template)
5779

80+
def _as_sql_repeat(self, compiler, connection):
81+
return self.as_sql(compiler, connection, function='REPLICATE')
82+
83+
def _as_sql_rpad(self, compiler, connection):
84+
i = iter(self.get_source_expressions())
85+
expression, expression_arg = compiler.compile(next(i))
86+
length, length_arg = compiler.compile(next(i))
87+
fill_text, fill_text_arg = compiler.compile(next(i))
88+
params = []
89+
params.extend(expression_arg)
90+
params.extend(fill_text_arg)
91+
params.extend(length_arg)
92+
params.extend(length_arg)
93+
template='LEFT(%(expression)s + REPLICATE(%(fill_text)s, %(length)s), %(length)s)'
94+
return template % {'expression':expression, 'length':length, 'fill_text':fill_text }, params
95+
5896
def _as_sql_stddev(self, compiler, connection):
5997
function = 'STDEV'
6098
if self.function == 'STDDEV_POP':
@@ -72,6 +110,9 @@ def _as_sql_substr(self, compiler, connection):
72110
self.get_source_expressions().append(Value(2**31-1))
73111
return self.as_sql(compiler, connection)
74112

113+
def _as_sql_trim(self, compiler, connection):
114+
return self.as_sql(compiler, connection, template='LTRIM(RTRIM(%(expressions)s))')
115+
75116
def _as_sql_variance(self, compiler, connection):
76117
function = 'VAR'
77118
if self.function == 'VAR_POP':
@@ -120,6 +161,8 @@ def as_sql(self, with_limits=True, with_col_aliases=False):
120161
try:
121162
extra_select, order_by, group_by = self.pre_sql_setup()
122163
for_update_part = None
164+
# Is a LIMIT/OFFSET clause needed?
165+
with_limit_offset = with_limits and (self.query.high_mark is not None or self.query.low_mark)
123166
combinator = self.query.combinator
124167
features = self.connection.features
125168

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

150193
if self.query.distinct:
151-
result.append(self.connection.ops.distinct_sql(distinct_fields))
194+
distinct_result, distinct_params = self.connection.ops.distinct_sql(
195+
distinct_fields,
196+
distinct_params,
197+
)
198+
result += distinct_result
199+
params += distinct_params
152200

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

194-
result.append(', '.join(out_cols))
195-
196242
if self.query.select_for_update and self.connection.features.has_select_for_update:
197243
if self.connection.get_autocommit():
198244
raise TransactionManagementError('select_for_update cannot be used outside of a transaction.')
199245

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

226-
result.append('FROM')
227-
result.extend(from_)
272+
result += [', '.join(out_cols), 'FROM', *from_]
228273
params.extend(f_params)
229274

230275
if where:
@@ -237,16 +282,20 @@ def as_sql(self, with_limits=True, with_col_aliases=False):
237282
params.extend(g_params)
238283
if grouping:
239284
if distinct_fields:
240-
raise NotImplementedError(
241-
"annotate() + distinct(fields) is not implemented.")
242-
if not order_by:
243-
order_by = self.connection.ops.force_no_ordering()
285+
raise NotImplementedError('annotate() + distinct(fields) is not implemented.')
286+
order_by = order_by or self.connection.ops.force_no_ordering()
244287
result.append('GROUP BY %s' % ', '.join(grouping))
245288

246289
if having:
247290
result.append('HAVING %s' % having)
248291
params.extend(h_params)
249292

293+
if self.query.explain_query:
294+
result.insert(0, self.connection.ops.explain_query_prefix(
295+
self.query.explain_format,
296+
**self.query.explain_options
297+
))
298+
250299
if order_by:
251300
ordering = []
252301
for _, (o_sql, o_params, _) in order_by:
@@ -269,9 +318,7 @@ def as_sql(self, with_limits=True, with_col_aliases=False):
269318
if not self.query.subquery:
270319
result.append('ORDER BY X.rn')
271320
else:
272-
result.append('OFFSET %d ROWS' % low_mark)
273-
if do_limit:
274-
result.append('FETCH FIRST %d ROWS ONLY' % (high_mark - low_mark))
321+
result.append(self.connection.ops.limit_offset_sql(self.query.low_mark, self.query.high_mark))
275322

276323
if self.query.subquery and extra_select:
277324
# If the query is used as a subquery, the extra selects would
@@ -313,6 +360,8 @@ def _as_microsoft(self, node):
313360
as_microsoft = None
314361
if isinstance(node, Avg):
315362
as_microsoft = _as_sql_agv
363+
elif isinstance(node, Chr):
364+
as_microsoft = _as_sql_chr
316365
elif isinstance(node, ConcatPair):
317366
as_microsoft = _as_sql_concatpair
318367
elif isinstance(node, Count):
@@ -323,16 +372,24 @@ def _as_microsoft(self, node):
323372
as_microsoft = _as_sql_least
324373
elif isinstance(node, Length):
325374
as_microsoft = _as_sql_length
375+
elif isinstance(node, RPad):
376+
as_microsoft = _as_sql_rpad
377+
elif isinstance(node, LPad):
378+
as_microsoft = _as_sql_lpad
326379
elif isinstance(node, Exists):
327380
as_microsoft = _as_sql_exists
328381
elif isinstance(node, OrderBy):
329382
as_microsoft = _as_sql_order_by
383+
elif isinstance(node, Repeat):
384+
as_microsoft = _as_sql_repeat
330385
elif isinstance(node, StdDev):
331386
as_microsoft = _as_sql_stddev
332387
elif isinstance(node, StrIndex):
333388
as_microsoft = _as_sql_strindex
334389
elif isinstance(node, Substr):
335390
as_microsoft = _as_sql_substr
391+
elif isinstance(node, Trim):
392+
as_microsoft = _as_sql_trim
336393
elif isinstance(node, Variance):
337394
as_microsoft = _as_sql_variance
338395
if as_microsoft:
@@ -349,11 +406,9 @@ def as_sql(self):
349406
qn = self.connection.ops.quote_name
350407
opts = self.query.get_meta()
351408
result = ['INSERT INTO %s' % qn(opts.db_table)]
409+
fields = self.query.fields or [opts.pk]
352410

353-
has_fields = bool(self.query.fields)
354-
355-
if has_fields:
356-
fields = self.query.fields
411+
if self.query.fields:
357412
result.append('(%s)' % ', '.join(qn(f.column) for f in fields))
358413
values_format = 'VALUES (%s)'
359414
value_rows = [
@@ -370,7 +425,7 @@ def as_sql(self):
370425
# queries and generate their own placeholders. Doing that isn't
371426
# necessary and it should be possible to use placeholders and
372427
# expressions in bulk inserts too.
373-
can_bulk = (not self.return_id and self.connection.features.has_bulk_insert) and has_fields
428+
can_bulk = (not self.return_id and self.connection.features.has_bulk_insert) and self.query.fields
374429

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

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

393-
if has_fields:
448+
if self.query.fields:
394449
if opts.auto_field is not None:
395450
# db_column is None if not explicitly specified by model field
396451
auto_field_column = opts.auto_field.db_column or opts.auto_field.column

sql_server/pyodbc/features.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33

44
class DatabaseFeatures(BaseDatabaseFeatures):
5-
allow_sliced_subqueries = False
5+
allow_sliced_subqueries_with_in = False
66
can_introspect_autofield = True
77
can_introspect_small_integer_field = True
88
can_return_id_from_insert = True

0 commit comments

Comments
 (0)